diff --git a/.github/ISSUE_TEMPLATE/Bug_report.yml b/.github/ISSUE_TEMPLATE/Bug_report.yml index afa1a27f8..f6fb7d280 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.yml +++ b/.github/ISSUE_TEMPLATE/Bug_report.yml @@ -11,22 +11,35 @@ body: required: true - type: checkboxes attributes: - label: I've found a bug and checked that ... - description: Prior to placing the issue, please check following:** *(fill out each checkbox with an `X` once done)* + label: Checklist prior issue creation + description: Prior to creating the issue... options: - - label: ... I understand that not following the below instructions will result in immediate closure and/or deletion of my issue. + - label: I understand that failure to follow below instructions may cause this issue to be closed. required: true - - label: ... I have understood that this bug report is dedicated for bugs, and not for support-related inquiries. + - label: I understand that vague, incomplete or inaccurate information may cause this issue to be closed. required: true - - label: ... I have understood that answers are voluntary and community-driven, and not commercial support. + - label: I understand that this form is intended solely for reporting software bugs and not for support-related inquiries. required: true - - label: ... I have verified that my issue has not been already answered in the past. I also checked previous [issues](https://github.com/mailcow/mailcow-dockerized/issues). + - 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 in 1-2 sentences. If applicable, add screenshots to help explain your problem. Very useful for bugs in mailcow UI. - render: plain text + 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 @@ -36,45 +49,36 @@ body: render: plain text 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." - render: plain text - placeholder: |- - 1. ... - 2. ... - 3. ... - 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. + 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: "#### `git rev-parse --abbrev-ref HEAD`" + description: "#### Run: `git rev-parse --abbrev-ref HEAD`" multiple: false options: - - master + - master (stable) + - staging - nightly validations: required: true - type: dropdown attributes: label: "Which architecture are you using?" - description: "#### `uname -m`" + description: "#### Run: `uname -m`" multiple: false options: - - x86 + - 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 @@ -93,43 +97,44 @@ body: - type: input attributes: label: "Virtualization technology:" - placeholder: "KVM, VMware, Xen, etc - **LXC and OpenVZ are not supported**" + description: "LXC and OpenVZ are not supported!" + placeholder: "KVM, VMware ESXi, Xen, etc" validations: required: true - type: input attributes: label: "Docker version:" - description: "#### `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: "#### `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: "#### ```git describe --tags `git rev-list --tags --max-count=1` ```" - placeholder: "2022-08" + 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" + 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? If so, **please post them**:" + 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: true + required: false - type: textarea attributes: label: "Logs of iptables -L -vn:" diff --git a/.github/workflows/close_old_issues_and_prs.yml b/.github/workflows/close_old_issues_and_prs.yml index 91870fd1d..bba04b0d1 100644 --- a/.github/workflows/close_old_issues_and_prs.yml +++ b/.github/workflows/close_old_issues_and_prs.yml @@ -14,7 +14,7 @@ jobs: pull-requests: write steps: - name: Mark/Close Stale Issues and Pull Requests 🗑️ - uses: actions/stale@v10.0.0 + uses: actions/stale@v10.4.0 with: repo-token: ${{ secrets.STALE_ACTION_PAT }} days-before-stale: 60 diff --git a/.github/workflows/image_builds.yml b/.github/workflows/image_builds.yml index a5ebb902f..aa56b0381 100644 --- a/.github/workflows/image_builds.yml +++ b/.github/workflows/image_builds.yml @@ -27,7 +27,7 @@ jobs: - "watchdog-mailcow" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Setup Docker run: | curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh diff --git a/.github/workflows/pr_to_nightly.yml b/.github/workflows/pr_to_nightly.yml index 334dcf69a..8d6b2524d 100644 --- a/.github/workflows/pr_to_nightly.yml +++ b/.github/workflows/pr_to_nightly.yml @@ -8,11 +8,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Run the Action - uses: devops-infra/action-pull-request@v0.6.1 + 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}} diff --git a/.github/workflows/rebuild_backup_image.yml b/.github/workflows/rebuild_backup_image.yml index a8679d980..4490afee1 100644 --- a/.github/workflows/rebuild_backup_image.yml +++ b/.github/workflows/rebuild_backup_image.yml @@ -13,24 +13,24 @@ jobs: packages: write steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v7 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Login to GHCR if: github.event_name != 'pull_request' - uses: docker/login-action@v3 + 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@v6 + uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/update_postscreen_access_list.yml b/.github/workflows/update_postscreen_access_list.yml index eed07876e..ed63281c7 100644 --- a/.github/workflows/update_postscreen_access_list.yml +++ b/.github/workflows/update_postscreen_access_list.yml @@ -15,14 +15,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + 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@v7 + uses: peter-evans/create-pull-request@v8 with: token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }} commit-message: update postscreen_access.cidr diff --git a/.gitignore b/.gitignore index a06c3da7f..416af9f7d 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ 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/ data/hooks/dovecot/* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fae8f1d5f..5e194b20d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,11 +1,11 @@ # Contribution Guidelines -**_Last modified on 15th August 2024_** +**_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 MIGHT CLOSE ISSUES/PULL REQUESTS IF THEY DON'T FULLFIL OUR WRITTEN GUIDELINES WRITTEN INSIDE THIS DOCUMENT**. So please check this guidelines before you propose a Issue/Pull Request. +**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 @@ -27,14 +27,18 @@ However, please note the following regarding pull requests: 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 15th August 2024_** +**_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). diff --git a/README.md b/README.md index 40288f10e..8844c7482 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,9 @@ A big thank you to everyone supporting us on GitHub Sponsors—your contribution + ### 50$/Month Sponsors &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 @@ -32,45 +38,45 @@ get_docker_version(){ } get_compose_type(){ - if docker compose > /dev/null 2>&1; then - if docker compose version --short | grep -e "^2." -e "^v2." > /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 "^2." > /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 + 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.\e[0m" - echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m" - exit 1 + 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() { @@ -221,4 +227,4 @@ detect_major_update() { fi fi fi -} \ No newline at end of file +} diff --git a/_modules/scripts/ipv6_controller.sh b/_modules/scripts/ipv6_controller.sh index 1ff5eb198..a025ee178 100644 --- a/_modules/scripts/ipv6_controller.sh +++ b/_modules/scripts/ipv6_controller.sh @@ -155,6 +155,7 @@ docker_daemon_edit(){ fi if [[ $ans =~ ^[Yy]$ ]]; then + mkdir -p "$(dirname "$DOCKER_DAEMON_CONFIG")" if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then cat > "$DOCKER_DAEMON_CONFIG" <> 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 @@ -276,21 +279,36 @@ adapt_new_options() { 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 ;; diff --git a/data/Dockerfiles/acme/Dockerfile b/data/Dockerfiles/acme/Dockerfile index f6e990e57..193ca7e00 100644 --- a/data/Dockerfiles/acme/Dockerfile +++ b/data/Dockerfiles/acme/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.21 +FROM alpine:3.23 LABEL maintainer = "The Infrastructure Company GmbH " @@ -14,11 +14,22 @@ RUN apk upgrade --no-cache \ tini \ tzdata \ python3 \ - acme-tiny + 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 + +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 diff --git a/data/Dockerfiles/acme/acme.sh b/data/Dockerfiles/acme/acme.sh index 69b18bc1f..271de4fc9 100755 --- a/data/Dockerfiles/acme/acme.sh +++ b/data/Dockerfiles/acme/acme.sh @@ -14,6 +14,17 @@ until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do 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 @@ -42,6 +53,10 @@ 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 @@ -238,14 +253,46 @@ while true; do unset VALIDATED_CONFIG_DOMAINS_SUBDOMAINS declare -a VALIDATED_CONFIG_DOMAINS_SUBDOMAINS for SUBDOMAIN in "${ADDITIONAL_WC_ARR[@]}"; do - if [[ "${SUBDOMAIN}.${SQL_DOMAIN}" != "${MAILCOW_HOSTNAME}" ]]; then - if check_domain "${SUBDOMAIN}.${SQL_DOMAIN}"; then - VALIDATED_CONFIG_DOMAINS_SUBDOMAINS+=("${SUBDOMAIN}.${SQL_DOMAIN}") - fi + FULL_SUBDOMAIN="${SUBDOMAIN}.${SQL_DOMAIN}" + + # 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 @@ -274,13 +321,31 @@ while true; do 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 - SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs)) + 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 [*] - SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs)) + 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]} diff --git a/data/Dockerfiles/acme/functions.sh b/data/Dockerfiles/acme/functions.sh index 183be01b0..707f46950 100644 --- a/data/Dockerfiles/acme/functions.sh +++ b/data/Dockerfiles/acme/functions.sh @@ -80,6 +80,11 @@ check_domain(){ 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= @@ -130,3 +135,32 @@ verify_challenge_path(){ 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 000000000..af9caeaf6 --- /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 000000000..f88089f01 --- /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 index f476bf666..9d727a202 100644 --- a/data/Dockerfiles/acme/obtain-certificate.sh +++ b/data/Dockerfiles/acme/obtain-certificate.sh @@ -20,6 +20,10 @@ 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 diff --git a/data/Dockerfiles/backup/Dockerfile b/data/Dockerfiles/backup/Dockerfile index 6234e725b..9ff6bbb13 100644 --- a/data/Dockerfiles/backup/Dockerfile +++ b/data/Dockerfiles/backup/Dockerfile @@ -1,3 +1,3 @@ -FROM debian:bookworm-slim +FROM debian:trixie-slim -RUN apt update && apt install pigz -y --no-install-recommends \ No newline at end of file +RUN apt update && apt install pigz zstd -y --no-install-recommends \ No newline at end of file diff --git a/data/Dockerfiles/dockerapi/Dockerfile b/data/Dockerfiles/dockerapi/Dockerfile index 872764317..c27f6154b 100644 --- a/data/Dockerfiles/dockerapi/Dockerfile +++ b/data/Dockerfiles/dockerapi/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.21 +FROM alpine:3.23 LABEL maintainer = "The Infrastructure Company GmbH " diff --git a/data/Dockerfiles/dockerapi/main.py b/data/Dockerfiles/dockerapi/main.py index 57e262864..bf197bd61 100644 --- a/data/Dockerfiles/dockerapi/main.py +++ b/data/Dockerfiles/dockerapi/main.py @@ -110,12 +110,12 @@ async def get_container(container_id : str): return Response(content=json.dumps(res, indent=4), media_type="application/json") @app.get("/containers/json") -async def get_containers(): +async def get_containers(all: bool = False): global dockerapi containers = {} try: - for container in (await dockerapi.async_docker_client.containers.list()): + 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") diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index 10e141ab8..f1152c8a1 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -3,7 +3,7 @@ FROM alpine:3.21 LABEL maintainer="The Infrastructure Company GmbH " # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?.*)$ -ARG GOSU_VERSION=1.17 +ARG GOSU_VERSION=1.19 ENV LANG=C.UTF-8 ENV LC_ALL=C.UTF-8 diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index fe2341bfa..e58abbb6a 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -204,16 +204,17 @@ 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 -# Creating additional creds file for SOGo notify crons (calendars, etc) -echo -n ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/cron.creds cat < /etc/dovecot/sogo-sso.conf # Autogenerated by mailcow passdb { driver = static - args = allow_real_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS} + 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 diff --git a/data/Dockerfiles/dovecot/quarantine_notify.py b/data/Dockerfiles/dovecot/quarantine_notify.py index a681c1fda..5757cdf53 100755 --- a/data/Dockerfiles/dovecot/quarantine_notify.py +++ b/data/Dockerfiles/dovecot/quarantine_notify.py @@ -47,7 +47,7 @@ try: if max_score == "": max_score = 9999.0 - def query_mysql(query, headers = True, update = False): + 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") @@ -57,7 +57,10 @@ try: else: break cur = cnx.cursor() - cur.execute(query) + if params: + cur.execute(query, params) + else: + cur.execute(query) if not update: result = [] columns = tuple( [d[0] for d in cur.description] ) @@ -76,7 +79,7 @@ try: 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 < %f AND (action = "%s" OR "all" = "%s")' % (rcpt, max_score, category, category)) + 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 @@ -130,7 +133,7 @@ try: 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 = "%d"' % (res['id']), update = True) + 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: @@ -138,7 +141,7 @@ try: 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 < %f AND rcpt in (SELECT username FROM mailbox) GROUP BY rcpt' % (max_score)) + 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 = '' @@ -156,7 +159,7 @@ try: 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_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 diff --git a/data/Dockerfiles/netfilter/Dockerfile b/data/Dockerfiles/netfilter/Dockerfile index 57dbd6e94..70cd49c1a 100644 --- a/data/Dockerfiles/netfilter/Dockerfile +++ b/data/Dockerfiles/netfilter/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.21 +FROM alpine:3.23 LABEL maintainer = "The Infrastructure Company GmbH " @@ -40,4 +40,4 @@ COPY ./docker-entrypoint.sh /app/ RUN chmod +x /app/docker-entrypoint.sh -CMD ["/bin/sh", "-c", "/app/docker-entrypoint.sh"] \ No newline at end of file +CMD ["/bin/sh", "-c", "/app/docker-entrypoint.sh"] diff --git a/data/Dockerfiles/netfilter/docker-entrypoint.sh b/data/Dockerfiles/netfilter/docker-entrypoint.sh index 47370a1fe..98cab0a72 100755 --- a/data/Dockerfiles/netfilter/docker-entrypoint.sh +++ b/data/Dockerfiles/netfilter/docker-entrypoint.sh @@ -1,6 +1,6 @@ #!/bin/sh -backend=iptables +backend=nftables nft list table ip filter &>/dev/null nftables_found=$? diff --git a/data/Dockerfiles/netfilter/main.py b/data/Dockerfiles/netfilter/main.py index 2232d0d1d..5b718a94b 100644 --- a/data/Dockerfiles/netfilter/main.py +++ b/data/Dockerfiles/netfilter/main.py @@ -449,6 +449,11 @@ if __name__ == '__main__': 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() diff --git a/data/Dockerfiles/netfilter/modules/Logger.py b/data/Dockerfiles/netfilter/modules/Logger.py index 0ba2f42ad..b5114539b 100644 --- a/data/Dockerfiles/netfilter/modules/Logger.py +++ b/data/Dockerfiles/netfilter/modules/Logger.py @@ -1,5 +1,6 @@ import time import json +import datetime class Logger: def __init__(self): @@ -8,17 +9,28 @@ class Logger: 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): - tolog = {} - tolog['time'] = int(round(time.time())) - tolog['priority'] = priority - tolog['message'] = message - print(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('Failed logging to redis: %s' % (ex)) + print(f'{ts} WARN: Failed logging to redis: {ex}', flush=True) def logWarn(self, message): self.log('warn', message) @@ -27,4 +39,4 @@ class Logger: self.log('crit', message) def logInfo(self, message): - self.log('info', message) + self.log('info', message) \ No newline at end of file diff --git a/data/Dockerfiles/nginx/Dockerfile b/data/Dockerfiles/nginx/Dockerfile index 7d2ce34f3..65b34838e 100644 --- a/data/Dockerfiles/nginx/Dockerfile +++ b/data/Dockerfiles/nginx/Dockerfile @@ -1,4 +1,4 @@ -FROM nginx:alpine +FROM nginx:1.30.3-alpine LABEL maintainer "The Infrastructure Company GmbH " ENV PIP_BREAK_SYSTEM_PACKAGES=1 diff --git a/data/Dockerfiles/nginx/bootstrap.py b/data/Dockerfiles/nginx/bootstrap.py index d2d01c0b9..97ce7a4e1 100644 --- a/data/Dockerfiles/nginx/bootstrap.py +++ b/data/Dockerfiles/nginx/bootstrap.py @@ -10,7 +10,7 @@ def includes_conf(env, template_vars): 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 not template_vars['ENABLE_IPV6']: + 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;" diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index e7b43790b..ca252163a 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -3,17 +3,17 @@ FROM php:8.2-fpm-alpine3.21 LABEL maintainer = "The Infrastructure Company GmbH " # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?.*)$ -ARG APCU_PECL_VERSION=5.1.26 +ARG APCU_PECL_VERSION=5.1.28 # renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?.*)$ -ARG IMAGICK_PECL_VERSION=3.8.0 +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.1.8 +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.3.0 +ARG MEMCACHED_PECL_VERSION=3.4.0 # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?.*)$ -ARG REDIS_PECL_VERSION=6.2.0 +ARG REDIS_PECL_VERSION=6.3.0 # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?.*)$ -ARG COMPOSER_VERSION=2.8.6 +ARG COMPOSER_VERSION=2.10.2 RUN apk add -U --no-cache autoconf \ aspell-dev \ diff --git a/data/Dockerfiles/phpfpm/docker-entrypoint.sh b/data/Dockerfiles/phpfpm/docker-entrypoint.sh index 0d09ac5fc..d7fa15556 100755 --- a/data/Dockerfiles/phpfpm/docker-entrypoint.sh +++ b/data/Dockerfiles/phpfpm/docker-entrypoint.sh @@ -167,7 +167,7 @@ DELIMITER // CREATE EVENT clean_spamalias ON SCHEDULE EVERY 1 DAY DO BEGIN - DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP(); + DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP() AND permanent = 0; END; // DELIMITER ; diff --git a/data/Dockerfiles/postfix-tlspol/Dockerfile b/data/Dockerfiles/postfix-tlspol/Dockerfile index c32f86f57..68f6ecced 100644 --- a/data/Dockerfiles/postfix-tlspol/Dockerfile +++ b/data/Dockerfiles/postfix-tlspol/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /src ENV CGO_ENABLED=0 \ GO111MODULE=on \ NOOPT=1 \ - VERSION=1.8.14 + VERSION=1.8.22 RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \ cd /src/postfix-tlspol && \ diff --git a/data/Dockerfiles/postfix/Dockerfile b/data/Dockerfiles/postfix/Dockerfile index 994612ec4..cf0647692 100644 --- a/data/Dockerfiles/postfix/Dockerfile +++ b/data/Dockerfiles/postfix/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:bookworm-slim +FROM debian:trixie-slim LABEL maintainer="The Infrastructure Company GmbH " diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index 0a6494ed6..51927ea11 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -329,14 +329,17 @@ query = SELECT goto FROM alias 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' @@ -390,7 +393,7 @@ 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 if [ ! -f /opt/postfix/conf/dns_blocklists.cf ]; then @@ -524,4 +527,4 @@ if [[ $? != 0 ]]; then else postfix -c /opt/postfix/conf start sleep 126144000 -fi \ No newline at end of file +fi diff --git a/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf b/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf index 8e15932a2..bbc9420fe 100644 --- a/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf +++ b/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf @@ -1,4 +1,4 @@ -@version: 3.38 +@version: 4.8 @include "scl.conf" options { chain_hostnames(off); @@ -7,7 +7,7 @@ options { dns_cache(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); - stats_freq(0); + stats(freq(0)); bad_hostname("^gconfd$"); }; source s_src { diff --git a/data/Dockerfiles/postfix/syslog-ng.conf b/data/Dockerfiles/postfix/syslog-ng.conf index fc7d1aa0f..8f09d04a4 100644 --- a/data/Dockerfiles/postfix/syslog-ng.conf +++ b/data/Dockerfiles/postfix/syslog-ng.conf @@ -1,4 +1,4 @@ -@version: 3.38 +@version: 4.8 @include "scl.conf" options { chain_hostnames(off); @@ -7,7 +7,7 @@ options { dns_cache(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); - stats_freq(0); + stats(freq(0)); bad_hostname("^gconfd$"); }; source s_src { diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile index e46981aa4..d37d1226a 100644 --- a/data/Dockerfiles/rspamd/Dockerfile +++ b/data/Dockerfiles/rspamd/Dockerfile @@ -1,9 +1,9 @@ -FROM debian:bookworm-slim +FROM debian:trixie-slim LABEL maintainer="The Infrastructure Company GmbH " ARG DEBIAN_FRONTEND=noninteractive -ARG RSPAMD_VER=rspamd_3.12.1-1~6dbfca2fa -ARG CODENAME=bookworm +ARG RSPAMD_VER=rspamd_4.1.0-1~e2b0b18 +ARG CODENAME=trixie ENV LC_ALL=C RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -14,8 +14,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ dnsutils \ netcat-traditional \ wget \ - redis-tools \ - procps \ + redis-tools \ + procps \ nano \ lua-cjson \ && arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \ diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index f2981ad04..b4b401e8c 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -1,47 +1,167 @@ -FROM debian:bookworm-slim +# 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 -ARG DEBIAN_VERSION=bookworm -ARG SOGO_DEBIAN_REPOSITORY=https://packagingv2.sogo.nu/sogo-nightly-debian/ +ARG SOGO_VERSION=SOGo-5.12.9 +ARG SOPE_VERSION=SOPE-5.12.9 +# 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.17 +ARG GOSU_VERSION=1.19 ENV LC_ALL=C -# Prerequisites -RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \ - && apt-get update && apt-get install -y --no-install-recommends \ - apt-transport-https \ - ca-certificates \ - gettext \ - gnupg \ - mariadb-client \ - rsync \ - supervisor \ - syslog-ng \ - syslog-ng-core \ - syslog-ng-mod-redis \ - dirmngr \ - netcat-traditional \ - psmisc \ - wget \ - patch \ +# 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 \ + # 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 \ + 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 \ - && mkdir /usr/share/doc/sogo \ - && touch /usr/share/doc/sogo/empty.sh \ - && wget -O- https://keys.openpgp.org/vks/v1/by-fingerprint/74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9 | gpg --dearmor | apt-key add - \ - && echo "deb [trusted=yes] ${SOGO_DEBIAN_REPOSITORY} ${DEBIAN_VERSION} main" > /etc/apt/sources.list.d/sogo.list \ - && apt-get update && apt-get install -y --no-install-recommends \ - sogo \ - sogo-activesync \ - && apt-get autoclean \ + # 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 @@ -56,4 +176,4 @@ RUN chmod +x /bootstrap-sogo.sh \ ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] \ No newline at end of file +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] diff --git a/data/Dockerfiles/sogo/acl.diff b/data/Dockerfiles/sogo/acl.diff index 513700388..e6b5d0520 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 abd398b34..1b2cd2ace 100755 --- a/data/Dockerfiles/sogo/bootstrap-sogo.sh +++ b/data/Dockerfiles/sogo/bootstrap-sogo.sh @@ -24,6 +24,10 @@ while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do 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 + # 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) @@ -126,18 +130,22 @@ chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist # Patch ACLs #if [[ ${ACL_ANYONE} == 'allow' ]]; then # #enable any or authenticated targets for ACL -# if patch -R -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then -# patch -R /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff; +# 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/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then -# patch /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff; +# 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 -if patch -R -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff > /dev/null; then - patch -R /usr/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff; +# 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 @@ -145,7 +153,7 @@ fi # Rsync web content echo "Syncing web content with named volume" -rsync -a /usr/lib/GNUstep/SOGo/. /sogo_web/ +rsync -a /usr/local/lib/GNUstep/SOGo/. /sogo_web/ # Chown backup path chown -R sogo:sogo /sogo_backup diff --git a/data/Dockerfiles/unbound/Dockerfile b/data/Dockerfiles/unbound/Dockerfile index 4903750ed..b1ec99e4f 100644 --- a/data/Dockerfiles/unbound/Dockerfile +++ b/data/Dockerfiles/unbound/Dockerfile @@ -1,12 +1,15 @@ -FROM alpine:3.21 +FROM alpine:3.23 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 \ bind-tools \ coreutils \ - unbound \ bash \ openssl \ drill \ diff --git a/data/Dockerfiles/watchdog/Dockerfile b/data/Dockerfiles/watchdog/Dockerfile index 6d8541d79..54854ea1a 100644 --- a/data/Dockerfiles/watchdog/Dockerfile +++ b/data/Dockerfiles/watchdog/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.21 +FROM alpine:3.23 LABEL maintainer = "The Infrastructure Company GmbH " @@ -37,5 +37,6 @@ RUN apk add --update \ 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 CMD ["/watchdog.sh"] diff --git a/data/Dockerfiles/watchdog/check_dns.sh b/data/Dockerfiles/watchdog/check_dns.sh index ce4cfa3b1..ba430fe95 100755 --- a/data/Dockerfiles/watchdog/check_dns.sh +++ b/data/Dockerfiles/watchdog/check_dns.sh @@ -19,19 +19,19 @@ if [ -z "$HOST" ]; then fi # run dig and measure the time it takes to run -START_TIME=$(date +%s%3N) +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 ',' -) -END_TIME=$(date +%s%3N) -ELAPSED_TIME=$((END_TIME - START_TIME)) +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 ms response time. $HOST returns $dig_output_ips" + echo "DNS OK: $ELAPSED_TIME seconds response time. $HOST returns $dig_output_ips" exit 0 else echo "Unknown error" diff --git a/data/Dockerfiles/watchdog/client.cnf b/data/Dockerfiles/watchdog/client.cnf new file mode 100644 index 000000000..f078f3cbe --- /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 558aaa197..020f3f838 100755 --- a/data/Dockerfiles/watchdog/watchdog.sh +++ b/data/Dockerfiles/watchdog/watchdog.sh @@ -38,7 +38,7 @@ if [[ ! -p /tmp/com_pipe ]]; then fi # Wait for containers -while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do +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 @@ -359,8 +359,8 @@ mysql_checks() { while [ ${err_count} -lt ${THRESHOLD} ]; do 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} 2>> /tmp/mysql-mailcow 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" 2>> /tmp/mysql-mailcow 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} @@ -384,7 +384,7 @@ mysql_repl_checks() { 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 -S /var/run/mysqld/mysqld.sock -u root -p ${DBROOT} 2>> /tmp/mysql_repl_checks 1>&2; err_count=$(( ${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} diff --git a/data/web/inc/lib/vendor/tightenco/collect/framework-.zip b/data/conf/acme/.gitkeep similarity index 100% rename from data/web/inc/lib/vendor/tightenco/collect/framework-.zip rename to data/conf/acme/.gitkeep diff --git a/data/conf/dovecot/auth/mailcowauth.php b/data/conf/dovecot/auth/mailcowauth.php index 06c0bd995..d1c7381f6 100644 --- a/data/conf/dovecot/auth/mailcowauth.php +++ b/data/conf/dovecot/auth/mailcowauth.php @@ -80,14 +80,21 @@ if ($isSOGoRequest) { } if ($result === false){ // If it's a SOGo Request, don't check for protocol access - $service = ($isSOGoRequest) ? false : array($post['service'] => true); - $result = apppass_login($post['username'], $post['password'], $service, array( + 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 " . $post['service'] . " from IP " . $post['real_rip']); - set_sasl_log($post['username'], $post['real_rip'], $post['service']); + 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){ diff --git a/data/conf/nginx/templates/nginx.conf.j2 b/data/conf/nginx/templates/nginx.conf.j2 index d71d63e56..08a85a144 100644 --- a/data/conf/nginx/templates/nginx.conf.j2 +++ b/data/conf/nginx/templates/nginx.conf.j2 @@ -13,6 +13,7 @@ events { 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" ' @@ -78,7 +79,7 @@ http { {%endif%} listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; - {% if not DISABLE_IPv6 %} + {% if ENABLE_IPV6 %} {% if not HTTP_REDIRECT %} listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; {%endif%} @@ -105,7 +106,7 @@ http { {%endif%} listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; - {% if not DISABLE_IPv6 %} + {% if ENABLE_IPV6 %} {% if not HTTP_REDIRECT %} listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; {%endif%} @@ -126,7 +127,7 @@ http { # rspamd dynmaps: server { listen 8081; - {% if not DISABLE_IPv6 %} + {% if ENABLE_IPV6 %} listen [::]:8081; {%endif%} index index.php index.html; @@ -199,7 +200,7 @@ http { {%endif%} listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; - {% if not DISABLE_IPv6 %} + {% if ENABLE_IPV6 %} {% if not HTTP_REDIRECT %} listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; {%endif%} diff --git a/data/conf/nginx/templates/sites-default.conf.j2 b/data/conf/nginx/templates/sites-default.conf.j2 index 23fe7b788..84bd8eae4 100644 --- a/data/conf/nginx/templates/sites-default.conf.j2 +++ b/data/conf/nginx/templates/sites-default.conf.j2 @@ -14,7 +14,6 @@ ssl_session_tickets off; add_header Strict-Transport-Security "max-age=15768000;"; 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-Frame-Options "SAMEORIGIN" always; @@ -262,19 +261,19 @@ location ~* /sogo$ { } location /SOGo.woa/WebServerResources/ { - alias /usr/lib/GNUstep/SOGo/WebServerResources/; + alias /usr/local/lib/GNUstep/SOGo/WebServerResources/; } location /.woa/WebServerResources/ { - alias /usr/lib/GNUstep/SOGo/WebServerResources/; + alias /usr/local/lib/GNUstep/SOGo/WebServerResources/; } location /SOGo/WebServerResources/ { - alias /usr/lib/GNUstep/SOGo/WebServerResources/; + alias /usr/local/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; + alias /usr/local/lib/GNUstep/SOGo/$1.SOGo/Resources/$2; } {% endif %} diff --git a/data/conf/phpfpm/php-conf.d/opcache-recommended.ini b/data/conf/phpfpm/php-conf.d/opcache-recommended.ini index 3c51e11b7..452b45484 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=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/postfix/main.cf b/data/conf/postfix/main.cf index f091cb3f9..080be499a 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -37,7 +37,7 @@ postscreen_access_list = permit_mynetworks, 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 @@ -107,8 +107,6 @@ smtpd_sender_restrictions = reject_authenticated_sender_login_mismatch, 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 diff --git a/data/conf/postfix/master.cf b/data/conf/postfix/master.cf index d5114df28..fb49f2df2 100644 --- a/data/conf/postfix/master.cf +++ b/data/conf/postfix/master.cf @@ -29,7 +29,6 @@ smtps inet n - n - - smtpd # 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 @@ -38,7 +37,6 @@ submission inet n - n - - smtpd 10587 inet n - n - - smtpd -o smtpd_upstream_proxy_protocol=haproxy -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 diff --git a/data/conf/postfix/postscreen_access.cidr b/data/conf/postfix/postscreen_access.cidr index 35fc1a802..6fcedff95 100644 --- a/data/conf/postfix/postscreen_access.cidr +++ b/data/conf/postfix/postscreen_access.cidr @@ -1,12 +1,14 @@ -# Whitelist generated by Postwhite v3.4 on Mon Sep 1 00:23:07 UTC 2025 +# Whitelist generated by Postwhite v3.4 on Wed Jul 1 00:51:20 UTC 2026 # https://github.com/stevejenkins/postwhite/ -# 2165 total rules +# 2251 total rules 2a00:1450:4000::/36 permit +2a00:1450:4864::/56 permit 2a01:111:f400::/48 permit -2a01:111:f403::/49 permit -2a01:111:f403:8000::/50 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 @@ -28,8 +30,13 @@ 2a01:b747:3005:200::/56 permit 2a01:b747:3006:200::/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 @@ -49,13 +56,9 @@ 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.250/31 permit 8.39.144.0/24 permit -8.40.222.0/23 permit -8.40.222.250/31 permit 12.130.86.238 permit -13.107.246.40 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 @@ -167,13 +170,13 @@ 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 @@ -183,7 +186,6 @@ 37.218.249.47 permit 37.218.251.62 permit 39.156.163.64/29 permit -40.90.65.81 permit 40.92.0.0/15 permit 40.92.0.0/16 permit 40.107.0.0/16 permit @@ -191,7 +193,6 @@ 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 @@ -271,12 +272,9 @@ 50.56.130.221 permit 50.56.130.222 permit 50.112.246.219 permit -51.77.79.158 permit 51.83.17.38 permit -51.89.119.103 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 @@ -300,15 +298,6 @@ 52.94.124.0/28 permit 52.95.48.152/29 permit 52.95.49.88/29 permit -52.96.91.34 permit -52.96.111.82 permit -52.96.172.98 permit -52.96.214.50 permit -52.96.222.194 permit -52.96.222.226 permit -52.96.223.2 permit -52.96.228.130 permit -52.96.229.242 permit 52.100.0.0/15 permit 52.102.0.0/16 permit 52.103.0.0/17 permit @@ -324,8 +313,6 @@ 52.234.172.96/28 permit 52.235.253.128 permit 52.236.28.240/28 permit -54.36.149.183 permit -54.38.221.122 permit 54.90.148.255 permit 54.165.19.38 permit 54.174.52.0/24 permit @@ -344,7 +331,6 @@ 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 @@ -376,6 +362,7 @@ 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 @@ -405,31 +392,11 @@ 64.207.219.143 permit 64.233.160.0/19 permit 65.52.80.137 permit -65.54.51.64/26 permit -65.54.61.64/26 permit -65.54.121.120/29 permit -65.54.190.0/24 permit -65.54.241.0/24 permit 65.55.29.77 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.78.128/25 permit -65.55.81.48/28 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.174.0/25 permit -65.55.178.128/27 permit -65.55.234.192/26 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 @@ -480,7 +447,11 @@ 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 @@ -545,8 +516,9 @@ 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.37.151.128/25 permit 70.42.149.35 permit 72.3.185.0/24 permit 72.14.192.0/18 permit @@ -643,7 +615,6 @@ 74.208.4.220 permit 74.208.4.221 permit 74.209.250.0/24 permit -75.2.70.75 permit 76.223.128.0/19 permit 76.223.176.0/20 permit 77.238.176.0/24 permit @@ -668,6 +639,7 @@ 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 @@ -686,6 +658,11 @@ 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 @@ -734,11 +711,11 @@ 87.248.117.205 permit 87.253.232.0/21 permit 89.22.108.0/24 permit -91.198.2.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 -94.245.112.0/27 permit -94.245.112.10/31 permit 95.131.104.0/21 permit 95.217.114.154 permit 96.43.144.0/20 permit @@ -1231,19 +1208,14 @@ 98.139.245.208/30 permit 98.139.245.212/31 permit 99.78.197.208/28 permit -99.83.190.102 permit 103.9.96.0/22 permit 103.28.42.0/24 permit -103.122.78.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.47.20.0/23 permit -104.47.75.0/24 permit -104.47.108.0/23 permit 104.130.96.0/28 permit 104.130.122.0/23 permit 106.10.144.64/27 permit @@ -1378,15 +1350,9 @@ 108.174.6.215 permit 108.175.18.45 permit 108.175.30.45 permit -108.177.96.0/20 permit 108.179.144.0/20 permit 109.224.244.0/24 permit 109.237.142.0/24 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 112.19.199.64/29 permit 112.19.242.64/29 permit 116.214.12.47 permit @@ -1404,9 +1370,6 @@ 117.120.16.0/21 permit 119.42.242.52/31 permit 119.42.242.156 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 @@ -1434,6 +1397,7 @@ 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 @@ -1442,6 +1406,20 @@ 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 @@ -1450,19 +1428,23 @@ 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 @@ -1470,21 +1452,8 @@ 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 @@ -1492,6 +1461,10 @@ 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 @@ -1499,9 +1472,11 @@ 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 @@ -1536,6 +1511,9 @@ 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 @@ -1543,39 +1521,38 @@ 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.183 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.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.225.0/25 permit -157.56.24.0/25 permit 157.56.120.128/26 permit -157.56.232.0/21 permit -157.56.240.0/20 permit -157.56.248.0/21 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 @@ -1597,8 +1574,11 @@ 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 @@ -1606,6 +1586,7 @@ 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 @@ -1616,12 +1597,20 @@ 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 @@ -1640,9 +1629,12 @@ 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.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 @@ -1651,28 +1643,12 @@ 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.144.0/25 permit -169.148.144.10 permit -169.148.146.0/23 permit -169.148.175.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 -172.217.0.0/20 permit -172.217.32.0/20 permit -172.217.128.0/19 permit -172.217.160.0/20 permit -172.217.192.0/19 permit -172.253.56.0/21 permit -172.253.112.0/20 permit 173.0.84.0/29 permit 173.0.84.224/27 permit 173.0.94.244/30 permit @@ -1681,6 +1657,7 @@ 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 @@ -1700,8 +1677,7 @@ 182.50.78.64/28 permit 183.240.219.64/29 permit 185.4.120.0/22 permit -185.11.253.128/27 permit -185.11.255.0/24 permit +185.11.255.144 permit 185.12.80.0/22 permit 185.28.196.0/22 permit 185.58.84.93 permit @@ -1715,8 +1691,16 @@ 185.138.56.128/25 permit 185.189.236.0/22 permit 185.211.120.0/22 permit -185.233.188.0/23 permit -185.233.190.0/23 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 @@ -1775,8 +1759,24 @@ 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 @@ -1784,15 +1784,12 @@ 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 193.109.254.0/23 permit 193.122.128.100 permit 193.123.56.63 permit -193.142.157.0/24 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 @@ -1816,6 +1813,7 @@ 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 @@ -1829,6 +1827,7 @@ 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 @@ -1850,16 +1849,7 @@ 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 @@ -1912,12 +1902,9 @@ 204.14.232.64/28 permit 204.14.234.64/28 permit 204.75.142.0/24 permit -204.79.197.212 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 @@ -1940,24 +1927,13 @@ 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.50.72 permit 207.46.50.82 permit -207.46.50.192/26 permit -207.46.50.224 permit 207.46.52.71 permit 207.46.52.79 permit -207.46.58.128/25 permit -207.46.116.128/29 permit -207.46.117.0/24 permit -207.46.132.128/27 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.97.204.96/29 permit 207.126.144.0/20 permit 207.171.160.0/19 permit @@ -1965,6 +1941,7 @@ 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 @@ -1972,6 +1949,8 @@ 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 @@ -1998,6 +1977,7 @@ 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 @@ -2106,14 +2086,10 @@ 212.227.126.225 permit 212.227.126.226 permit 212.227.126.227 permit -213.95.19.64/27 permit -213.95.135.4 permit 213.199.128.139 permit 213.199.128.145 permit 213.199.138.181 permit 213.199.138.191 permit -213.199.161.128/27 permit -213.199.177.0/26 permit 216.17.150.242 permit 216.17.150.251 permit 216.24.224.0/20 permit @@ -2141,7 +2117,6 @@ 216.39.62.60/31 permit 216.39.62.136/29 permit 216.39.62.144/31 permit -216.58.192.0/19 permit 216.66.217.240/29 permit 216.71.138.33 permit 216.71.152.207 permit @@ -2163,6 +2138,7 @@ 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 @@ -2183,6 +2159,7 @@ 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 @@ -2200,26 +2177,25 @@ 2001:748:400:3301::3 permit 2001:748:400:3301::4 permit 2404:6800:4000::/36 permit -2603:1010:3:3::5b permit -2603:1020:201:10::10f permit -2603:1030:20e:3::23c permit -2603:1030:b:3::152 permit -2603:1030:c02:8::14 permit +2404:6800:4864::/56 permit +2603:1061:14:1c2::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 -2620:109:c003:104::215 permit +2607:f8b0:4864::/56 permit 2620:109:c003:104::/64 permit -2620:109:c006:104::215 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::215 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/rspamd/dynmaps/aliasexp.php b/data/conf/rspamd/dynmaps/aliasexp.php index 824037cf1..814c0d38c 100644 --- a/data/conf/rspamd/dynmaps/aliasexp.php +++ b/data/conf/rspamd/dynmaps/aliasexp.php @@ -133,7 +133,7 @@ try { 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` AND '1'"); + $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) { diff --git a/data/conf/rspamd/local.d/metadata_exporter.conf b/data/conf/rspamd/local.d/metadata_exporter.conf index daaa79b4e..cc85faacd 100644 --- a/data/conf/rspamd/local.d/metadata_exporter.conf +++ b/data/conf/rspamd/local.d/metadata_exporter.conf @@ -3,8 +3,7 @@ rules { backend = "http"; url = "http://nginx:9081/pipe.php"; selector = "reject_no_global_bl"; - formatter = "default"; - meta_headers = true; + formatter = "multipart"; } RLINFO { backend = "http"; @@ -16,8 +15,7 @@ rules { backend = "http"; url = "http://nginx:9081/pushover.php"; selector = "mailcow_rcpt"; - formatter = "json"; - meta_headers = true; + formatter = "multipart"; } } diff --git a/data/conf/rspamd/lua/rspamd.local.lua b/data/conf/rspamd/lua/rspamd.local.lua index 9d0a58bcf..313ba7e90 100644 --- a/data/conf/rspamd/lua/rspamd.local.lua +++ b/data/conf/rspamd/lua/rspamd.local.lua @@ -146,8 +146,171 @@ rspamd_config:register_symbol({ return false end + -- Helper function to parse IPv6 into 8 segments + local function ipv6_to_segments(ip_str) + -- Remove zone identifier if present (e.g., %eth0) + ip_str = ip_str:gsub("%%.*$", "") + + local segments = {} + + -- Handle :: compression + if ip_str:find('::') then + local before, after = ip_str:match('^(.*)::(.*)$') + before = before or '' + after = after or '' + + local before_parts = {} + local after_parts = {} + + if before ~= '' then + for seg in before:gmatch('[^:]+') do + table.insert(before_parts, tonumber(seg, 16) or 0) + end + end + + if after ~= '' then + for seg in after:gmatch('[^:]+') do + table.insert(after_parts, tonumber(seg, 16) or 0) + end + end + + -- Add before segments + for _, seg in ipairs(before_parts) do + table.insert(segments, seg) + end + + -- Add compressed zeros + local zeros_needed = 8 - #before_parts - #after_parts + for i = 1, zeros_needed do + table.insert(segments, 0) + end + + -- Add after segments + for _, seg in ipairs(after_parts) do + table.insert(segments, seg) + end + else + -- No compression + for seg in ip_str:gmatch('[^:]+') do + table.insert(segments, tonumber(seg, 16) or 0) + end + end + + -- Ensure we have exactly 8 segments + while #segments < 8 do + table.insert(segments, 0) + end + + return segments + end + + -- Generate all common IPv6 notations + local function get_ipv6_variants(ip_str) + local variants = {} + local seen = {} + + local function add_variant(v) + if v and not seen[v] then + table.insert(variants, v) + seen[v] = true + end + end + + -- For IPv4, just return the original + if not ip_str:find(':') then + add_variant(ip_str) + return variants + end + + local segments = ipv6_to_segments(ip_str) + + -- 1. Fully expanded form (all zeros shown as 0000) + local expanded_parts = {} + for _, seg in ipairs(segments) do + table.insert(expanded_parts, string.format('%04x', seg)) + end + add_variant(table.concat(expanded_parts, ':')) + + -- 2. Standard form (no leading zeros, but all segments present) + local standard_parts = {} + for _, seg in ipairs(segments) do + table.insert(standard_parts, string.format('%x', seg)) + end + add_variant(table.concat(standard_parts, ':')) + + -- 3. Find all possible :: compressions + -- RFC 5952: compress the longest run of consecutive zeros + -- But we need to check all possibilities since Redis might have any form + + -- Find all zero runs + local zero_runs = {} + local in_run = false + local run_start = 0 + local run_length = 0 + + for i = 1, 8 do + if segments[i] == 0 then + if not in_run then + in_run = true + run_start = i + run_length = 1 + else + run_length = run_length + 1 + end + else + if in_run then + if run_length >= 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 @@ -155,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) @@ -166,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") + 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 @@ -210,6 +384,7 @@ rspamd_config:register_symbol({ 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" @@ -217,9 +392,7 @@ rspamd_config:register_symbol({ 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 mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN") local function remove_moo_tag() local moo_tag_header = task:get_header('X-Moo-Tag', false) @@ -231,101 +404,147 @@ rspamd_config:register_symbol({ return true 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) + -- 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 - if action ~= 'no action' and action ~= 'greylist' then - rspamd_logger.infox("skipping tag handler for action: %s", action) - remove_moo_tag() - return true + 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 + + local action = task:get_metric_action('default') + rspamd_logger.infox("TAG_MOO: metric action: %s", action) + + -- 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 - local function http_callback(err_message, code, body, headers) - if body ~= nil and body ~= "" then - rspamd_logger.infox(rspamd_config, "expanding rcpt to \"%s\"", body) + -- 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 function tag_callback_subject(err, data) - if err or type(data) ~= 'string' then - rspamd_logger.infox(rspamd_config, "subject tag handler rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying subfolder tag handler...", body, data, err) + rspamd_logger.infox("TAG_MOO: processing allowed") - local function tag_callback_subfolder(err, data) - if err or type(data) ~= 'string' then - rspamd_logger.infox(rspamd_config, "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("Add X-Moo-Tag header") - task:set_milter_reply({ - add_headers = {['X-Moo-Tag'] = 'YES'} - }) - end - end + 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 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, "cannot make request to load tag handler for rcpt") + 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 - - else - 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, - ['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, "cannot make request to load tag handler for rcpt") - remove_moo_tag() - end - - end - end - - if rcpts and #rcpts == 1 then - for _,rcpt in ipairs(rcpts) do - local rcpt_split = rspamd_str_split(rcpt['addr'], '@') - if #rcpt_split == 2 then - if rcpt_split[1] == 'postmaster' then - rspamd_logger.infox(rspamd_config, "not expanding postmaster alias") + 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() - else - rspamd_http.request({ - task=task, - url='http://nginx:8081/aliasexp.php', - body='', - callback=http_callback, - headers={Rcpt=rcpt['addr']}, - }) 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}, + }) end else + rspamd_logger.infox("TAG_MOO: invalid rcpt format") remove_moo_tag() end end, @@ -335,6 +554,7 @@ rspamd_config:register_symbol({ 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" @@ -363,11 +583,13 @@ rspamd_config:register_symbol({ 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 = task:get_from(stp)[1].addr, + from = from_addr, recipients = bcc_dest, helo = 'bcc', timeout = 20, @@ -397,27 +619,41 @@ rspamd_config:register_symbol({ end local action = task:get_metric_action('default') - rspamd_logger.infox("metric action now: %s", action) + 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 - if action == 'no action' or action == 'add header' or action == 'rewrite subject' then - send_mail(task, body) - end + 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 - if action == 'no action' or action == 'add header' or action == 'rewrite subject' then - send_mail(task, body) - end + 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, "checking bcc for rcpt address %s", e) + rspamd_logger.infox(rspamd_config, "BCC: checking bcc for rcpt address %s", e) rspamd_http.request({ task=task, url='http://nginx:8081/bcc.php', @@ -430,7 +666,7 @@ rspamd_config:register_symbol({ if from_table then for _,e in ipairs(from_table) do - rspamd_logger.infox(rspamd_config, "checking bcc for from address %s", e) + rspamd_logger.infox(rspamd_config, "BCC: checking bcc for from address %s", e) rspamd_http.request({ task=task, url='http://nginx:8081/bcc.php', @@ -441,7 +677,7 @@ rspamd_config:register_symbol({ end end - return true + -- Don't return true to avoid symbol being logged end, priority = 20 }) diff --git a/data/conf/rspamd/meta_exporter/pipe.php b/data/conf/rspamd/meta_exporter/pipe.php index bd5d875bd..0a9ed59e2 100644 --- a/data/conf/rspamd/meta_exporter/pipe.php +++ b/data/conf/rspamd/meta_exporter/pipe.php @@ -32,47 +32,42 @@ 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; } -$raw_data_content = file_get_contents('php://input'); +$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($_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']; -$fuzzy = $headers['X-Rspamd-Fuzzy']; -$subject = iconv_mime_decode($headers['X-Rspamd-Subject']); -$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']; - -$raw_size = (int)$_SERVER['CONTENT_LENGTH']; +$qid = $meta['qid'] ?? 'unknown'; +$subject = iconv_mime_decode($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()); if (empty($sender)) { error_log("QUARANTINE: Unknown sender, assuming empty-env-from@localhost" . PHP_EOL); $sender = 'empty-env-from@localhost'; } -if ($fuzzy == 'unknown') { - $fuzzy = '[]'; -} - try { $max_size = (int)$redis->Get('Q_MAX_SIZE'); if (($max_size * 1048576) < $raw_size) { @@ -94,7 +89,7 @@ catch (RedisException $e) { $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); @@ -182,7 +177,7 @@ foreach (json_decode($rcpts, true) as $rcpt) { 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` AND '1'"); + $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) { diff --git a/data/conf/rspamd/meta_exporter/pushover.php b/data/conf/rspamd/meta_exporter/pushover.php index af1b21ebc..a2cd0995b 100644 --- a/data/conf/rspamd/meta_exporter/pushover.php +++ b/data/conf/rspamd/meta_exporter/pushover.php @@ -32,50 +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): 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; } -$headers = getallheaders(); -$json_body = json_decode(file_get_contents('php://input')); +$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 = $headers['X-Rspamd-Qid']; -$rcpts = $headers['X-Rspamd-Rcpt']; -$sender = $headers['X-Rspamd-From']; -$ip = $headers['X-Rspamd-Ip']; -$subject = iconv_mime_decode($headers['X-Rspamd-Subject']); -$messageid= $json_body->message_id; +$qid = $meta['qid'] ?? 'unknown'; +$rcpts = $meta['rcpt'] ?? array(); +$sender = $meta['from'] ?? ''; +$ip = $meta['ip'] ?? 'unknown'; +$subject = iconv_mime_decode($meta['subject'] ?? ''); +$messageid= $meta['message_id'] ?? ''; $priority = 0; -$symbols_array = json_decode($headers['X-Rspamd-Symbols'], true); +$symbols_array = $meta['symbols'] ?? array(); if (is_array($symbols_array)) { foreach ($symbols_array as $symbol) { - if ($symbol['name'] == 'HAS_X_PRIO_ONE') { + if (($symbol['name'] ?? null) == 'HAS_X_PRIO_ONE') { $priority = 1; break; } } } -$sender_address = $json_body->header_from[0]; +$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 = $json_body->header_to[0]; +$to_address = $meta['header_to'][0] ?? ''; $to_name = '-'; if (preg_match('/(?.*?)<(?
.*?)>/i', $to_address, $matches)) { $to_address = $matches['address']; @@ -85,7 +81,7 @@ if (preg_match('/(?.*?)<(?
.*?)>/i', $to_address, $matches)) { $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); @@ -167,7 +163,7 @@ foreach (json_decode($rcpts, true) as $rcpt) { 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` AND '1'"); + $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) { diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index 2c8d80a12..6854569f5 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -86,6 +86,12 @@ 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; diff --git a/data/web/admin/dashboard.php b/data/web/admin/dashboard.php index 443c2ea2a..2867770bc 100644 --- a/data/web/admin/dashboard.php +++ b/data/web/admin/dashboard.php @@ -2,18 +2,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php'; -if (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(); -} -elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") { - header('Location: /admin'); - exit(); -} +protect_route(['admin']); require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; diff --git a/data/web/admin/index.php b/data/web/admin/index.php index 9ae4a0380..9e0bf1bd7 100644 --- a/data/web/admin/index.php +++ b/data/web/admin/index.php @@ -3,8 +3,11 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php'; if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') { - header('Location: /admin/dashboard'); - exit(); + // Only redirect to dashboard if NO pending actions + if (empty($_SESSION['pending_tfa_setup']) && empty($_SESSION['pending_pw_update'])) { + header('Location: /admin/dashboard'); + exit(); + } } elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') { header('Location: /domainadmin/mailbox'); diff --git a/data/web/admin/mailbox.php b/data/web/admin/mailbox.php index d0073bbd6..7b07d6fa9 100644 --- a/data/web/admin/mailbox.php +++ b/data/web/admin/mailbox.php @@ -2,18 +2,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php'; -if (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(); -} -elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") { - header('Location: /admin'); - exit(); -} +protect_route(['admin']); require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; diff --git a/data/web/admin/queue.php b/data/web/admin/queue.php index 85ec59401..66682c7ae 100644 --- a/data/web/admin/queue.php +++ b/data/web/admin/queue.php @@ -2,19 +2,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php'; -if (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(); -} -elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") { - header('Location: /admin'); - exit(); -} - +protect_route(['admin']); require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; $js_minifier->add('/web/js/site/queue.js'); diff --git a/data/web/admin/system.php b/data/web/admin/system.php index 9fd44e0d8..4db40c753 100644 --- a/data/web/admin/system.php +++ b/data/web/admin/system.php @@ -2,18 +2,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php'; -if (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(); -} -elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") { - header('Location: /admin'); - exit(); -} +protect_route(['admin']); require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; diff --git a/data/web/api/openapi.yaml b/data/web/api/openapi.yaml index f207ee6a1..77c58b609 100644 --- a/data/web/api/openapi.yaml +++ b/data/web/api/openapi.yaml @@ -1072,6 +1072,7 @@ paths: password2: "*" quota: "3072" force_pw_update: "1" + force_tfa: "1" tls_enforce_in: "1" tls_enforce_out: "1" tags: ["tag1", "tag2"] @@ -1118,6 +1119,7 @@ paths: password2: atedismonsin quota: "3072" force_pw_update: "1" + force_tfa: "1" tls_enforce_in: "1" tls_enforce_out: "1" tags: ["tag1", "tag2"] @@ -1151,6 +1153,9 @@ paths: 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 @@ -2454,6 +2459,90 @@ paths: 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: @@ -3330,6 +3419,7 @@ paths: - mailbox - active: "1" force_pw_update: "0" + force_tfa: "0" name: Full name password: "*" password2: "*" @@ -3380,6 +3470,7 @@ paths: attr: active: "1" force_pw_update: "0" + force_tfa: "0" name: Full name authsource: mailcow password: "" @@ -3403,6 +3494,9 @@ paths: 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 @@ -4797,6 +4891,7 @@ paths: - active: "1" attributes: force_pw_update: "0" + force_tfa: "0" mailbox_format: "maildir:" quarantine_notification: never sogo_access: "1" @@ -5721,6 +5816,7 @@ paths: - active: "1" attributes: force_pw_update: "0" + force_tfa: "0" mailbox_format: "maildir:" quarantine_notification: never sogo_access: "1" diff --git a/data/web/autoconfig.php b/data/web/autoconfig.php index 6a528d4a2..bb4a55cb6 100644 --- a/data/web/autoconfig.php +++ b/data/web/autoconfig.php @@ -29,8 +29,8 @@ header('Content-Type: application/xml'); %EMAILDOMAIN% - A mailcow mail server - mail server + + diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php index 9526313ae..3e31dc3dd 100644 --- a/data/web/autodiscover.php +++ b/data/web/autodiscover.php @@ -7,6 +7,8 @@ if(file_exists('inc/vars.local.inc.php')) { 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'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php'; $default_autodiscover_config = $autodiscover_config; $autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config); @@ -58,101 +60,31 @@ $pdo = new PDO($dsn, $database_user, $database_pass, $opt); $iam_provider = identity_provider('init'); $iam_settings = identity_provider('get'); -$login_user = strtolower(trim($_SERVER['PHP_AUTH_USER'])); -$login_pass = trim(htmlspecialchars_decode($_SERVER['PHP_AUTH_PW'])); +// Passwordless autodiscover - no authentication required +// Email will be extracted from the request body +$login_user = null; +$login_role = null; -if (empty($_SERVER['PHP_AUTH_USER']) || empty($_SERVER['PHP_AUTH_PW'])) { - $json = json_encode( - array( - "time" => time(), - "ua" => $_SERVER['HTTP_USER_AGENT'], - "user" => "none", - "ip" => $_SERVER['REMOTE_ADDR'], - "service" => "Error: must be authenticated" - ) - ); - $redis->lPush('AUTODISCOVER_LOG', $json); - header('WWW-Authenticate: Basic realm="' . $_SERVER['HTTP_HOST'] . '"'); - header('HTTP/1.0 401 Unauthorized'); - exit(0); -} - -$login_role = check_login($login_user, $login_pass, array('eas' => TRUE)); - -if ($login_role === "user") { - header("Content-Type: application/xml"); - echo '' . PHP_EOL; +header("Content-Type: application/xml"); +echo '' . PHP_EOL; ?> time(), - "ua" => $_SERVER['HTTP_USER_AGENT'], - "user" => $_SERVER['PHP_AUTH_USER'], - "ip" => $_SERVER['REMOTE_ADDR'], - "service" => "Error: invalid or missing request data" - ) - ); - $redis->lPush('AUTODISCOVER_LOG', $json); - $redis->lTrim('AUTODISCOVER_LOG', 0, 100); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'msg' => 'Redis: '.$e - ); - return false; - } - list($usec, $sec) = explode(' ', microtime()); -?> - - - 600 - Invalid Request - - - - -Request->EMailAddress; - } catch (Exception $e) { - $email = $_SERVER['PHP_AUTH_USER']; - } - - $username = trim($email); - try { - $stmt = $pdo->prepare("SELECT `name` FROM `mailbox` WHERE `username`= :username"); - $stmt->execute(array(':username' => $username)); - $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); - } - catch(PDOException $e) { - die("Failed to determine name from SQL"); - } - if (!empty($MailboxData['name'])) { - $displayname = $MailboxData['name']; - } - else { - $displayname = $email; - } +if(!$data) { try { $json = json_encode( array( "time" => time(), "ua" => $_SERVER['HTTP_USER_AGENT'], - "user" => $_SERVER['PHP_AUTH_USER'], + "user" => "none", "ip" => $_SERVER['REMOTE_ADDR'], - "service" => $autodiscover_config['autodiscoverType'] + "service" => "Error: invalid or missing request data" ) ); $redis->lPush('AUTODISCOVER_LOG', $json); $redis->lTrim('AUTODISCOVER_LOG', 0, 100); + $redis->publish("F2B_CHANNEL", "Autodiscover: Invalid request by " . $_SERVER['REMOTE_ADDR']); + error_log("Autodiscover: Invalid request by " . $_SERVER['REMOTE_ADDR']); } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -161,7 +93,143 @@ if ($login_role === "user") { ); return false; } - if ($autodiscover_config['autodiscoverType'] == 'imap') { + list($usec, $sec) = explode(' ', microtime()); +?> + + + 600 + Invalid Request + + + + +Request->EMailAddress; +} catch (Exception $e) { + // If parsing fails, return error + try { + $json = json_encode( + array( + "time" => time(), + "ua" => $_SERVER['HTTP_USER_AGENT'], + "user" => "none", + "ip" => $_SERVER['REMOTE_ADDR'], + "service" => "Error: could not parse email from request" + ) + ); + $redis->lPush('AUTODISCOVER_LOG', $json); + $redis->lTrim('AUTODISCOVER_LOG', 0, 100); + $redis->publish("F2B_CHANNEL", "Autodiscover: Malformed XML by " . $_SERVER['REMOTE_ADDR']); + error_log("Autodiscover: Malformed XML by " . $_SERVER['REMOTE_ADDR']); + } + catch (RedisException $e) { + // Silently fail + } + list($usec, $sec) = explode(' ', microtime()); +?> + + + 600 + Invalid Request + + + + +prepare("SELECT `mailbox`.`name`, `mailbox`.`active` FROM `mailbox` + INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain` + WHERE `mailbox`.`username` = :username + AND `mailbox`.`active` = '1' + AND `domain`.`active` = '1'"); + $stmt->execute(array(':username' => $username)); + $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); +} +catch(PDOException $e) { + // Database error - return error response with complete XML + list($usec, $sec) = explode(' ', microtime()); +?> + + + 500 + Database Error + + + + + time(), + "ua" => $_SERVER['HTTP_USER_AGENT'], + "user" => $email, + "ip" => $_SERVER['REMOTE_ADDR'], + "service" => "Error: mailbox not found or inactive" + ) + ); + $redis->lPush('AUTODISCOVER_LOG', $json); + $redis->lTrim('AUTODISCOVER_LOG', 0, 100); + $redis->publish("F2B_CHANNEL", "Autodiscover: Invalid mailbox attempt by " . $_SERVER['REMOTE_ADDR']); + error_log("Autodiscover: Invalid mailbox attempt by " . $_SERVER['REMOTE_ADDR']); + } + catch (RedisException $e) { + // Silently fail + } + list($usec, $sec) = explode(' ', microtime()); +?> + + + 600 + Invalid Request + + + + + time(), + "ua" => $_SERVER['HTTP_USER_AGENT'], + "user" => $email, + "ip" => $_SERVER['REMOTE_ADDR'], + "service" => $autodiscover_config['autodiscoverType'] + ) + ); + $redis->lPush('AUTODISCOVER_LOG', $json); + $redis->lTrim('AUTODISCOVER_LOG', 0, 100); +} +catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'msg' => 'Redis: '.$e + ); + return false; +} +if ($autodiscover_config['autodiscoverType'] == 'imap') { ?> @@ -236,6 +304,3 @@ if ($login_role === "user") { } ?> - diff --git a/data/web/domainadmin/index.php b/data/web/domainadmin/index.php index 0d70ec3ae..07c62a7d2 100644 --- a/data/web/domainadmin/index.php +++ b/data/web/domainadmin/index.php @@ -3,8 +3,11 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php'; if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') { - header('Location: /domainadmin/mailbox'); - exit(); + // Only redirect to mailbox if NO pending actions + if (empty($_SESSION['pending_tfa_setup']) && empty($_SESSION['pending_pw_update'])) { + header('Location: /domainadmin/mailbox'); + exit(); + } } elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') { header('Location: /admin/dashboard'); diff --git a/data/web/domainadmin/mailbox.php b/data/web/domainadmin/mailbox.php index bb2ef16f3..8beb5abb9 100644 --- a/data/web/domainadmin/mailbox.php +++ b/data/web/domainadmin/mailbox.php @@ -2,18 +2,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php'; -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'] == 'user') { - header('Location: /user'); - exit(); -} -elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "domainadmin") { - header('Location: /domainadmin'); - exit(); -} +protect_route(['domainadmin']); require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; diff --git a/data/web/domainadmin/user.php b/data/web/domainadmin/user.php index 7f1b392e0..a45e00dfc 100644 --- a/data/web/domainadmin/user.php +++ b/data/web/domainadmin/user.php @@ -2,41 +2,28 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php'; -if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') { +/* +/ DOMAIN ADMIN +*/ - /* - / DOMAIN ADMIN - */ +protect_route(['domainadmin']); - require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; - $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; - $tfa_data = get_tfa(); - $fido2_data = fido2(array("action" => "get_friendly_names")); - $username = $_SESSION['mailcow_cc_username']; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; +$_SESSION['return_to'] = $_SERVER['REQUEST_URI']; +$tfa_data = get_tfa(); +$fido2_data = fido2(array("action" => "get_friendly_names")); +$username = $_SESSION['mailcow_cc_username']; - $template = 'domainadmin.twig'; - $template_data = [ - 'acl' => $_SESSION['acl'], - 'acl_json' => json_encode($_SESSION['acl']), - 'user_spam_score' => mailbox('get', 'spam_score', $username), - 'tfa_data' => $tfa_data, - 'fido2_data' => $fido2_data, - 'lang_user' => json_encode($lang['user']), - 'lang_datatables' => json_encode($lang['datatables']), - ]; -} -elseif (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'] == 'user') { - header('Location: /user'); - exit(); -} -else { - header('Location: /domainadmin'); - exit(); -} +$template = 'domainadmin.twig'; +$template_data = [ + 'acl' => $_SESSION['acl'], + 'acl_json' => json_encode($_SESSION['acl']), + 'user_spam_score' => mailbox('get', 'spam_score', $username), + 'tfa_data' => $tfa_data, + 'fido2_data' => $fido2_data, + 'lang_user' => json_encode($lang['user']), + 'lang_datatables' => json_encode($lang['datatables']), +]; $js_minifier->add('/web/js/site/user.js'); $js_minifier->add('/web/js/site/pwgen.js'); diff --git a/data/web/edit.php b/data/web/edit.php index 57cf24bd2..48f2309c1 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -1,10 +1,8 @@ 0 && $mta_sts['active'] == 1) { if (!in_array($domain, $alias_domains)) { $records[] = array( diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index ecc4ddce1..4ced152ae 100644 --- a/data/web/inc/footer.inc.php +++ b/data/web/inc/footer.inc.php @@ -64,6 +64,8 @@ $globalVariables = [ 'pending_tfa_methods' => @$_SESSION['pending_tfa_methods'], 'pending_tfa_authmechs' => $pending_tfa_authmechs, 'pending_mailcow_cc_username' => @$_SESSION['pending_mailcow_cc_username'], + 'pending_tfa_setup' => !empty($_SESSION['pending_tfa_setup']), + 'pending_pw_update_modal' => !empty($_SESSION['pending_pw_update']), 'lang_footer' => json_encode($lang['footer']), 'lang_acl' => json_encode($lang['acl']), 'lang_tfa' => json_encode($lang['tfa']), diff --git a/data/web/inc/functions.admin.inc.php b/data/web/inc/functions.admin.inc.php index 9f42fd721..7bd0af42f 100644 --- a/data/web/inc/functions.admin.inc.php +++ b/data/web/inc/functions.admin.inc.php @@ -121,34 +121,56 @@ function admin($_action, $_data = null) { continue; } } - if (!empty($password)) { - if (password_check($password, $password2) !== true) { - return false; + // 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']); } - $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)); @@ -223,7 +245,8 @@ function admin($_action, $_data = null) { `tfa`.`active` AS `tfa_active`, `admin`.`username`, `admin`.`created`, - `admin`.`active` 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'"); @@ -240,6 +263,7 @@ function admin($_action, $_data = null) { $admindata['active'] = $row['active']; $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 index b493fc914..aacbb82df 100644 --- a/data/web/inc/functions.app_passwd.inc.php +++ b/data/web/inc/functions.app_passwd.inc.php @@ -1,7 +1,7 @@ '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' - ); + if (password_check($password, $password2) !== true) { return false; } $password_hashed = hash_password($password); @@ -88,15 +75,15 @@ function app_passwd($_action, $_data = null) { 'log' => array(__FUNCTION__, $_action, $_data_log), 'msg' => 'app_passwd_added' ); - break; + 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['password'])) ? $_data['password'] : null; - $password2 = (!empty($_data['password2'])) ? $_data['password2'] : null; + $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; @@ -126,20 +113,7 @@ function app_passwd($_action, $_data = null) { } $app_name = htmlspecialchars(trim($app_name)); 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' - ); - continue; - } - if ($password != $password2) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'password_mismatch' - ); + if (password_check($password, $password2) !== true) { continue; } $password_hashed = hash_password($password); @@ -182,7 +156,7 @@ function app_passwd($_action, $_data = null) { 'msg' => array('object_modified', htmlspecialchars(implode(', ', $ids))) ); } - break; + break; case 'delete': $ids = (array)$_data['id']; foreach ($ids as $id) { @@ -213,19 +187,17 @@ function app_passwd($_action, $_data = null) { 'msg' => array('app_passwd_removed', htmlspecialchars($id)) ); } - break; + 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; + break; case 'details': $app_passwd_data = array(); - $stmt = $pdo->prepare("SELECT * - FROM `app_passwd` - WHERE `id` = :id"); + $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)) { @@ -237,6 +209,6 @@ function app_passwd($_action, $_data = null) { } $app_passwd_data['name'] = htmlspecialchars(trim($app_passwd_data['name'])); return $app_passwd_data; - break; + break; } } diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php index 059dd4cd9..e5a303f9b 100644 --- a/data/web/inc/functions.auth.inc.php +++ b/data/web/inc/functions.auth.inc.php @@ -1,10 +1,11 @@ prepare("SELECT `password` FROM `admin` + $stmt = $pdo->prepare("SELECT `password`, `attributes` FROM `admin` WHERE `superadmin` = '1' AND `active` = '1' AND `username` = :user"); @@ -104,6 +91,13 @@ function admin_login($user, $pass){ // 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) { @@ -123,6 +117,10 @@ function admin_login($user, $pass){ // 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, '*'), @@ -148,7 +146,7 @@ function domainadmin_login($user, $pass){ return false; } - $stmt = $pdo->prepare("SELECT `password` FROM `admin` + $stmt = $pdo->prepare("SELECT `password`, `attributes` FROM `admin` WHERE `superadmin` = '0' AND `active`='1' AND `username` = :user"); @@ -157,6 +155,13 @@ function domainadmin_login($user, $pass){ // 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) { @@ -176,6 +181,10 @@ function domainadmin_login($user, $pass){ // 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, '*'), @@ -193,7 +202,7 @@ function user_login($user, $pass, $extra = null){ global $iam_settings; $is_internal = $extra['is_internal']; - $service = $extra['service']; + $extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service']; if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) { if (!$is_internal){ @@ -236,10 +245,10 @@ function user_login($user, $pass, $extra = null){ $row = $stmt->fetch(PDO::FETCH_ASSOC); if (!empty($row)) { - // check if user has access to service (imap, smtp, pop3, sieve) if service is set + // 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 (isset($service)) { - $key = strtolower($service) . "_access"; + if ($extra['service'] != 'NONE') { + $key = strtolower($extra['service']) . "_access"; if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') { return false; } @@ -253,8 +262,8 @@ function user_login($user, $pass, $extra = null){ // check if user has access to service (imap, smtp, pop3, sieve) if service is set $row['attributes'] = json_decode($row['attributes'], true); - if (isset($service)) { - $key = strtolower($service) . "_access"; + if ($extra['service'] != 'NONE') { + $key = strtolower($extra['service']) . "_access"; if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') { return false; } @@ -278,6 +287,8 @@ function user_login($user, $pass, $extra = null){ 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) { @@ -299,6 +310,10 @@ function user_login($user, $pass, $extra = null){ // 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'), @@ -330,6 +345,8 @@ function user_login($user, $pass, $extra = null){ 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) { @@ -351,6 +368,10 @@ function user_login($user, $pass, $extra = null){ // 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'), @@ -394,6 +415,10 @@ function user_login($user, $pass, $extra = null){ // 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'), @@ -408,7 +433,7 @@ function user_login($user, $pass, $extra = null){ return false; } -function apppass_login($user, $pass, $app_passwd_data, $extra = null){ +function apppass_login($user, $pass, $extra = null){ global $pdo; $is_internal = $extra['is_internal']; @@ -424,20 +449,8 @@ function apppass_login($user, $pass, $app_passwd_data, $extra = null){ return false; } - $protocol = false; - if ($app_passwd_data['eas']){ - $protocol = 'eas'; - } else if ($app_passwd_data['dav']){ - $protocol = 'dav'; - } else if ($app_passwd_data['smtp']){ - $protocol = 'smtp'; - } else if ($app_passwd_data['imap']){ - $protocol = 'imap'; - } else if ($app_passwd_data['sieve']){ - $protocol = 'sieve'; - } else if ($app_passwd_data['pop3']){ - $protocol = 'pop3'; - } else if (!$is_internal) { + $extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service']; + if (!$is_internal && $extra['service'] == 'NONE') { return false; } @@ -458,7 +471,7 @@ function apppass_login($user, $pass, $app_passwd_data, $extra = null){ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { - if ($protocol && $row[$protocol . '_access'] != '1'){ + if ($extra['service'] != 'NONE' && $row[strtolower($extra['service']) . '_access'] != '1'){ continue; } diff --git a/data/web/inc/functions.customize.inc.php b/data/web/inc/functions.customize.inc.php index 56527ff96..b396eec21 100644 --- a/data/web/inc/functions.customize.inc.php +++ b/data/web/inc/functions.customize.inc.php @@ -325,8 +325,10 @@ 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'; + $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'))) { diff --git a/data/web/inc/functions.docker.inc.php b/data/web/inc/functions.docker.inc.php index 5b5b7ace1..daed17c63 100644 --- a/data/web/inc/functions.docker.inc.php +++ b/data/web/inc/functions.docker.inc.php @@ -63,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); diff --git a/data/web/inc/functions.domain_admin.inc.php b/data/web/inc/functions.domain_admin.inc.php index bb88ea34c..46b651b85 100644 --- a/data/web/inc/functions.domain_admin.inc.php +++ b/data/web/inc/functions.domain_admin.inc.php @@ -195,17 +195,23 @@ function domain_admin($_action, $_data = null) { )); } } + $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 WHERE `username` = :username"); + $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 + ':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"); @@ -217,11 +223,15 @@ function domain_admin($_action, $_data = null) { } } else { - $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `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 + ':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"); @@ -244,31 +254,37 @@ function domain_admin($_action, $_data = null) { // Can only edit itself elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") { $username = $_SESSION['mailcow_cc_username']; - $password_old = $_data['user_old_pass']; + $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; + // 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 WHERE `username` = :username"); + $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), @@ -360,9 +376,11 @@ function domain_admin($_action, $_data = null) { `tfa`.`active` AS `tfa_active`, `domain_admins`.`username`, `domain_admins`.`created`, - `domain_admins`.`active` AS `active` + `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 @@ -377,6 +395,7 @@ function domain_admin($_action, $_data = null) { $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 ( diff --git a/data/web/inc/functions.fwdhost.inc.php b/data/web/inc/functions.fwdhost.inc.php index d7ac2567c..52c751591 100644 --- a/data/web/inc/functions.fwdhost.inc.php +++ b/data/web/inc/functions.fwdhost.inc.php @@ -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 { diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 55329e73a..89f14b574 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -205,6 +205,42 @@ function password_complexity($_action, $_data = null) { 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'); @@ -814,6 +850,32 @@ function verify_hash($hash, $password) { $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); @@ -971,20 +1033,24 @@ function edit_user_account($_data) { } // edit password - if (!empty($password_old) && !empty($_data['user_new_pass']) && !empty($_data['user_new_pass2'])) { - $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); + $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' => 'access_denied' - ); - return false; + if (!verify_hash($row['password'], $password_old)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } } $password_new = $_data['user_new_pass']; @@ -1006,7 +1072,7 @@ function edit_user_account($_data) { update_sogo_static_view(); } // edit password recovery email - elseif (isset($pw_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', @@ -1016,6 +1082,21 @@ function edit_user_account($_data) { 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'"); @@ -1107,11 +1188,21 @@ function user_get_alias_details($username) { } return $data; } -function is_valid_domain_name($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)); @@ -1123,50 +1214,52 @@ function set_tfa($_data) { global $iam_settings; $_data_log = $_data; - $access_denied = null; !isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*'; - $username = $_SESSION['mailcow_cc_username']; - // check for empty user and role - if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true; - - // check admin confirm 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; + // 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; - // check mailbox confirm 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 (!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; } } - } - // set access_denied error - if ($access_denied){ - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return 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; + } } switch ($_data["tfa_method"]) { @@ -1219,6 +1312,7 @@ function set_tfa($_data) { ); return false; } + unset($_SESSION['pending_tfa_setup']); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_data_log), @@ -1232,6 +1326,7 @@ function set_tfa($_data) { //$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'])); + unset($_SESSION['pending_tfa_setup']); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_data_log), @@ -1260,6 +1355,7 @@ function set_tfa($_data) { 0 )); + unset($_SESSION['pending_tfa_setup']); $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_data_log), @@ -1267,6 +1363,25 @@ function set_tfa($_data) { ); 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' => 'tfa_removal_blocked' + ); + return false; + } $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); $stmt->execute(array(':username' => $username)); $_SESSION['return'][] = array( @@ -1519,6 +1634,26 @@ function unset_tfa_key($_data) { 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' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'tfa_removal_blocked' + ); + return false; + } + // check if it's last key $stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa` WHERE `username` = :username AND `active` = '1'"); @@ -1551,6 +1686,15 @@ function unset_tfa_key($_data) { 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'])) { @@ -3353,6 +3497,49 @@ function set_user_loggedin_session($user) { 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($application, $lines = false) { if ($lines === false) { $lines = $GLOBALS['LOG_LINES'] - 1; diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index a0199fbdc..a9f17705d 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -9,6 +9,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $_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) { @@ -49,6 +53,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { // 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']; $description = $_data['description']; $valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain']; @@ -65,13 +75,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } $validity = strtotime("+" . $_data["validity"] . " hour"); - $stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `description`, `goto`, `validity`) VALUES - (:address, :description, :goto, :validity)"); + $stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `description`, `goto`, `validity`, `permanent`) VALUES + (:address, :description, :goto, :validity, :permanent)"); $stmt->execute(array( ':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', @@ -688,6 +699,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $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']); @@ -843,8 +855,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `internal`, `active`) - VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :internal, :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, @@ -855,6 +867,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':domain' => $domain, ':sogo_visible' => $sogo_visible, ':internal' => $internal, + ':sender_allowed' => $sender_allowed, ':active' => $active )); } @@ -867,6 +880,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':domain' => $domain, ':sogo_visible' => $sogo_visible, ':internal' => $internal, + ':sender_allowed' => $sender_allowed, ':active' => $active )); } @@ -876,6 +890,17 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), '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': @@ -1068,9 +1093,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $_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']); @@ -1078,17 +1106,32 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $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($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), @@ -1096,6 +1139,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { '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']), @@ -1349,15 +1394,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ), $_extra); } - try { - update_sogo_static_view($username); - } catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => $e->getMessage() - ); - } + // Track affected mailboxes for SOGo update + $update_sogo_mailboxes[] = $username; $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -1446,7 +1484,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } 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)) { + if (!is_valid_domain_name($mx_domain, array('allow_wildcard' => true))) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr), @@ -1588,6 +1626,9 @@ 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") { @@ -1701,9 +1742,19 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $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']); @@ -1714,12 +1765,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $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']; @@ -2025,6 +2080,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 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; + } if ($_data['spam_score'] == "default") { $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); @@ -2103,15 +2166,23 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - if (empty($_data['validity'])) { + if (empty($_data['validity']) && empty($_data['permanent'])) { continue; } - $validity = round((int)time() + ($_data['validity'] * 3600)); - $stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity WHERE + 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 + ':validity' => $validity, + ':permanent' => $permanent )); $_SESSION['return'][] = array( 'type' => 'success', @@ -2486,6 +2557,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (!empty($is_now)) { $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; @@ -2671,6 +2743,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `goto` = :goto, `sogo_visible`= :sogo_visible, `internal`= :internal, + `sender_allowed`= :sender_allowed, `active`= :active WHERE `id` = :id"); $stmt->execute(array( @@ -2681,6 +2754,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':goto' => $goto, ':sogo_visible' => $sogo_visible, ':internal' => $internal, + ':sender_allowed' => $sender_allowed, ':active' => $active, ':id' => $is_now['id'] )); @@ -2690,6 +2764,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': @@ -3028,15 +3124,20 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $_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)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']); + (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']; @@ -3054,6 +3155,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 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 { @@ -3170,9 +3274,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } 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 @@ -3260,16 +3365,25 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt->execute(array( ':username' => $username )); - $fixed_sender_aliases = mailbox('get', 'sender_acl_handles', $username)['fixed_sender_aliases']; + $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; } - // Don't add if allowed by alias - if (in_array($sender_acl, $fixed_sender_aliases)) { + + // 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( @@ -3314,12 +3428,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `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"); @@ -3329,11 +3446,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':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, @@ -3382,17 +3502,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'msg' => array('mailbox_modified', $username) ); - try { - update_sogo_static_view($username); - } catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => $e->getMessage() - ); - } + // Track affected mailboxes for SOGo update + $update_sogo_mailboxes[] = $username; } - return true; break; case 'mailbox_rename': $domain = $_data['domain']; @@ -3703,9 +3815,19 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $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']; @@ -3716,6 +3838,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $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){ @@ -3897,7 +4021,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 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)) { + if (!is_valid_domain_name($mx_domain, array('allow_wildcard' => true))) { $invalid_mx = $mx_domain; break; } @@ -4017,6 +4141,9 @@ 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': @@ -4145,13 +4272,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(); + $data['fixed_sender_aliases_allowed'] = array(); + $data['fixed_sender_aliases_blocked'] = array(); $data['external_sender_aliases'] = array(); - // Fixed addresses - $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` REGEXP :goto AND `address` NOT LIKE '@%'"); + // 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` @@ -4584,10 +4720,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `description`, `validity`, `created`, - `modified` + `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; @@ -4709,6 +4847,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `internal`, `active`, `sogo_visible`, + `sender_allowed`, `created`, `modified` FROM `alias` @@ -4742,6 +4881,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $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'])) { @@ -5162,7 +5302,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $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"); + $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']; @@ -5708,6 +5848,18 @@ 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' => $alias_data['id'] @@ -5966,22 +6118,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { continue; } - try { - update_sogo_static_view($username); - }catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => $e->getMessage() - ); - } $_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; } - return true; break; case 'mailbox_templates': if ($_SESSION['mailcow_cc_role'] != "admin") { @@ -6081,6 +6226,9 @@ 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': @@ -6187,9 +6335,21 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } break; } - if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'resource')) && getenv('SKIP_SOGO') != "y") { + if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'resource', 'mailbox')) && getenv('SKIP_SOGO') != "y") { try { - update_sogo_static_view(); + 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', diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php index 9ab2ad174..e5737b63c 100644 --- a/data/web/inc/header.inc.php +++ b/data/web/inc/header.inc.php @@ -62,7 +62,11 @@ if ($app_links_processed){ } } - +// 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'), @@ -85,7 +89,7 @@ $globalVariables = [ 'app_links' => $app_links, 'app_links_processed' => $app_links_processed, 'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'), - 'uri' => $_SERVER['REQUEST_URI'], + 'uri' => parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/', ]; foreach ($globalVariables as $globalVariableName => $globalVariableValue) { diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 1c4f0ebf2..72018a6bc 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -4,7 +4,7 @@ function init_db_schema() try { global $pdo; - $db_version = "19082025_1436"; + $db_version = "19022026_1220"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -76,7 +76,8 @@ function init_db_schema() "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'" + "active" => "TINYINT(1) NOT NULL DEFAULT '1'", + "attributes" => "JSON" ), "keys" => array( "primary" => array( @@ -185,6 +186,7 @@ function init_db_schema() "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( @@ -554,7 +556,8 @@ function init_db_schema() "description" => "TEXT NOT NULL", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", - "validity" => "INT(11)" + "validity" => "INT(11)", + "permanent" => "TINYINT(1) NOT NULL DEFAULT '0'" ), "keys" => array( "primary" => array( @@ -1337,6 +1340,14 @@ function init_db_schema() $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%' @@ -1380,11 +1391,18 @@ function init_db_schema() $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;"); @@ -1437,6 +1455,7 @@ function init_db_schema() "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']), diff --git a/data/web/inc/lib/composer.json b/data/web/inc/lib/composer.json index 91a50bcbf..3998065cb 100644 --- a/data/web/inc/lib/composer.json +++ b/data/web/inc/lib/composer.json @@ -11,6 +11,7 @@ "directorytree/ldaprecord": "^3.3", "twig/twig": "^3.0", "stevenmaguire/oauth2-keycloak": "^4.0", - "league/oauth2-client": "^2.7" + "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 fb1c3903b..4254deffb 100644 --- a/data/web/inc/lib/composer.lock +++ b/data/web/inc/lib/composer.lock @@ -4,8 +4,62 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8f5a147cdb147b935a158b86f47a4747", + "content-hash": "50fb4a320500820e36f30eabc45222a0", "packages": [ + { + "name": "bacon/bacon-qr-code", + "version": "2.0.8", + "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" + }, + "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", @@ -137,6 +191,56 @@ ], "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": "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" + }, + "time": "2025-09-16T12:23:56+00:00" + }, { "name": "ddeboer/imap", "version": "1.13.1", @@ -214,30 +318,32 @@ }, { "name": "directorytree/ldaprecord", - "version": "v2.20.5", + "version": "v3.8.5", "source": { "type": "git", "url": "https://github.com/DirectoryTree/LdapRecord.git", - "reference": "5bd0a5a9d257cf1049ae83055dbba4c3479ddf16" + "reference": "00e5f088f8c4028d5f398783cccc2e8119a27a65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DirectoryTree/LdapRecord/zipball/5bd0a5a9d257cf1049ae83055dbba4c3479ddf16", - "reference": "5bd0a5a9d257cf1049ae83055dbba4c3479ddf16", + "url": "https://api.github.com/repos/DirectoryTree/LdapRecord/zipball/00e5f088f8c4028d5f398783cccc2e8119a27a65", + "reference": "00e5f088f8c4028d5f398783cccc2e8119a27a65", "shasum": "" }, "require": { + "ext-iconv": "*", "ext-json": "*", "ext-ldap": "*", - "illuminate/contracts": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0", - "nesbot/carbon": "^1.0|^2.0", - "php": ">=7.3", - "psr/log": "^1.0|^2.0|^3.0", - "psr/simple-cache": "^1.0|^2.0", - "symfony/polyfill-php80": "^1.25", - "tightenco/collect": "^5.6|^6.0|^7.0|^8.0|^9.0" + "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" @@ -284,7 +390,7 @@ "type": "github" } ], - "time": "2023-10-11T16:34:34+00:00" + "time": "2025-10-06T02:22:34+00:00" }, { "name": "firebase/php-jwt", @@ -677,6 +783,107 @@ ], "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", @@ -725,6 +932,52 @@ }, "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", @@ -2452,145 +2705,6 @@ ], "time": "2023-12-26T14:02:43+00:00" }, - { - "name": "symfony/var-dumper", - "version": "v6.4.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "0435a08f69125535336177c29d56af3abc1f69da" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0435a08f69125535336177c29d56af3abc1f69da", - "reference": "0435a08f69125535336177c29d56af3abc1f69da", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^6.3|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/uid": "^5.4|^6.0|^7.0", - "twig/twig": "^2.13|^3.0.4" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "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": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/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-23T14:53:30+00:00" - }, - { - "name": "tightenco/collect", - "version": "v9.52.7", - "source": { - "type": "git", - "url": "https://github.com/tighten/collect.git", - "reference": "b15143cd11fe01a700fcc449df61adc64452fa6d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/tighten/collect/zipball/b15143cd11fe01a700fcc449df61adc64452fa6d", - "reference": "b15143cd11fe01a700fcc449df61adc64452fa6d", - "shasum": "" - }, - "require": { - "php": "^8.0", - "symfony/var-dumper": "^3.4 || ^4.0 || ^5.0 || ^6.0" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "nesbot/carbon": "^2.23.0", - "phpunit/phpunit": "^8.3" - }, - "type": "library", - "autoload": { - "files": [ - "src/Collect/Support/helpers.php", - "src/Collect/Support/alias.php" - ], - "psr-4": { - "Tightenco\\Collect\\": "src/Collect" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylorotwell@gmail.com" - } - ], - "description": "Collect - Illuminate Collections as a separate package.", - "keywords": [ - "collection", - "laravel" - ], - "support": { - "issues": "https://github.com/tighten/collect/issues", - "source": "https://github.com/tighten/collect/tree/v9.52.7" - }, - "time": "2023-04-14T21:51:36+00:00" - }, { "name": "twig/twig", "version": "v3.14.0", @@ -2674,10 +2788,10 @@ "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/vendor/bacon/bacon-qr-code/LICENSE b/data/web/inc/lib/vendor/bacon/bacon-qr-code/LICENSE new file mode 100644 index 000000000..d45a35647 --- /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 000000000..9c099fefd --- /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 000000000..7f193daf0 --- /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 000000000..d9e4d57bc --- /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 000000000..158384fe8 --- /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 000000000..10bf8fe20 --- /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 000000000..0c575b493 --- /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 000000000..9049ccb30 --- /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 000000000..a9a1d07d7 --- /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 000000000..172b5f278 --- /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 000000000..9bbf44068 --- /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 000000000..38295fc69 --- /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 000000000..af5a113f4 --- /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 000000000..a5aad0b51 --- /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 000000000..917d048df --- /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 000000000..be54afaa9 --- /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 000000000..b58cc0ab2 --- /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 000000000..320846003 --- /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 000000000..ba97dfb79 --- /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 000000000..0967e298a --- /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 000000000..f568e88cf --- /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 000000000..6f70c2055 --- /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 000000000..d6de390ad --- /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 000000000..b50d1cace --- /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 000000000..793540621 --- /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 000000000..0d0312590 --- /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 000000000..ab68f3cd1 --- /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 000000000..64d54ee28 --- /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 000000000..a3892b476 --- /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 000000000..b581b540d --- /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 000000000..0935819a8 --- /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 000000000..cb37a9ff8 --- /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 000000000..7e88da6df --- /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 000000000..ab16276c9 --- /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 000000000..f536e5ab3 --- /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 000000000..90482f21d --- /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 000000000..eb29dc60a --- /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 000000000..0ccb0e0fe --- /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 000000000..9ab460766 --- /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 000000000..b07feb02d --- /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 000000000..9f2385abb --- /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 000000000..3149a39bc --- /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 000000000..481d0dd15 --- /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 000000000..a5fa0edc4 --- /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 000000000..8aa76528b --- /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 000000000..b0aae390d --- /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 000000000..d54268e63 --- /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 000000000..3813dfd17 --- /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 000000000..c1ca75471 --- /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 000000000..d5bdc5c35 --- /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 000000000..add798b1b --- /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 000000000..8ad86d4c2 --- /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 000000000..2904d312f --- /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 000000000..369b5d917 --- /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 000000000..39534a24a --- /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 000000000..51fcb3eb4 --- /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 000000000..47975b523 --- /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 000000000..f6f038ba0 --- /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 000000000..9baa66b16 --- /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 000000000..46670fce7 --- /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 000000000..106ceaaf5 --- /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 000000000..3df8687ec --- /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 000000000..9a429edc8 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 000000000..47e3b1a29 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/var-dump-server b/data/web/inc/lib/vendor/bin/var-dump-server deleted file mode 100755 index 18db1c1eb..000000000 --- a/data/web/inc/lib/vendor/bin/var-dump-server +++ /dev/null @@ -1,119 +0,0 @@ -#!/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__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'); - } -} - -return include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'; diff --git a/data/web/inc/lib/vendor/composer/InstalledVersions.php b/data/web/inc/lib/vendor/composer/InstalledVersions.php index 51e734a77..6d29bff66 100644 --- a/data/web/inc/lib/vendor/composer/InstalledVersions.php +++ b/data/web/inc/lib/vendor/composer/InstalledVersions.php @@ -32,6 +32,11 @@ class InstalledVersions */ private static $installed; + /** + * @var bool + */ + private static $installedIsLocalDir; + /** * @var bool|null */ @@ -309,6 +314,12 @@ class InstalledVersions { 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; } /** @@ -322,19 +333,27 @@ class InstalledVersions } $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'; - $installed[] = self::$installedByVendor[$vendorDir] = $required; - if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { - self::$installed = $installed[count($installed) - 1]; + 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; + } } } @@ -350,7 +369,7 @@ class InstalledVersions } } - if (self::$installed !== array()) { + if (self::$installed !== array() && !$copiedLocalDir) { $installed[] = self::$installed; } diff --git a/data/web/inc/lib/vendor/composer/autoload_classmap.php b/data/web/inc/lib/vendor/composer/autoload_classmap.php index 8e4b7d313..3885b22a6 100644 --- a/data/web/inc/lib/vendor/composer/autoload_classmap.php +++ b/data/web/inc/lib/vendor/composer/autoload_classmap.php @@ -9,6 +9,7 @@ return array( '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', diff --git a/data/web/inc/lib/vendor/composer/autoload_files.php b/data/web/inc/lib/vendor/composer/autoload_files.php index c426e71df..e2cabb90d 100644 --- a/data/web/inc/lib/vendor/composer/autoload_files.php +++ b/data/web/inc/lib/vendor/composer/autoload_files.php @@ -10,15 +10,12 @@ return array( '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', - 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', 'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php', - '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', - '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', + '60799491728b879e74601d83e38b2cad' => $vendorDir . '/illuminate/collections/helpers.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', '23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php', - 'fe62ba7e10580d903cc46d808b5961a4' => $vendorDir . '/tightenco/collect/src/Collect/Support/helpers.php', - 'caf31cc6ec7cf2241cb6f12c226c3846' => $vendorDir . '/tightenco/collect/src/Collect/Support/alias.php', '04c6c5c2f7095ccf6c481d3e53e1776f' => $vendorDir . '/mustangostang/spyc/Spyc.php', '89efb1254ef2d1c5d80096acd12c4098' => $vendorDir . '/twig/twig/src/Resources/core.php', 'ffecb95d45175fd40f75be8a23b34f90' => $vendorDir . '/twig/twig/src/Resources/debug.php', diff --git a/data/web/inc/lib/vendor/composer/autoload_psr4.php b/data/web/inc/lib/vendor/composer/autoload_psr4.php index eca85b846..236034cf0 100644 --- a/data/web/inc/lib/vendor/composer/autoload_psr4.php +++ b/data/web/inc/lib/vendor/composer/autoload_psr4.php @@ -7,13 +7,11 @@ $baseDir = dirname($vendorDir); return array( 'Twig\\' => array($vendorDir . '/twig/twig/src'), - 'Tightenco\\Collect\\' => array($vendorDir . '/tightenco/collect/src/Collect'), '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\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'), 'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'), 'Stevenmaguire\\OAuth2\\Client\\' => array($vendorDir . '/stevenmaguire/oauth2-keycloak/src'), 'RobThree\\Auth\\' => array($vendorDir . '/robthree/twofactorauth/lib'), @@ -29,6 +27,7 @@ return array( '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'), @@ -36,6 +35,8 @@ return array( '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_static.php b/data/web/inc/lib/vendor/composer/autoload_static.php index 5375b0bea..f66ad0c71 100644 --- a/data/web/inc/lib/vendor/composer/autoload_static.php +++ b/data/web/inc/lib/vendor/composer/autoload_static.php @@ -11,15 +11,12 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', - 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', 'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php', - '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', - '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + '60799491728b879e74601d83e38b2cad' => __DIR__ . '/..' . '/illuminate/collections/helpers.php', '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', '23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php', - 'fe62ba7e10580d903cc46d808b5961a4' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/helpers.php', - 'caf31cc6ec7cf2241cb6f12c226c3846' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/alias.php', '04c6c5c2f7095ccf6c481d3e53e1776f' => __DIR__ . '/..' . '/mustangostang/spyc/Spyc.php', '89efb1254ef2d1c5d80096acd12c4098' => __DIR__ . '/..' . '/twig/twig/src/Resources/core.php', 'ffecb95d45175fd40f75be8a23b34f90' => __DIR__ . '/..' . '/twig/twig/src/Resources/debug.php', @@ -28,27 +25,25 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b ); public static $prefixLengthsPsr4 = array ( - 'T' => + 'T' => array ( 'Twig\\' => 5, - 'Tightenco\\Collect\\' => 18, ), - 'S' => + 'S' => array ( 'Symfony\\Polyfill\\Php81\\' => 23, 'Symfony\\Polyfill\\Php80\\' => 23, 'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Polyfill\\Ctype\\' => 23, 'Symfony\\Contracts\\Translation\\' => 30, - 'Symfony\\Component\\VarDumper\\' => 28, 'Symfony\\Component\\Translation\\' => 30, 'Stevenmaguire\\OAuth2\\Client\\' => 28, ), - 'R' => + 'R' => array ( 'RobThree\\Auth\\' => 14, ), - 'P' => + 'P' => array ( 'Psr\\SimpleCache\\' => 16, 'Psr\\Log\\' => 8, @@ -59,181 +54,193 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b 'PhpMimeMailParser\\' => 18, 'PHPMailer\\PHPMailer\\' => 20, ), - 'M' => + 'M' => array ( 'MatthiasMullie\\PathConverter\\' => 29, 'MatthiasMullie\\Minify\\' => 22, ), - 'L' => + 'L' => array ( 'League\\OAuth2\\Client\\' => 21, 'LdapRecord\\' => 11, ), - 'I' => + 'I' => array ( + 'Illuminate\\Support\\' => 19, 'Illuminate\\Contracts\\' => 21, ), - 'H' => + 'H' => array ( 'Html2Text\\' => 10, ), - 'G' => + 'G' => array ( 'GuzzleHttp\\Psr7\\' => 16, 'GuzzleHttp\\Promise\\' => 19, 'GuzzleHttp\\' => 11, ), - 'F' => + 'F' => array ( 'Firebase\\JWT\\' => 13, ), - 'D' => + 'D' => array ( 'Ddeboer\\Imap\\' => 13, + 'DASPRiD\\Enum\\' => 13, ), - 'C' => + 'C' => array ( 'Carbon\\Doctrine\\' => 16, 'Carbon\\' => 7, ), + 'B' => + array ( + 'BaconQrCode\\' => 12, + ), ); public static $prefixDirsPsr4 = array ( - 'Twig\\' => + 'Twig\\' => array ( 0 => __DIR__ . '/..' . '/twig/twig/src', ), - 'Tightenco\\Collect\\' => - array ( - 0 => __DIR__ . '/..' . '/tightenco/collect/src/Collect', - ), - 'Symfony\\Polyfill\\Php81\\' => + 'Symfony\\Polyfill\\Php81\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php81', ), - 'Symfony\\Polyfill\\Php80\\' => + 'Symfony\\Polyfill\\Php80\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', ), - 'Symfony\\Polyfill\\Mbstring\\' => + 'Symfony\\Polyfill\\Mbstring\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', ), - 'Symfony\\Polyfill\\Ctype\\' => + 'Symfony\\Polyfill\\Ctype\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', ), - 'Symfony\\Contracts\\Translation\\' => + 'Symfony\\Contracts\\Translation\\' => array ( 0 => __DIR__ . '/..' . '/symfony/translation-contracts', ), - 'Symfony\\Component\\VarDumper\\' => - array ( - 0 => __DIR__ . '/..' . '/symfony/var-dumper', - ), - 'Symfony\\Component\\Translation\\' => + 'Symfony\\Component\\Translation\\' => array ( 0 => __DIR__ . '/..' . '/symfony/translation', ), - 'Stevenmaguire\\OAuth2\\Client\\' => + 'Stevenmaguire\\OAuth2\\Client\\' => array ( 0 => __DIR__ . '/..' . '/stevenmaguire/oauth2-keycloak/src', ), - 'RobThree\\Auth\\' => + 'RobThree\\Auth\\' => array ( 0 => __DIR__ . '/..' . '/robthree/twofactorauth/lib', ), - 'Psr\\SimpleCache\\' => + 'Psr\\SimpleCache\\' => array ( 0 => __DIR__ . '/..' . '/psr/simple-cache/src', ), - 'Psr\\Log\\' => + 'Psr\\Log\\' => array ( 0 => __DIR__ . '/..' . '/psr/log/src', ), - 'Psr\\Http\\Message\\' => + 'Psr\\Http\\Message\\' => array ( 0 => __DIR__ . '/..' . '/psr/http-factory/src', 1 => __DIR__ . '/..' . '/psr/http-message/src', ), - 'Psr\\Http\\Client\\' => + 'Psr\\Http\\Client\\' => array ( 0 => __DIR__ . '/..' . '/psr/http-client/src', ), - 'Psr\\Container\\' => + 'Psr\\Container\\' => array ( 0 => __DIR__ . '/..' . '/psr/container/src', ), - 'Psr\\Clock\\' => + 'Psr\\Clock\\' => array ( 0 => __DIR__ . '/..' . '/psr/clock/src', ), - 'PhpMimeMailParser\\' => + 'PhpMimeMailParser\\' => array ( 0 => __DIR__ . '/..' . '/php-mime-mail-parser/php-mime-mail-parser/src', ), - 'PHPMailer\\PHPMailer\\' => + 'PHPMailer\\PHPMailer\\' => array ( 0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src', ), - 'MatthiasMullie\\PathConverter\\' => + 'MatthiasMullie\\PathConverter\\' => array ( 0 => __DIR__ . '/..' . '/matthiasmullie/path-converter/src', ), - 'MatthiasMullie\\Minify\\' => + 'MatthiasMullie\\Minify\\' => array ( 0 => __DIR__ . '/..' . '/matthiasmullie/minify/src', ), - 'League\\OAuth2\\Client\\' => + 'League\\OAuth2\\Client\\' => array ( 0 => __DIR__ . '/..' . '/league/oauth2-client/src', ), - 'LdapRecord\\' => + 'LdapRecord\\' => array ( 0 => __DIR__ . '/..' . '/directorytree/ldaprecord/src', ), - 'Illuminate\\Contracts\\' => + 'Illuminate\\Support\\' => + array ( + 0 => __DIR__ . '/..' . '/illuminate/collections', + 1 => __DIR__ . '/..' . '/illuminate/macroable', + 2 => __DIR__ . '/..' . '/illuminate/conditionable', + ), + 'Illuminate\\Contracts\\' => array ( 0 => __DIR__ . '/..' . '/illuminate/contracts', ), - 'Html2Text\\' => + 'Html2Text\\' => array ( 0 => __DIR__ . '/..' . '/soundasleep/html2text/src', ), - 'GuzzleHttp\\Psr7\\' => + 'GuzzleHttp\\Psr7\\' => array ( 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', ), - 'GuzzleHttp\\Promise\\' => + 'GuzzleHttp\\Promise\\' => array ( 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', ), - 'GuzzleHttp\\' => + 'GuzzleHttp\\' => array ( 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', ), - 'Firebase\\JWT\\' => + 'Firebase\\JWT\\' => array ( 0 => __DIR__ . '/..' . '/firebase/php-jwt/src', ), - 'Ddeboer\\Imap\\' => + 'Ddeboer\\Imap\\' => array ( 0 => __DIR__ . '/..' . '/ddeboer/imap/src', ), - 'Carbon\\Doctrine\\' => + 'DASPRiD\\Enum\\' => + array ( + 0 => __DIR__ . '/..' . '/dasprid/enum/src', + ), + 'Carbon\\Doctrine\\' => array ( 0 => __DIR__ . '/..' . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine', ), - 'Carbon\\' => + 'Carbon\\' => array ( 0 => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon', ), + 'BaconQrCode\\' => + array ( + 0 => __DIR__ . '/..' . '/bacon/bacon-qr-code/src', + ), ); public static $prefixesPsr0 = array ( - 'O' => + 'O' => array ( - 'OAuth2' => + 'OAuth2' => array ( 0 => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src', ), @@ -244,6 +251,7 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b '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', diff --git a/data/web/inc/lib/vendor/composer/installed.json b/data/web/inc/lib/vendor/composer/installed.json index 26e8bc42b..e2d7113bc 100644 --- a/data/web/inc/lib/vendor/composer/installed.json +++ b/data/web/inc/lib/vendor/composer/installed.json @@ -1,5 +1,62 @@ { "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", @@ -133,6 +190,59 @@ ], "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", @@ -213,36 +323,38 @@ }, { "name": "directorytree/ldaprecord", - "version": "v2.20.5", - "version_normalized": "2.20.5.0", + "version": "v3.8.5", + "version_normalized": "3.8.5.0", "source": { "type": "git", "url": "https://github.com/DirectoryTree/LdapRecord.git", - "reference": "5bd0a5a9d257cf1049ae83055dbba4c3479ddf16" + "reference": "00e5f088f8c4028d5f398783cccc2e8119a27a65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DirectoryTree/LdapRecord/zipball/5bd0a5a9d257cf1049ae83055dbba4c3479ddf16", - "reference": "5bd0a5a9d257cf1049ae83055dbba4c3479ddf16", + "url": "https://api.github.com/repos/DirectoryTree/LdapRecord/zipball/00e5f088f8c4028d5f398783cccc2e8119a27a65", + "reference": "00e5f088f8c4028d5f398783cccc2e8119a27a65", "shasum": "" }, "require": { + "ext-iconv": "*", "ext-json": "*", "ext-ldap": "*", - "illuminate/contracts": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0", - "nesbot/carbon": "^1.0|^2.0", - "php": ">=7.3", - "psr/log": "^1.0|^2.0|^3.0", - "psr/simple-cache": "^1.0|^2.0", - "symfony/polyfill-php80": "^1.25", - "tightenco/collect": "^5.6|^6.0|^7.0|^8.0|^9.0" + "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": "2023-10-11T16:34:34+00:00", + "time": "2025-10-06T02:22:34+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -691,6 +803,113 @@ ], "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", @@ -742,6 +961,55 @@ }, "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", @@ -2538,151 +2806,6 @@ ], "install-path": "../symfony/translation-contracts" }, - { - "name": "symfony/var-dumper", - "version": "v6.4.3", - "version_normalized": "6.4.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "0435a08f69125535336177c29d56af3abc1f69da" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0435a08f69125535336177c29d56af3abc1f69da", - "reference": "0435a08f69125535336177c29d56af3abc1f69da", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/console": "<5.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^6.3|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/uid": "^5.4|^6.0|^7.0", - "twig/twig": "^2.13|^3.0.4" - }, - "time": "2024-01-23T14:53:30+00:00", - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "installation-source": "dist", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "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": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/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/var-dumper" - }, - { - "name": "tightenco/collect", - "version": "v9.52.7", - "version_normalized": "9.52.7.0", - "source": { - "type": "git", - "url": "https://github.com/tighten/collect.git", - "reference": "b15143cd11fe01a700fcc449df61adc64452fa6d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/tighten/collect/zipball/b15143cd11fe01a700fcc449df61adc64452fa6d", - "reference": "b15143cd11fe01a700fcc449df61adc64452fa6d", - "shasum": "" - }, - "require": { - "php": "^8.0", - "symfony/var-dumper": "^3.4 || ^4.0 || ^5.0 || ^6.0" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "nesbot/carbon": "^2.23.0", - "phpunit/phpunit": "^8.3" - }, - "time": "2023-04-14T21:51:36+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "files": [ - "src/Collect/Support/helpers.php", - "src/Collect/Support/alias.php" - ], - "psr-4": { - "Tightenco\\Collect\\": "src/Collect" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylorotwell@gmail.com" - } - ], - "description": "Collect - Illuminate Collections as a separate package.", - "keywords": [ - "collection", - "laravel" - ], - "support": { - "issues": "https://github.com/tighten/collect/issues", - "source": "https://github.com/tighten/collect/tree/v9.52.7" - }, - "install-path": "../tightenco/collect" - }, { "name": "twig/twig", "version": "v3.14.0", diff --git a/data/web/inc/lib/vendor/composer/installed.php b/data/web/inc/lib/vendor/composer/installed.php index 074ec9f80..15a1aa89f 100644 --- a/data/web/inc/lib/vendor/composer/installed.php +++ b/data/web/inc/lib/vendor/composer/installed.php @@ -1,9 +1,9 @@ array( 'name' => '__root__', - 'pretty_version' => 'dev-master', - 'version' => 'dev-master', - 'reference' => '220fdbb168792c07493db330d898b345cc902055', + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => null, 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -11,14 +11,23 @@ ), 'versions' => array( '__root__' => array( - 'pretty_version' => 'dev-master', - 'version' => 'dev-master', - 'reference' => '220fdbb168792c07493db330d898b345cc902055', + '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', @@ -37,6 +46,15 @@ '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', @@ -47,9 +65,9 @@ 'dev_requirement' => false, ), 'directorytree/ldaprecord' => array( - 'pretty_version' => 'v2.20.5', - 'version' => '2.20.5.0', - 'reference' => '5bd0a5a9d257cf1049ae83055dbba4c3479ddf16', + 'pretty_version' => 'v3.8.5', + 'version' => '3.8.5.0', + 'reference' => '00e5f088f8c4028d5f398783cccc2e8119a27a65', 'type' => 'library', 'install_path' => __DIR__ . '/../directorytree/ldaprecord', 'aliases' => array(), @@ -97,6 +115,24 @@ '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', @@ -106,6 +142,15 @@ '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', @@ -376,24 +421,6 @@ 0 => '2.3|3.0', ), ), - 'symfony/var-dumper' => array( - 'pretty_version' => 'v6.4.3', - 'version' => '6.4.3.0', - 'reference' => '0435a08f69125535336177c29d56af3abc1f69da', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/var-dumper', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'tightenco/collect' => array( - 'pretty_version' => 'v9.52.7', - 'version' => '9.52.7.0', - 'reference' => 'b15143cd11fe01a700fcc449df61adc64452fa6d', - 'type' => 'library', - 'install_path' => __DIR__ . '/../tightenco/collect', - 'aliases' => array(), - 'dev_requirement' => false, - ), 'twig/twig' => array( 'pretty_version' => 'v3.14.0', 'version' => '3.14.0.0', 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 000000000..d45a35647 --- /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 000000000..da37045e3 --- /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 000000000..a099aba54 --- /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 000000000..43006dce6 --- /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 000000000..95b88568d --- /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 000000000..4b37dbebe --- /dev/null +++ b/data/web/inc/lib/vendor/dasprid/enum/src/Exception/CloneNotSupportedException.php @@ -0,0 +1,10 @@ + **Environment:** - LDAP Server Type: [e.g. ActiveDirectory / OpenLDAP / FreeIPA] - - PHP Version: [e.g. 7.3 / 7.4 / 8.0] + - PHP Version: [e.g. 8.1 / 8.2] **Describe the bug:** 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 index 731d5cea0..b97a977cb 100644 --- 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 @@ -17,4 +17,4 @@ assignees: '' --> **Environment:** - LDAP Server Type: [e.g. ActiveDirectory / OpenLDAP / FreeIPA] - - PHP Version: [e.g. 7.3 / 7.4 / 8.0] + - 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 000000000..80cd34d61 --- /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 index 6a6b2f9e5..de67e91c9 100644 --- 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 @@ -1,62 +1,62 @@ name: run-integration-tests on: - push: - pull_request: - schedule: - - cron: "0 0 * * *" + push: + pull_request: + schedule: + - cron: "0 0 * * *" jobs: - run-tests: - runs-on: ${{ matrix.os }} + 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 + 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.0, 7.4] + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + php: [8.1, 8.2, 8.3] - name: ${{ matrix.os }} - P${{ matrix.php }} + name: ${{ matrix.os }} - P${{ matrix.php }} - steps: - - name: Checkout code - uses: actions/checkout@v2 + steps: + - name: Checkout code + uses: actions/checkout@v2 - - name: Cache dependencies - uses: actions/cache@v2 - with: - path: ~/.composer/cache/files - key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} + - 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: 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: 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: 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: Install dependencies + run: composer update --prefer-dist --no-interaction - - name: Execute tests - run: vendor/bin/phpunit --testsuite Integration + - 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 index 6aaa3fd4a..8781e918a 100644 --- 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 @@ -1,41 +1,41 @@ name: run-tests on: - push: - pull_request: - schedule: - - cron: "0 0 * * *" + push: + pull_request: + schedule: + - cron: "0 0 * * *" jobs: - run-tests: - runs-on: ${{ matrix.os }} - name: ${{ matrix.os }} - P${{ matrix.php }} + 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.0, 7.4, 7.3] + 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 + steps: + - name: Checkout code + uses: actions/checkout@v2 - - name: Cache dependencies - uses: actions/cache@v2 - with: - path: ~/.composer/cache/files - key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} + - 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: 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: Install dependencies + run: composer update --prefer-dist --no-interaction - - name: Execute tests - run: vendor/bin/phpunit --testsuite Unit + - name: Execute tests + run: vendor/bin/phpunit --testsuite Unit diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/.scrutinizer.yml b/data/web/inc/lib/vendor/directorytree/ldaprecord/.scrutinizer.yml deleted file mode 100644 index b3f8f88b2..000000000 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/.scrutinizer.yml +++ /dev/null @@ -1,9 +0,0 @@ -filter: - excluded_paths: - - tests/* -build: - nodes: - analysis: - tests: - override: - - command: php-scrutinizer-run diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/.styleci.yml b/data/web/inc/lib/vendor/directorytree/ldaprecord/.styleci.yml deleted file mode 100644 index 0285f1790..000000000 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/.styleci.yml +++ /dev/null @@ -1 +0,0 @@ -preset: laravel diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/composer.json b/data/web/inc/lib/vendor/directorytree/ldaprecord/composer.json index 65d7dd8f6..97e84cd5e 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/composer.json +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/composer.json @@ -29,20 +29,22 @@ } ], "require": { - "php": ">=7.3", + "php": ">=8.1", "ext-ldap": "*", "ext-json": "*", - "psr/log": "^1.0|^2.0|^3.0", - "psr/simple-cache": "^1.0|^2.0", - "nesbot/carbon": "^1.0|^2.0", - "tightenco/collect": "^5.6|^6.0|^7.0|^8.0|^9.0", - "illuminate/contracts": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0", - "symfony/polyfill-php80": "^1.25" + "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" + "spatie/ray": "^1.24", + "laravel/pint": "^1.6", + "fakerphp/faker": "^1.21" }, "archive": { "exclude": [ diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/docker-compose.yml b/data/web/inc/lib/vendor/directorytree/ldaprecord/docker-compose.yml index de96b1398..1abf0979d 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/docker-compose.yml +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3' - services: ldap: image: osixia/openldap:1.4.0 diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/readme.md b/data/web/inc/lib/vendor/directorytree/ldaprecord/readme.md index e00f8ef69..4e34bd727 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/readme.md +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/readme.md @@ -1,25 +1,12 @@ - -

- +

- - - - - - - - - - - - - - - + + + +

@@ -32,9 +19,9 @@

- Quickstart + Quickstart · - Documentation + Documentation · Laravel Integration · @@ -67,15 +54,15 @@ 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/v2/active-directory/users/#uac). +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/v2/active-directory/users/#changing-passwords) and [resetting](https://ldaprecord.com/docs/core/v2/active-directory/users/#resetting-passwords) passwords on Active Directory accounts. +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** -We've all been there -- accidentally deleting a user or group in Active Directory. [Restore them](https://ldaprecord.com/docs/core/v2/models/#restoring-deleted-models) by seamlessly accessing your directory's recycle bin. +Seamlessly access your Active Directory recycle bin and [restore deleted objects](https://ldaprecord.com/docs/core/v3/models/#restoring-deleted-models). --- diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Events/Event.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Events/Event.php index 7c1bb3c87..29687dbf2 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Events/Event.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Events/Event.php @@ -8,33 +8,23 @@ abstract class Event { /** * The connection that the username and password is being bound on. - * - * @var LdapInterface */ - protected $connection; + protected LdapInterface $connection; /** * The username that is being used for binding. - * - * @var string */ - protected $username; + protected ?string $username; /** * The password that is being used for binding. - * - * @var string */ - protected $password; + protected ?string $password; /** * Constructor. - * - * @param LdapInterface $connection - * @param string $username - * @param string $password */ - public function __construct(LdapInterface $connection, $username, $password) + public function __construct(LdapInterface $connection, ?string $username = null, ?string $password = null) { $this->connection = $connection; $this->username = $username; @@ -42,31 +32,25 @@ abstract class Event } /** - * Returns the events connection. - * - * @return LdapInterface + * Get the event's connection. */ - public function getConnection() + public function getConnection(): LdapInterface { return $this->connection; } /** - * Returns the authentication events username. - * - * @return string + * Get the authentication event's username. */ - public function getUsername() + public function getUsername(): ?string { return $this->username; } /** - * Returns the authentication events password. - * - * @return string + * Get the authentication event's password. */ - public function getPassword() + 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 index 7133e4390..b26049169 100644 --- 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 @@ -2,7 +2,32 @@ namespace LdapRecord\Auth\Events; +use Exception; +use LdapRecord\Auth\BindException; +use LdapRecord\LdapInterface; + class Failed extends Event { - // + /** + * The exception that was thrown during the bind attempt. + */ + protected BindException $exception; + + /** + * Constructor. + */ + public function __construct(LdapInterface $connection, ?string $username, ?string $password, BindException $exception) + { + parent::__construct($connection, $username, $password); + + $this->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/Guard.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Guard.php index c00989ae6..d560098b7 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Guard.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Guard.php @@ -3,11 +3,6 @@ namespace LdapRecord\Auth; use Exception; -use LdapRecord\Auth\Events\Attempting; -use LdapRecord\Auth\Events\Binding; -use LdapRecord\Auth\Events\Bound; -use LdapRecord\Auth\Events\Failed; -use LdapRecord\Auth\Events\Passed; use LdapRecord\Configuration\DomainConfiguration; use LdapRecord\Events\DispatcherInterface; use LdapRecord\LdapInterface; @@ -16,30 +11,21 @@ class Guard { /** * The connection to bind to. - * - * @var LdapInterface */ - protected $connection; + protected LdapInterface $connection; /** * The domain configuration to utilize. - * - * @var DomainConfiguration */ - protected $configuration; + protected DomainConfiguration $configuration; /** * The event dispatcher. - * - * @var DispatcherInterface */ - protected $events; + protected ?DispatcherInterface $events = null; /** * Constructor. - * - * @param LdapInterface $connection - * @param DomainConfiguration $configuration */ public function __construct(LdapInterface $connection, DomainConfiguration $configuration) { @@ -50,15 +36,10 @@ class Guard /** * Attempt binding a user to the LDAP server. * - * @param string $username - * @param string $password - * @param bool $stayBound - * @return bool - * * @throws UsernameRequiredException * @throws PasswordRequiredException */ - public function attempt($username, $password, $stayBound = false) + public function attempt(string $username, string $password, bool $stayBound = false): bool { switch (true) { case empty($username): @@ -67,58 +48,73 @@ class Guard throw new PasswordRequiredException('A password must be specified.'); } - $this->fireAttemptingEvent($username, $password); + $this->fireAuthEvent('attempting', $username, $password); try { $this->bind($username, $password); - $authenticated = true; + $bound = true; - $this->firePassedEvent($username, $password); - } catch (BindException $e) { - $authenticated = false; + $this->fireAuthEvent('passed', $username, $password); + } catch (BindException) { + $bound = false; } if (! $stayBound) { $this->bindAsConfiguredUser(); } - return $authenticated; + return $bound; } /** - * Attempt binding a user to the LDAP server. Supports anonymous binding. - * - * @param string|null $username - * @param string|null $password + * Attempt binding a user to the LDAP server. Supports sasl and anonymous binding. * * @throws BindException * @throws \LdapRecord\ConnectionException */ - public function bind($username = null, $password = null) + public function bind(?string $username = null, ?string $password = null): void { - $this->fireBindingEvent($username, $password); + $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->isBound()) { + if ($this->connection->isUsingTLS() && ! $this->connection->isSecure()) { $this->connection->startTLS(); } try { - if (! $this->connection->bind($username, $password)) { + if (! $this->authenticate($username, $password)) { throw new Exception($this->connection->getLastError(), $this->connection->errNo()); } - $this->fireBoundEvent($username, $password); + $this->fireAuthEvent('bound', $username, $password); } catch (Exception $e) { - $this->fireFailedEvent($username, $password); + $exception = BindException::withDetailedError($e, $this->connection->getDetailedError()); - throw 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. * @@ -126,7 +122,7 @@ class Guard * @throws \LdapRecord\ConnectionException * @throws \LdapRecord\Configuration\ConfigurationException */ - public function bindAsConfiguredUser() + public function bindAsConfiguredUser(): void { $this->bind( $this->configuration->get('username'), @@ -136,92 +132,29 @@ class Guard /** * Get the event dispatcher instance. - * - * @return DispatcherInterface */ - public function getDispatcher() + public function getDispatcher(): ?DispatcherInterface { return $this->events; } /** * Set the event dispatcher instance. - * - * @param DispatcherInterface $dispatcher - * @return void */ - public function setDispatcher(DispatcherInterface $dispatcher) + public function setDispatcher(?DispatcherInterface $dispatcher = null): void { $this->events = $dispatcher; } /** - * Fire the attempting event. - * - * @param string $username - * @param string $password - * @return void + * Fire an authentication event. */ - protected function fireAttemptingEvent($username, $password) + protected function fireAuthEvent(string $name, ?string $username = null, ?string $password = null, ...$args): void { if (isset($this->events)) { - $this->events->fire(new Attempting($this->connection, $username, $password)); - } - } + $event = implode('\\', [Events::class, ucfirst($name)]); - /** - * 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)); + $this->events->fire(new $event($this->connection, $username, $password, ...$args)); } } } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/DomainConfiguration.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/DomainConfiguration.php index f71f63d87..425fbd9ed 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/DomainConfiguration.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Configuration/DomainConfiguration.php @@ -8,19 +8,15 @@ class DomainConfiguration { /** * The extended configuration options. - * - * @var array */ - protected static $extended = []; + protected static array $extended = []; /** * The configuration options array. * * The default values for each key indicate the type of value it requires. - * - * @var array */ - protected $options = [ + protected array $options = [ // An array of LDAP hosts. 'hosts' => [], @@ -33,6 +29,9 @@ class DomainConfiguration // 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' => '', @@ -42,13 +41,28 @@ class DomainConfiguration // The password to use for binding. 'password' => '', - // Whether or not to use SSL when connecting. + // Whether to use SSL when connecting. 'use_ssl' => false, - // Whether or not to use TLS when connecting. + // Whether to use TLS when connecting. 'use_tls' => false, - // Whether or not follow referrals is enabled when performing LDAP operations. + // 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. @@ -58,8 +72,6 @@ class DomainConfiguration /** * Constructor. * - * @param array $options - * * @throws ConfigurationException When an option value given is an invalid type. */ public function __construct(array $options = []) @@ -73,32 +85,24 @@ class DomainConfiguration /** * Extend the configuration with a custom option, or override an existing. - * - * @param string $option - * @param mixed $default - * @return void */ - public static function extend($option, $default = null) + public static function extend(string $option, mixed $default = null): void { static::$extended[$option] = $default; } /** * Flush the extended configuration options. - * - * @return void */ - public static function flushExtended() + public static function flushExtended(): void { static::$extended = []; } /** * Get all configuration options. - * - * @return array */ - public function all() + public function all(): array { return $this->options; } @@ -106,12 +110,9 @@ class DomainConfiguration /** * Set a configuration option. * - * @param string $key - * @param mixed $value - * * @throws ConfigurationException When an option value given is an invalid type. */ - public function set($key, $value) + public function set(string $key, mixed $value): void { if ($this->validate($key, $value)) { $this->options[$key] = $value; @@ -119,17 +120,14 @@ class DomainConfiguration } /** - * Returns the value for the specified configuration options. - * - * @param string $key - * @return mixed + * Get the value for the specified configuration options. * * @throws ConfigurationException When the option specified does not exist. */ - public function get($key) + public function get(string $key): mixed { if (! $this->has($key)) { - throw new ConfigurationException("Option {$key} does not exist."); + throw new ConfigurationException("Option $key does not exist."); } return $this->options[$key]; @@ -137,11 +135,8 @@ class DomainConfiguration /** * Checks if a configuration option exists. - * - * @param string $key - * @return bool */ - public function has($key) + public function has(string $key): bool { return array_key_exists($key, $this->options); } @@ -149,13 +144,9 @@ class DomainConfiguration /** * Validate the configuration option. * - * @param string $key - * @param mixed $value - * @return bool - * * @throws ConfigurationException When an option value given is an invalid type. */ - protected function validate($key, $value) + protected function validate(string $key, mixed $value): bool { $default = $this->get($key); 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 index 4aa43ed46..1324d1681 100644 --- 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 @@ -6,15 +6,13 @@ class ArrayValidator extends Validator { /** * The validation exception message. - * - * @var string */ - protected $message = 'Option [:option] must be an array.'; + protected string $message = 'Option [:option] must be an array.'; /** - * @inheritdoc + * {@inheritdoc} */ - public function passes() + public function passes(): bool { return is_array($this->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 index 1d25a4b25..1156859d3 100644 --- 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 @@ -6,15 +6,13 @@ class BooleanValidator extends Validator { /** * The validation exception message. - * - * @var string */ - protected $message = 'Option [:option] must be a boolean.'; + protected string $message = 'Option [:option] must be a boolean.'; /** - * @inheritdoc + * {@inheritdoc} */ - public function passes() + public function passes(): bool { return is_bool($this->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 index 5c4f0f95e..d7015888e 100644 --- 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 @@ -6,15 +6,13 @@ class IntegerValidator extends Validator { /** * The validation exception message. - * - * @var string */ - protected $message = 'Option [:option] must be an integer.'; + protected string $message = 'Option [:option] must be an integer.'; /** - * @inheritdoc + * {@inheritdoc} */ - public function passes() + public function passes(): bool { return is_numeric($this->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 index bc2337245..5ca453c74 100644 --- 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 @@ -6,15 +6,13 @@ class StringOrNullValidator extends Validator { /** * The validation exception message. - * - * @var string */ - protected $message = 'Option [:option] must be a string or null.'; + protected string $message = 'Option [:option] must be a string or null.'; /** - * @inheritdoc + * {@inheritdoc} */ - public function passes() + public function passes(): bool { return is_string($this->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 index d107314d5..25aedcbfa 100644 --- 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 @@ -8,32 +8,23 @@ abstract class Validator { /** * The configuration key under validation. - * - * @var string */ - protected $key; + protected string $key; /** * The configuration value under validation. - * - * @var mixed */ - protected $value; + protected mixed $value; /** * The validation exception message. - * - * @var string */ - protected $message; + protected string $message; /** * Constructor. - * - * @param string $key - * @param mixed $value */ - public function __construct($key, $value) + public function __construct(string $key, mixed $value) { $this->key = $key; $this->value = $value; @@ -41,19 +32,15 @@ abstract class Validator /** * Determine if the validation rule passes. - * - * @return bool */ - abstract public function passes(); + abstract public function passes(): bool; /** * Validate the configuration value. * - * @return bool - * * @throws ConfigurationException */ - public function validate() + public function validate(): bool { if (! $this->passes()) { $this->fail(); @@ -65,11 +52,9 @@ abstract class Validator /** * Throw a configuration exception. * - * @return void - * * @throws ConfigurationException */ - protected function fail() + 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 index 6e55cffc9..e04393a27 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Connection.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Connection.php @@ -5,6 +5,7 @@ namespace LdapRecord; use Carbon\Carbon; use Closure; use LdapRecord\Auth\Guard; +use LdapRecord\Configuration\ConfigurationException; use LdapRecord\Configuration\DomainConfiguration; use LdapRecord\Events\DispatcherInterface; use LdapRecord\Query\Builder; @@ -17,85 +18,64 @@ class Connection /** * The underlying LDAP connection. - * - * @var Ldap */ - protected $ldap; + protected LdapInterface $ldap; /** * The cache driver. - * - * @var Cache|null */ - protected $cache; + protected ?Cache $cache = null; /** * The domain configuration. - * - * @var DomainConfiguration */ - protected $configuration; + protected DomainConfiguration $configuration; /** - * The event dispatcher;. - * - * @var DispatcherInterface|null + * The event dispatcher. */ - protected $dispatcher; + protected ?DispatcherInterface $dispatcher = null; /** - * The current host connected to. - * - * @var string + * The currently connected host. */ - protected $host; + protected string $host; /** * The configured domain hosts. - * - * @var array */ - protected $hosts = []; + protected array $hosts = []; /** * The attempted hosts that failed connecting to. - * - * @var array */ - protected $attempted = []; + protected array $attempted = []; /** * The callback to execute upon total connection failure. - * - * @var Closure */ - protected $failed; + protected Closure $failed; /** * The authentication guard resolver. - * - * @var Closure */ - protected $authGuardResolver; + protected Closure $authGuardResolver; /** * Whether the connection is retrying the initial connection attempt. - * - * @var bool */ - protected $retryingInitialConnection = false; + protected bool $retryingInitialConnection = false; /** * Constructor. * - * @param array|DomainConfiguration $config - * @param LdapInterface|null $ldap + * @throws ConfigurationException */ - public function __construct($config = [], LdapInterface $ldap = null) + public function __construct(DomainConfiguration|array $config = [], ?LdapInterface $ldap = null) { $this->setConfiguration($config); - $this->setLdapConnection($ldap ?? new Ldap()); + $this->setLdapConnection($ldap ?? new Ldap); $this->failed = function () { $this->dispatch(new Events\ConnectionFailed($this)); @@ -109,12 +89,9 @@ class Connection /** * Set the connection configuration. * - * @param array|DomainConfiguration $config - * @return $this - * * @throws Configuration\ConfigurationException */ - public function setConfiguration($config = []) + public function setConfiguration(DomainConfiguration|array $config = []): void { if (! $config instanceof DomainConfiguration) { $config = new DomainConfiguration($config); @@ -125,54 +102,50 @@ class Connection $this->hosts = $this->configuration->get('hosts'); $this->host = reset($this->hosts); - - return $this; } /** * Set the LDAP connection. - * - * @param LdapInterface $ldap - * @return $this */ - public function setLdapConnection(LdapInterface $ldap) + public function setLdapConnection(LdapInterface $ldap): void { $this->ldap = $ldap; - - return $this; } /** * Set the event dispatcher. - * - * @param DispatcherInterface $dispatcher - * @return $this */ - public function setDispatcher(DispatcherInterface $dispatcher) + public function setDispatcher(DispatcherInterface $dispatcher): void { $this->dispatcher = $dispatcher; - - return $this; } /** - * Initializes the LDAP connection. - * - * @return void + * Get the event dispatcher. */ - public function initialize() + 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->ldap->connect( + $this->host, + $this->configuration->get('port'), + $this->configuration->get('protocol') + ); } /** * Configure the LDAP connection. - * - * @return void */ - protected function configure() + protected function configure(): void { if ($this->configuration->get('use_ssl')) { $this->ldap->ssl(); @@ -192,60 +165,53 @@ class Connection /** * Set the cache store. - * - * @param CacheInterface $store - * @return $this */ - public function setCache(CacheInterface $store) + public function setCache(CacheInterface $store): void { $this->cache = new Cache($store); - - return $this; } /** * Get the cache store. - * - * @return Cache|null */ - public function getCache() + public function getCache(): ?Cache { return $this->cache; } /** * Get the LDAP configuration instance. - * - * @return DomainConfiguration */ - public function getConfiguration() + public function getConfiguration(): DomainConfiguration { return $this->configuration; } /** * Get the LDAP connection instance. - * - * @return Ldap */ - public function getLdapConnection() + 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. * - * @param string|null $username - * @param string|null $password - * @return Connection - * * @throws Auth\BindException * @throws LdapRecordException */ - public function connect($username = null, $password = null) + public function connect(?string $username = null, ?string $password = null): void { $attempt = function () use ($username, $password) { $this->dispatch(new Events\Connecting($this)); @@ -266,19 +232,16 @@ class Connection $this->retryOnNextHost($e, $attempt); } - - return $this; } /** * Reconnect to the LDAP server. * - * @return void - * * @throws Auth\BindException * @throws ConnectionException + * @throws LdapRecordException */ - public function reconnect() + public function reconnect(): void { $this->reinitialize(); @@ -287,10 +250,8 @@ class Connection /** * Reinitialize the connection. - * - * @return void */ - protected function reinitialize() + protected function reinitialize(): void { $this->disconnect(); @@ -299,31 +260,24 @@ class Connection /** * Clone the connection. - * - * @return static */ - public function replicate() + public function replicate(): static { return new static($this->configuration, new $this->ldap); } /** * Disconnect from the LDAP server. - * - * @return void */ - public function disconnect() + public function disconnect(): void { $this->ldap->close(); } /** * Dispatch an event. - * - * @param object $event - * @return void */ - public function dispatch($event) + public function dispatch(object $event): void { if (isset($this->dispatcher)) { $this->dispatcher->dispatch($event); @@ -332,25 +286,20 @@ class Connection /** * Get the attempted hosts that failed connecting to. - * - * @return array */ - public function attempted() + public function attempted(): array { return $this->attempted; } /** * Perform the operation on the LDAP connection. - * - * @param Closure $operation - * @return mixed */ - public function run(Closure $operation) + 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 + // connection is bound and connect if necessary. Otherwise, // some LDAP operations will not be executed properly. if (! $this->isConnected()) { $this->connect(); @@ -368,11 +317,8 @@ class Connection /** * Perform the operation on an isolated LDAP connection. - * - * @param Closure $operation - * @return mixed */ - public function isolate(Closure $operation) + public function isolate(Closure $operation): mixed { $connection = $this->replicate(); @@ -385,11 +331,8 @@ class Connection /** * Attempt to get an exception for the cause of failure. - * - * @param LdapRecordException $e - * @return mixed */ - protected function getExceptionForCauseOfFailure(LdapRecordException $e) + protected function getExceptionForCauseOfFailure(LdapRecordException $e): ?LdapRecordException { switch (true) { case $this->errorContainsMessage($e->getMessage(), 'Already exists'): @@ -399,29 +342,22 @@ class Connection case $this->errorContainsMessage($e->getMessage(), 'Constraint violation'): return Exceptions\ConstraintViolationException::withDetailedError($e, $e->getDetailedError()); default: - return; + return null; } } /** * Run the operation callback on the current LDAP connection. - * - * @param Closure $operation - * @return mixed - * - * @throws LdapRecordException */ - protected function runOperationCallback(Closure $operation) + protected function runOperationCallback(Closure $operation): mixed { return $operation($this->ldap); } /** * Get a new auth guard instance. - * - * @return Auth\Guard */ - public function auth() + public function auth(): Guard { if (! $this->ldap->isConnected()) { $this->initialize(); @@ -430,7 +366,7 @@ class Connection $guard = call_user_func($this->authGuardResolver); $guard->setDispatcher( - Container::getInstance()->getEventDispatcher() + Container::getInstance()->getDispatcher() ); return $guard; @@ -438,10 +374,8 @@ class Connection /** * Get a new query builder for the connection. - * - * @return Query\Builder */ - public function query() + public function query(): Builder { return (new Builder($this)) ->setCache($this->cache) @@ -450,10 +384,8 @@ class Connection /** * Determine if the LDAP connection is bound. - * - * @return bool */ - public function isConnected() + public function isConnected(): bool { return $this->ldap->isBound(); } @@ -461,13 +393,9 @@ class Connection /** * Attempt to retry an LDAP operation if due to a lost connection. * - * @param LdapRecordException $e - * @param Closure $operation - * @return mixed - * * @throws LdapRecordException */ - protected function tryAgainIfCausedByLostConnection(LdapRecordException $e, Closure $operation) + 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 @@ -482,12 +410,9 @@ class Connection /** * Retry the operation on the current host. * - * @param Closure $operation - * @return mixed - * * @throws LdapRecordException */ - protected function retry(Closure $operation) + protected function retry(Closure $operation): mixed { try { $this->retryingInitialConnection @@ -503,13 +428,9 @@ class Connection /** * Attempt the operation again on the next host. * - * @param LdapRecordException $e - * @param Closure $operation - * @return mixed - * * @throws LdapRecordException */ - protected function retryOnNextHost(LdapRecordException $e, Closure $operation) + protected function retryOnNextHost(LdapRecordException $e, Closure $operation): mixed { $this->attempted[$this->host] = Carbon::now(); diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/ConnectionManager.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/ConnectionManager.php index 0597f1f68..8bef8d150 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/ConnectionManager.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/ConnectionManager.php @@ -2,7 +2,6 @@ namespace LdapRecord; -use BadMethodCallException; use LdapRecord\Events\Dispatcher; use LdapRecord\Events\DispatcherInterface; use LdapRecord\Events\Logger; @@ -10,148 +9,83 @@ use Psr\Log\LoggerInterface; class ConnectionManager { - /** - * The logger instance. - * - * @var LoggerInterface|null - */ - protected $logger; - - /** - * The event dispatcher instance. - * - * @var DispatcherInterface|null - */ - protected $dispatcher; - /** * The added LDAP connections. * * @var Connection[] */ - protected $connections = []; + protected array $connections = []; /** * The name of the default connection. - * - * @var string */ - protected $default = 'default'; + protected string $default = 'default'; /** * The events to register listeners for during initialization. - * - * @var array */ - protected $listen = [ + protected array $listen = [ 'LdapRecord\Auth\Events\*', 'LdapRecord\Query\Events\*', 'LdapRecord\Models\Events\*', ]; /** - * The method calls to proxy for compatibility. - * - * To be removed in the next major version. - * - * @var array + * The logger instance. */ - protected $proxy = [ - 'reset' => 'flush', - 'addConnection' => 'add', - 'getConnection' => 'get', - 'allConnections' => 'all', - 'removeConnection' => 'remove', - 'getDefaultConnection' => 'getDefault', - 'setDefaultConnection' => 'setDefault', - 'getEventDispatcher' => 'dispatcher', - 'setEventDispatcher' => 'setDispatcher', - ]; + protected ?LoggerInterface $logger = null; + + /** + * The event dispatcher instance. + */ + protected ?DispatcherInterface $dispatcher = null; /** * Constructor. - * - * @return void */ - public function __construct() + public function __construct($dispatcher = new Dispatcher) { - $this->dispatcher = new Dispatcher(); - } - - /** - * Forward missing method calls onto the instance. - * - * @param string $method - * @param mixed $args - * @return mixed - */ - public function __call($method, $args) - { - $method = $this->proxy[$method] ?? $method; - - if (! method_exists($this, $method)) { - throw new BadMethodCallException(sprintf( - 'Call to undefined method %s::%s()', - static::class, - $method - )); - } - - return $this->{$method}(...$args); + $this->dispatcher = $dispatcher; } /** * Add a new connection. - * - * @param Connection $connection - * @param string|null $name - * @return $this */ - public function add(Connection $connection, $name = null) + public function addConnection(Connection $connection, ?string $name = null): void { $this->connections[$name ?? $this->default] = $connection; if ($this->dispatcher) { $connection->setDispatcher($this->dispatcher); } - - return $this; } /** - * Remove a connection. - * - * @param $name - * @return $this + * Remove a connection by its name. */ - public function remove($name) + public function removeConnection(string $name): void { unset($this->connections[$name]); - - return $this; } /** - * Get all of the connections. + * Get all the registered connections. * * @return Connection[] */ - public function all() + public function getConnections(): array { return $this->connections; } /** - * Get a connection by name or return the default. - * - * @param string|null $name - * @return Connection + * Get a connection by its name or return the default. * * @throws ContainerException If the given connection does not exist. */ - public function get($name = null) + public function getConnection(?string $name = null): Connection { - if ($this->exists($name = $name ?? $this->default)) { + if ($this->hasConnection($name = $name ?? $this->default)) { return $this->connections[$name]; } @@ -159,82 +93,61 @@ class ConnectionManager } /** - * Return the default connection. - * - * @return Connection + * Get the default connection. */ - public function getDefault() + public function getDefaultConnection(): Connection { - return $this->get($this->default); + 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. - * - * @return string */ - public function getDefaultConnectionName() + public function getDefaultConnectionName(): string { return $this->default; } /** - * Checks if the connection exists. - * - * @param string $name - * @return bool + * Determine if a connection exists. */ - public function exists($name) + public function hasConnection(string $name): bool { return array_key_exists($name, $this->connections); } - /** - * Set the default connection name. - * - * @param string $name - * @return $this - */ - public function setDefault($name = null) - { - $this->default = $name; - - return $this; - } - /** * Flush the manager of all instances and connections. - * - * @return $this */ - public function flush() + public function flush(): void { $this->logger = null; $this->connections = []; - $this->dispatcher = new Dispatcher(); - - return $this; + $this->dispatcher->forgetAll(); } /** * Get the logger instance. - * - * @return LoggerInterface|null */ - public function getLogger() + public function getLogger(): ?LoggerInterface { return $this->logger; } /** * Set the event logger to use. - * - * @param LoggerInterface $logger - * @return void */ - public function setLogger(LoggerInterface $logger) + public function setLogger(LoggerInterface $logger): void { $this->logger = $logger; @@ -243,10 +156,8 @@ class ConnectionManager /** * Initialize the event logger. - * - * @return void */ - public function initEventLogger() + protected function initEventLogger(): void { $logger = $this->newEventLogger(); @@ -261,51 +172,40 @@ class ConnectionManager /** * Make a new event logger instance. - * - * @return Logger */ - protected function newEventLogger() + protected function newEventLogger(): Logger { return new Logger($this->logger); } /** * Unset the logger instance. - * - * @return void */ - public function unsetLogger() + public function unsetLogger(): void { $this->logger = null; } /** * Get the event dispatcher. - * - * @return DispatcherInterface|null */ - public function dispatcher() + public function getDispatcher(): ?DispatcherInterface { return $this->dispatcher; } /** * Set the event dispatcher. - * - * @param DispatcherInterface $dispatcher - * @return void */ - public function setDispatcher(DispatcherInterface $dispatcher) + public function setDispatcher(DispatcherInterface $dispatcher): void { $this->dispatcher = $dispatcher; } /** * Unset the event dispatcher. - * - * @return void */ - public function unsetEventDispatcher() + 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 index 0c125593d..5a3bd7f4c 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Container.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Container.php @@ -2,122 +2,79 @@ namespace LdapRecord; -/** - * @method static $this reset() - * @method static Connection[] all() - * @method static Connection[] allConnections() - * @method static Connection getDefaultConnection() - * @method static Connection get(string|null $name = null) - * @method static Connection getConnection(string|null $name = null) - * @method static bool exists(string $name) - * @method static $this remove(string|null $name = null) - * @method static $this removeConnection(string|null $name = null) - * @method static $this setDefault(string|null $name = null) - * @method static $this setDefaultConnection(string|null $name = null) - * @method static $this add(Connection $connection, string|null $name = null) - * @method static $this addConnection(Connection $connection, string|null $name = null) - */ +/** @mixin ConnectionManager */ class Container { /** * The current container instance. - * - * @var Container */ - protected static $instance; + protected static Container $instance; /** * The connection manager instance. - * - * @var ConnectionManager */ - protected $manager; - - /** - * The methods to passthru, for compatibility. - * - * @var array - */ - protected $passthru = [ - 'reset', 'flush', - 'add', 'addConnection', - 'remove', 'removeConnection', - 'setDefault', 'setDefaultConnection', - ]; - - /** - * Forward missing static calls onto the current instance. - * - * @param string $method - * @param mixed $args - * @return mixed - */ - public static function __callStatic($method, $args) - { - return static::getInstance()->{$method}(...$args); - } + protected ConnectionManager $manager; /** * Get or set the current instance of the container. - * - * @return Container */ - public static function getInstance() + public static function getInstance(): static { return static::$instance ?? static::getNewInstance(); } /** * Set the container instance. - * - * @param Container|null $container - * @return Container|null */ - public static function setInstance(self $container = null) + public static function setInstance(?self $container = null): ?static { return static::$instance = $container; } /** * Set and get a new instance of the container. - * - * @return Container */ - public static function getNewInstance() + public static function getNewInstance(): static { - return static::setInstance(new static()); + return static::setInstance(new static); + } + + /** + * Forward missing static calls onto the current instance. + */ + public static function __callStatic(string $method, array $parameters): mixed + { + return static::getInstance()->{$method}(...$parameters); } /** * Constructor. - * - * @return void */ - public function __construct() + public function __construct(ConnectionManager $manager = new ConnectionManager) { - $this->manager = new ConnectionManager(); + $this->manager = $manager; } /** * Forward missing method calls onto the connection manager. - * - * @param string $method - * @param mixed $args - * @return mixed */ - public function __call($method, $args) + public function __call(string $method, array $parameters): mixed { - $value = $this->manager->{$method}(...$args); + return $this->manager->{$method}(...$parameters); + } - return in_array($method, $this->passthru) ? $this : $value; + /** + * Set the current container instance available globally. + */ + public function setAsGlobal(): void + { + static::setInstance($this); } /** * Get the connection manager. - * - * @return ConnectionManager */ - public function manager() + public function getConnectionManager(): ConnectionManager { return $this->manager; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/DetailedError.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/DetailedError.php index fff528c98..e9d9609dc 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/DetailedError.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/DetailedError.php @@ -4,67 +4,35 @@ namespace LdapRecord; class DetailedError { - /** - * The error code from ldap_errno. - * - * @var int|null - */ - protected $errorCode; - - /** - * The error message from ldap_error. - * - * @var string|null - */ - protected $errorMessage; - - /** - * The diagnostic message when retrieved after an ldap_error. - * - * @var string|null - */ - protected $diagnosticMessage; - /** * Constructor. - * - * @param int $errorCode - * @param string $errorMessage - * @param string $diagnosticMessage */ - public function __construct($errorCode, $errorMessage, $diagnosticMessage) - { - $this->errorCode = $errorCode; - $this->errorMessage = $errorMessage; - $this->diagnosticMessage = $diagnosticMessage; - } + public function __construct( + protected int $errorCode, + protected string $errorMessage, + protected ?string $diagnosticMessage + ) {} /** - * Returns the LDAP error code. - * - * @return int + * Get the LDAP error code. */ - public function getErrorCode() + public function getErrorCode(): int { return $this->errorCode; } /** - * Returns the LDAP error message. - * - * @return string + * Get the LDAP error message. */ - public function getErrorMessage() + public function getErrorMessage(): string { return $this->errorMessage; } /** - * Returns the LDAP diagnostic message. - * - * @return string + * Get the LDAP diagnostic message. */ - public function getDiagnosticMessage() + 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 index 61fc4a02e..bb6f675f0 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/DetectsErrors.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/DetectsErrors.php @@ -6,59 +6,43 @@ trait DetectsErrors { /** * Determine if the error was caused by a lost connection. - * - * @param string $error - * @return bool */ - protected function causedByLostConnection($error) + protected function causedByLostConnection(string $error): bool { return $this->errorContainsMessage($error, ["Can't contact LDAP server", 'Operations error']); } /** * Determine if the error was caused by lack of pagination support. - * - * @param string $error - * @return bool */ - protected function causedByPaginationSupport($error) + 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. - * - * @param $error - * @return bool */ - protected function causedBySizeLimit($error) + 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. - * - * @param string $error - * @return bool */ - protected function causedByNoSuchObject($error) + protected function causedByNoSuchObject(string $error): bool { return $this->errorContainsMessage($error, ['No such object']); } /** - * Determine if the error contains the any of the messages. - * - * @param string $error - * @param string|array $messages - * @return bool + * Determine if the error contains any of the messages. */ - protected function errorContainsMessage($error, $messages = []) + protected function errorContainsMessage(string $error, string|array $messages = []): bool { foreach ((array) $messages as $message) { - if (str_contains((string) $error, $message)) { + if (str_contains($error, $message)) { return true; } } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/EscapesValues.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/EscapesValues.php index ea53a2f93..088b50013 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/EscapesValues.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/EscapesValues.php @@ -8,13 +8,8 @@ trait EscapesValues { /** * Prepare a value to be escaped. - * - * @param string $value - * @param string $ignore - * @param int $flags - * @return EscapedValue */ - public function escape($value, $ignore = '', $flags = 0) + public function escape(mixed $value = null, string $ignore = '', int $flags = 0): EscapedValue { return new EscapedValue($value, $ignore, $flags); } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/ConnectionEvent.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/ConnectionEvent.php index d2d77e2fc..99a6e6f1c 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/ConnectionEvent.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/ConnectionEvent.php @@ -8,15 +8,11 @@ abstract class ConnectionEvent { /** * The LDAP connection. - * - * @var Connection */ - protected $connection; + protected Connection $connection; /** * Constructor. - * - * @param Connection $connection */ public function __construct(Connection $connection) { @@ -25,10 +21,8 @@ abstract class ConnectionEvent /** * Get the connection pertaining to the event. - * - * @return Connection */ - public function getConnection() + public function getConnection(): Connection { return $this->connection; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/Dispatcher.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/Dispatcher.php index 4fedbc2ef..304e15ccd 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/Dispatcher.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/Dispatcher.php @@ -2,7 +2,9 @@ namespace LdapRecord\Events; +use Closure; use LdapRecord\Support\Arr; +use LdapRecord\Support\Str; /** * Class Dispatcher. @@ -21,29 +23,23 @@ class Dispatcher implements DispatcherInterface { /** * The registered event listeners. - * - * @var array */ - protected $listeners = []; + protected array $listeners = []; /** * The wildcard listeners. - * - * @var array */ - protected $wildcards = []; + protected array $wildcards = []; /** * The cached wildcard listeners. - * - * @var array */ - protected $wildcardsCache = []; + protected array $wildcardsCache = []; /** - * @inheritdoc + * {@inheritdoc} */ - public function listen($events, $listener) + public function listen(string|array $events, mixed $listener): void { foreach ((array) $events as $event) { if (str_contains((string) $event, '*')) { @@ -56,12 +52,8 @@ class Dispatcher implements DispatcherInterface /** * Setup a wildcard listener callback. - * - * @param string $event - * @param mixed $listener - * @return void */ - protected function setupWildcardListen($event, $listener) + protected function setupWildcardListen(string $event, mixed $listener): void { $this->wildcards[$event][] = $this->makeListener($listener, true); @@ -69,33 +61,33 @@ class Dispatcher implements DispatcherInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function hasListeners($eventName) + public function hasListeners(string $event): bool { - return isset($this->listeners[$eventName]) || isset($this->wildcards[$eventName]); + return isset($this->listeners[$event]) || isset($this->wildcards[$event]); } /** - * @inheritdoc + * {@inheritdoc} */ - public function until($event, $payload = []) + public function until(string|object $event, mixed $payload = []): mixed { return $this->dispatch($event, $payload, true); } /** - * @inheritdoc + * {@inheritdoc} */ - public function fire($event, $payload = [], $halt = false) + public function fire(string|object $event, mixed $payload = [], bool $halt = false): void { - return $this->dispatch($event, $payload, $halt); + $this->dispatch($event, $payload, $halt); } /** - * @inheritdoc + * {@inheritdoc} */ - public function dispatch($event, $payload = [], $halt = false) + 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 @@ -132,12 +124,8 @@ class Dispatcher implements DispatcherInterface /** * Parse the given event and payload and prepare them for dispatching. - * - * @param mixed $event - * @param mixed $payload - * @return array */ - protected function parseEventAndPayload($event, $payload) + protected function parseEventAndPayload(string|object $event, mixed $payload): array { if (is_object($event)) { [$payload, $event] = [[$event], get_class($event)]; @@ -147,89 +135,42 @@ class Dispatcher implements DispatcherInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function getListeners($eventName) + public function getListeners(string $event): array { - $listeners = $this->listeners[$eventName] ?? []; + $listeners = $this->listeners[$event] ?? []; $listeners = array_merge( $listeners, - $this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName) + $this->wildcardsCache[$event] ?? $this->getWildcardListeners($event) ); - return class_exists($eventName, false) - ? $this->addInterfaceListeners($eventName, $listeners) + return class_exists($event, false) + ? $this->addInterfaceListeners($event, $listeners) : $listeners; } /** * Get the wildcard listeners for the event. - * - * @param string $eventName - * @return array */ - protected function getWildcardListeners($eventName) + protected function getWildcardListeners(string $event): array { $wildcards = []; foreach ($this->wildcards as $key => $listeners) { - if ($this->wildcardContainsEvent($key, $eventName)) { + if (Str::is($key, $event)) { $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; + return $this->wildcardsCache[$event] = $wildcards; } /** * 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 = []) + protected function addInterfaceListeners(string $eventName, array $listeners = []): array { foreach (class_implements($eventName) as $interface) { if (isset($this->listeners[$interface])) { @@ -244,12 +185,8 @@ class Dispatcher implements DispatcherInterface /** * Register an event listener with the dispatcher. - * - * @param \Closure|string $listener - * @param bool $wildcard - * @return \Closure */ - public function makeListener($listener, $wildcard = false) + public function makeListener(Closure|string $listener, bool $wildcard = false): Closure { if (is_string($listener)) { return $this->createClassListener($listener, $wildcard); @@ -266,12 +203,8 @@ class Dispatcher implements DispatcherInterface /** * Create a class based listener. - * - * @param string $listener - * @param bool $wildcard - * @return \Closure */ - protected function createClassListener($listener, $wildcard = false) + protected function createClassListener(string $listener, bool $wildcard = false): Closure { return function ($event, $payload) use ($listener, $wildcard) { if ($wildcard) { @@ -287,39 +220,53 @@ class Dispatcher implements DispatcherInterface /** * Create the class based event callable. - * - * @param string $listener - * @return callable */ - protected function createClassCallable($listener) + protected function createClassCallable(string $listener): callable { [$class, $method] = $this->parseListenerCallback($listener); - return [new $class(), $method]; + return [new $class, $method]; } /** * Parse the class listener into class and method. - * - * @param string $listener - * @return array */ - protected function parseListenerCallback($listener) + protected function parseListenerCallback(string $listener): array { - return str_contains((string) $listener, '@') + return str_contains($listener, '@') ? explode('@', $listener, 2) : [$listener, 'handle']; } /** - * @inheritdoc + * {@inheritdoc} */ - public function forget($event) + public function forget(string $event): void { - if (str_contains((string) $event, '*')) { + 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 index 590328f09..71de892c0 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/DispatcherInterface.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/DispatcherInterface.php @@ -6,63 +6,41 @@ interface DispatcherInterface { /** * Register an event listener with the dispatcher. - * - * @param string|array $events - * @param mixed $listener - * @return void */ - public function listen($events, $listener); + public function listen(string|array $events, mixed $listener): void; /** * Determine if a given event has listeners. - * - * @param string $eventName - * @return bool */ - public function hasListeners($eventName); + public function hasListeners(string $event): bool; /** * Fire an event until the first non-null response is returned. - * - * @param string|object $event - * @param mixed $payload - * @return array|null */ - public function until($event, $payload = []); + public function until(string|object $event, mixed $payload = []): mixed; /** * Fire an event and call the listeners. - * - * @param string|object $event - * @param mixed $payload - * @param bool $halt - * @return mixed */ - public function fire($event, $payload = [], $halt = false); + public function fire(string|object $event, mixed $payload = [], bool $halt = false): void; /** * Fire an event and call the listeners. - * - * @param string|object $event - * @param mixed $payload - * @param bool $halt - * @return array|null */ - public function dispatch($event, $payload = [], $halt = false); + public function dispatch(string|object $event, mixed $payload = [], $halt = false): mixed; /** - * Get all of the listeners for a given event name. - * - * @param string $eventName - * @return array + * Get all the listeners for a given event name. */ - public function getListeners($eventName); + public function getListeners(string $event): array; /** * Remove a set of listeners from the dispatcher. - * - * @param string $event - * @return void */ - public function forget($event); + public function forget(string $event): void; + + /** + * Remove all the listeners from the dispatcher. + */ + public function forgetAll(): void; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/Logger.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/Logger.php index b8a849169..5ac66f278 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/Logger.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/Logger.php @@ -13,28 +13,21 @@ class Logger { /** * The logger instance. - * - * @var LoggerInterface|null */ - protected $logger; + protected ?LoggerInterface $logger; /** * Constructor. - * - * @param LoggerInterface|null $logger */ - public function __construct(LoggerInterface $logger = null) + public function __construct(?LoggerInterface $logger = null) { $this->logger = $logger; } /** * Logs the given event. - * - * @param mixed $event - * @return void */ - public function log($event) + public function log($event): void { switch (true) { case $event instanceof AuthEvent: @@ -51,11 +44,8 @@ class Logger /** * Logs an authentication event. - * - * @param AuthEvent $event - * @return void */ - public function auth(AuthEvent $event) + public function auth(AuthEvent $event): void { if (isset($this->logger)) { $connection = $event->getConnection(); @@ -78,11 +68,8 @@ class Logger /** * Logs a model event. - * - * @param ModelEvent $event - * @return void */ - public function model(ModelEvent $event) + public function model(ModelEvent $event): void { if (isset($this->logger)) { $model = $event->getModel(); @@ -102,11 +89,8 @@ class Logger /** * Logs a query event. - * - * @param QueryEvent $event - * @return void */ - public function query(QueryEvent $event) + public function query(QueryEvent $event): void { if (isset($this->logger)) { $query = $event->getQuery(); @@ -127,12 +111,9 @@ class Logger } /** - * Returns the operational name of the given event. - * - * @param mixed $event - * @return string + * Get the operational name of the given event. */ - protected function getOperationName($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 index 7683d1f80..31f95cb77 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/NullDispatcher.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Events/NullDispatcher.php @@ -6,15 +6,11 @@ class NullDispatcher implements DispatcherInterface { /** * The underlying dispatcher instance. - * - * @var DispatcherInterface */ - protected $dispatcher; + protected DispatcherInterface $dispatcher; /** * Constructor. - * - * @param DispatcherInterface $dispatcher */ public function __construct(DispatcherInterface $dispatcher) { @@ -23,84 +19,62 @@ class NullDispatcher implements DispatcherInterface /** * Register an event listener with the dispatcher. - * - * @param string|array $events - * @param mixed $listener - * @return void */ - public function listen($events, $listener) + public function listen(string|array $events, mixed $listener): void { $this->dispatcher->listen($events, $listener); } /** * Determine if a given event has listeners. - * - * @param string $eventName - * @return bool */ - public function hasListeners($eventName) + public function hasListeners(string $event): bool { - return $this->dispatcher->hasListeners($eventName); + return $this->dispatcher->hasListeners($event); } /** * Fire an event until the first non-null response is returned. - * - * @param string|object $event - * @param mixed $payload - * @return null */ - public function until($event, $payload = []) + public function until(string|object $event, mixed $payload = []): ?array { return null; } /** * Fire an event and call the listeners. - * - * @param string|object $event - * @param mixed $payload - * @param bool $halt - * @return null */ - public function fire($event, $payload = [], $halt = false) - { - return null; - } + public function fire(string|object $event, mixed $payload = [], bool $halt = false): void {} /** * Fire an event and call the listeners. - * - * @param string|object $event - * @param mixed $payload - * @param bool $halt - * @return null */ - public function dispatch($event, $payload = [], $halt = false) + public function dispatch(string|object $event, mixed $payload = [], $halt = false): mixed { return null; } /** - * Get all of the listeners for a given event name. - * - * @param string $eventName - * @return array + * Get all the listeners for a given event name. */ - public function getListeners($eventName) + public function getListeners(string $event): array { - return $this->dispatcher->getListeners($eventName); + return $this->dispatcher->getListeners($event); } /** * Remove a set of listeners from the dispatcher. - * - * @param string $event - * @return void */ - public function forget($event) + 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/ConstraintViolationException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Exceptions/ConstraintViolationException.php index 641843a9b..b5d30a739 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Exceptions/ConstraintViolationException.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Exceptions/ConstraintViolationException.php @@ -11,25 +11,17 @@ class ConstraintViolationException extends LdapRecordException /** * Determine if the exception was generated due to the password policy. - * - * @return bool */ - public function causedByPasswordPolicy() + public function causedByPasswordPolicy(): bool { - return isset($this->detailedError) - ? $this->errorContainsMessage($this->detailedError->getDiagnosticMessage(), '0000052D') - : false; + return isset($this->detailedError) && $this->errorContainsMessage($this->detailedError->getDiagnosticMessage(), '0000052D'); } /** * Determine if the exception was generated due to an incorrect password. - * - * @return bool */ - public function causedByIncorrectPassword() + public function causedByIncorrectPassword(): bool { - return isset($this->detailedError) - ? $this->errorContainsMessage($this->detailedError->getDiagnosticMessage(), '00000056') - : false; + return isset($this->detailedError) && $this->errorContainsMessage($this->detailedError->getDiagnosticMessage(), '00000056'); } } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/HandlesConnection.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/HandlesConnection.php index 4a2e27598..e47904d8b 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/HandlesConnection.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/HandlesConnection.php @@ -5,88 +5,100 @@ namespace LdapRecord; use Closure; use ErrorException; use Exception; +use LDAP\Connection; +/** @mixin Ldap */ trait HandlesConnection { /** * The LDAP host that is currently connected. - * - * @var string|null */ - protected $host; + protected ?string $host = null; + + /** + * The LDAP protocol to use (ldap:// or ldaps://). + */ + protected ?string $protocol = null; /** * The LDAP connection resource. * - * @var resource|null + * @var Connection */ - protected $connection; + protected mixed $connection = null; /** - * The bound status of the connection. - * - * @var bool + * Whether the connection is bound. */ - protected $bound = false; + protected bool $bound = false; + + /** + * Whether the connection is secured over TLS or SSL. + */ + protected bool $secure = false; /** * Whether the connection must be bound over SSL. - * - * @var bool */ - protected $useSSL = false; + protected bool $useSSL = false; /** * Whether the connection must be bound over TLS. - * - * @var bool */ - protected $useTLS = false; + protected bool $useTLS = false; /** - * @inheritdoc + * {@inheritdoc} */ - public function isUsingSSL() + public function isUsingSSL(): bool { return $this->useSSL; } /** - * @inheritdoc + * {@inheritdoc} */ - public function isUsingTLS() + public function isUsingTLS(): bool { return $this->useTLS; } /** - * @inheritdoc + * {@inheritdoc} */ - public function isBound() + public function isBound(): bool { return $this->bound; } /** - * @inheritdoc + * {@inheritdoc} */ - public function isConnected() + public function isSecure(): bool + { + return $this->secure; + } + + /** + * {@inheritdoc} + */ + public function isConnected(): bool { return ! is_null($this->connection); } /** - * @inheritdoc + * {@inheritdoc} */ - public function canChangePasswords() + public function canChangePasswords(): bool { return $this->isUsingSSL() || $this->isUsingTLS(); } /** - * @inheritdoc + * {@inheritdoc} */ - public function ssl($enabled = true) + public function ssl(bool $enabled = true): static { $this->useSSL = $enabled; @@ -94,9 +106,9 @@ trait HandlesConnection } /** - * @inheritdoc + * {@inheritdoc} */ - public function tls($enabled = true) + public function tls(bool $enabled = true): static { $this->useTLS = $enabled; @@ -104,9 +116,9 @@ trait HandlesConnection } /** - * @inheritdoc + * {@inheritdoc} */ - public function setOptions(array $options = []) + public function setOptions(array $options = []): void { foreach ($options as $option => $value) { $this->setOption($option, $value); @@ -114,46 +126,57 @@ trait HandlesConnection } /** - * @inheritdoc + * {@inheritdoc} */ - public function getHost() + public function getHost(): ?string { return $this->host; } /** - * @inheritdoc + * {@inheritdoc} */ - public function getConnection() + public function getConnection(): ?Connection { return $this->connection; } /** - * @inheritdoc + * {@inheritdoc} */ - public function getProtocol() + public function getProtocol(): string { - return $this->isUsingSSL() ? LdapInterface::PROTOCOL_SSL : LdapInterface::PROTOCOL; + return $this->protocol ?: ( + $this->isUsingSSL() + ? LdapInterface::PROTOCOL_SSL + : LdapInterface::PROTOCOL + ); } /** - * @inheritdoc + * {@inheritdoc} */ - public function getExtendedError() + 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. * - * @param Closure $operation - * @return mixed - * * @throws LdapRecordException */ - protected function executeFailableOperation(Closure $operation) + 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. @@ -172,8 +195,8 @@ trait HandlesConnection } // If the failed query operation was a based on a query being executed - // -- such as a search, read, or listing, then we can safely return - // the failed response here and prevent throwning an exception. + // -- 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; } @@ -188,46 +211,24 @@ trait HandlesConnection /** * Determine if the failed operation should be bypassed. - * - * @param string $method - * @return bool */ - protected function shouldBypassFailure($method) + protected function shouldBypassFailure(string $method): bool { - return in_array($method, ['search', 'read', 'listing']); + return in_array($method, ['search', 'read', 'list']); } /** * Determine if the error should be bypassed. - * - * @param string $error - * @return bool */ - protected function shouldBypassError($error) + protected function shouldBypassError(string $error): bool { return $this->causedByPaginationSupport($error) || $this->causedBySizeLimit($error) || $this->causedByNoSuchObject($error); } - /** - * Determine if the current PHP version supports server controls. - * - * @deprecated since v2.5.0 - * - * @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 $port - * @return string */ - protected function makeConnectionUris($hosts, $port) + 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 @@ -244,15 +245,9 @@ trait HandlesConnection /** * Assemble the host URI strings. - * - * @param array|string $hosts - * @param string $port - * @return array */ - protected function assembleHostUris($hosts, $port) + protected function assembleHostUris(array|string $hosts, string|int $port): array { - return array_map(function ($host) use ($port) { - return "{$this->getProtocol()}{$host}:{$port}"; - }, (array) $hosts); + 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 index 76c372040..7f2be60b6 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Ldap.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Ldap.php @@ -6,15 +6,16 @@ use LDAP\Connection as RawLdapConnection; class Ldap implements LdapInterface { - use HandlesConnection, DetectsErrors; + use DetectsErrors; + use HandlesConnection; /** - * @inheritdoc + * {@inheritdoc} */ - public function getEntries($searchResults) + public function getEntries(mixed $result): array { - return $this->executeFailableOperation(function () use ($searchResults) { - return ldap_get_entries($this->connection, $searchResults); + return $this->executeFailableOperation(function () use ($result) { + return ldap_get_entries($this->connection, $result); }); } @@ -23,13 +24,12 @@ class Ldap implements LdapInterface * * @see http://php.net/manual/en/function.ldap-first-entry.php * - * @param resource $searchResults - * @return resource + * @param \Ldap\Result $result */ - public function getFirstEntry($searchResults) + public function getFirstEntry(mixed $result): mixed { - return $this->executeFailableOperation(function () use ($searchResults) { - return ldap_first_entry($this->connection, $searchResults); + return $this->executeFailableOperation(function () use ($result) { + return ldap_first_entry($this->connection, $result); }); } @@ -38,10 +38,9 @@ class Ldap implements LdapInterface * * @see http://php.net/manual/en/function.ldap-next-entry.php * - * @param resource $entry - * @return resource + * @param \Ldap\ResultEntry $entry */ - public function getNextEntry($entry) + public function getNextEntry(mixed $entry): mixed { return $this->executeFailableOperation(function () use ($entry) { return ldap_next_entry($this->connection, $entry); @@ -53,10 +52,9 @@ class Ldap implements LdapInterface * * @see http://php.net/manual/en/function.ldap-get-attributes.php * - * @param resource $entry - * @return array|false + * @param \Ldap\ResultEntry $entry */ - public function getAttributes($entry) + public function getAttributes(mixed $entry): array|false { return $this->executeFailableOperation(function () use ($entry) { return ldap_get_attributes($this->connection, $entry); @@ -64,41 +62,29 @@ class Ldap implements LdapInterface } /** - * Returns the number of entries from a search result. - * - * @see http://php.net/manual/en/function.ldap-count-entries.php - * - * @param resource $searchResults - * @return int + * {@inheritDoc} */ - public function countEntries($searchResults) + public function countEntries(mixed $result): int { - return $this->executeFailableOperation(function () use ($searchResults) { - return ldap_count_entries($this->connection, $searchResults); + return $this->executeFailableOperation(function () use ($result) { + return ldap_count_entries($this->connection, $result); }); } /** - * Compare value of attribute found in entry specified with DN. - * - * @see http://php.net/manual/en/function.ldap-compare.php - * - * @param string $dn - * @param string $attribute - * @param string $value - * @return mixed + * {@inheritDoc} */ - public function compare($dn, $attribute, $value) + public function compare(string $dn, string $attribute, string $value, ?array $controls = null): bool|int { - return $this->executeFailableOperation(function () use ($dn, $attribute, $value) { - return ldap_compare($this->connection, $dn, $attribute, $value); + return $this->executeFailableOperation(function () use ($dn, $attribute, $value, $controls) { + return ldap_compare($this->connection, $dn, $attribute, $value, $controls); }); } /** - * @inheritdoc + * {@inheritdoc} */ - public function getLastError() + public function getLastError(): ?string { if (! $this->connection) { return null; @@ -108,9 +94,9 @@ class Ldap implements LdapInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function getDetailedError() + public function getDetailedError(): ?DetailedError { if (! $number = $this->errNo()) { return null; @@ -122,15 +108,9 @@ class Ldap implements LdapInterface } /** - * Get all binary values from the specified result entry. - * - * @see http://php.net/manual/en/function.ldap-get-values-len.php - * - * @param $entry - * @param $attribute - * @return array + * {@inheritDoc} */ - public function getValuesLen($entry, $attribute) + 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); @@ -138,17 +118,17 @@ class Ldap implements LdapInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function setOption($option, $value) + public function setOption(int $option, mixed $value): bool { return ldap_set_option($this->connection, $option, $value); } /** - * @inheritdoc + * {@inheritdoc} */ - public function getOption($option, &$value = null) + public function getOption(int $option, mixed &$value = null): mixed { ldap_get_option($this->connection, $option, $value); @@ -159,59 +139,63 @@ class Ldap implements LdapInterface * Set a callback function to do re-binds on referral chasing. * * @see http://php.net/manual/en/function.ldap-set-rebind-proc.php - * - * @param callable $callback - * @return bool */ - public function setRebindCallback(callable $callback) + public function setRebindCallback(callable $callback): bool { return ldap_set_rebind_proc($this->connection, $callback); } /** - * @inheritdoc + * {@inheritdoc} */ - public function startTLS() + public function startTLS(): bool { - return $this->executeFailableOperation(function () { + return $this->secure = $this->executeFailableOperation(function () { return ldap_start_tls($this->connection); }); } /** - * @inheritdoc + * {@inheritdoc} */ - public function connect($hosts = [], $port = 389) + 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->executeFailableOperation(function () { + $this->connection = $this->executeFailableOperation(function () { return ldap_connect($this->host); }); + + return $this->connection instanceof RawLdapConnection; } /** - * @inheritdoc + * {@inheritdoc} */ - public function close() + public function close(): bool { - $result = (is_resource($this->connection) || $this->connection instanceof RawLdapConnection) - ? @ldap_close($this->connection) - : false; + $result = false; + + if ($this->connection instanceof RawLdapConnection) { + $result = @ldap_close($this->connection); + } - $this->connection = null; $this->bound = false; + $this->secure = false; + $this->host = null; + $this->protocol = null; + $this->connection = null; return $result; } /** - * @inheritdoc + * {@inheritdoc} */ - public function search($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = LDAP_DEREF_NEVER, $serverControls = []) + 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, @@ -221,18 +205,16 @@ class Ldap implements LdapInterface $size, $time, $deref, - $serverControls + $controls ) { - return empty($serverControls) - ? ldap_search($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time, $deref) - : ldap_search($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time, $deref, $serverControls); + return ldap_search($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time, $deref, $controls); }); } /** - * @inheritdoc + * {@inheritdoc} */ - public function listing($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = LDAP_DEREF_NEVER, $serverControls = []) + 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, @@ -242,18 +224,16 @@ class Ldap implements LdapInterface $size, $time, $deref, - $serverControls + $controls ) { - return empty($serverControls) - ? ldap_list($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time, $deref) - : ldap_list($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time, $deref, $serverControls); + return ldap_list($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time, $deref, $controls); }); } /** - * @inheritdoc + * {@inheritdoc} */ - public function read($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = LDAP_DEREF_NEVER, $serverControls = []) + 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, @@ -263,47 +243,78 @@ class Ldap implements LdapInterface $size, $time, $deref, - $serverControls + $controls ) { - return empty($serverControls) - ? ldap_read($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time, $deref) - : ldap_read($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time, $deref, $serverControls); + return ldap_read($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time, $deref, $controls); }); } /** - * @inheritdoc + * {@inheritdoc} */ - public function parseResult($result, &$errorCode, &$dn, &$errorMessage, &$referrals, &$serverControls = []) + public function parseResult(mixed $result, int &$errorCode = 0, ?string &$dn = null, ?string &$errorMessage = null, ?array &$referrals = null, ?array &$controls = null): LdapResultResponse|false { - return $this->executeFailableOperation(function () use ( - $result, - &$errorCode, - &$dn, - &$errorMessage, - &$referrals, - &$serverControls - ) { - return empty($serverControls) - ? ldap_parse_result($this->connection, $result, $errorCode, $dn, $errorMessage, $referrals) - : ldap_parse_result($this->connection, $result, $errorCode, $dn, $errorMessage, $referrals, $serverControls); - }); + if (ldap_parse_result($this->connection, $result, $errorCode, $dn, $errorMessage, $referrals, $controls)) { + return new LdapResultResponse( + $errorCode, + $dn, + $errorMessage, + $referrals, + $controls + ); + } + + return false; } /** - * @inheritdoc + * {@inheritdoc} */ - public function bind($username, $password) + public function bind(?string $dn = null, ?string $password = null, ?array $controls = null): LdapResultResponse { - return $this->bound = $this->executeFailableOperation(function () use ($username, $password) { - return ldap_bind($this->connection, $username, $password ? html_entity_decode($password) : null); + /** @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 + * {@inheritdoc} */ - public function add($dn, array $entry) + public function add(string $dn, array $entry): bool { return $this->executeFailableOperation(function () use ($dn, $entry) { return ldap_add($this->connection, $dn, $entry); @@ -311,9 +322,9 @@ class Ldap implements LdapInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function delete($dn) + public function delete(string $dn): bool { return $this->executeFailableOperation(function () use ($dn) { return ldap_delete($this->connection, $dn); @@ -321,9 +332,9 @@ class Ldap implements LdapInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function rename($dn, $newRdn, $newParent, $deleteOldRdn = false) + public function rename(string $dn, string $newRdn, string $newParent, bool $deleteOldRdn = false): bool { return $this->executeFailableOperation(function () use ( $dn, @@ -336,9 +347,9 @@ class Ldap implements LdapInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function modify($dn, array $entry) + public function modify(string $dn, array $entry): bool { return $this->executeFailableOperation(function () use ($dn, $entry) { return ldap_modify($this->connection, $dn, $entry); @@ -346,9 +357,9 @@ class Ldap implements LdapInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function modifyBatch($dn, array $values) + public function modifyBatch(string $dn, array $values): bool { return $this->executeFailableOperation(function () use ($dn, $values) { return ldap_modify_batch($this->connection, $dn, $values); @@ -356,9 +367,9 @@ class Ldap implements LdapInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function modAdd($dn, array $entry) + public function modAdd(string $dn, array $entry): bool { return $this->executeFailableOperation(function () use ($dn, $entry) { return ldap_mod_add($this->connection, $dn, $entry); @@ -366,9 +377,9 @@ class Ldap implements LdapInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function modReplace($dn, array $entry) + public function modReplace(string $dn, array $entry): bool { return $this->executeFailableOperation(function () use ($dn, $entry) { return ldap_mod_replace($this->connection, $dn, $entry); @@ -376,9 +387,9 @@ class Ldap implements LdapInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function modDelete($dn, array $entry) + public function modDelete(string $dn, array $entry): bool { return $this->executeFailableOperation(function () use ($dn, $entry) { return ldap_mod_del($this->connection, $dn, $entry); @@ -386,88 +397,63 @@ class Ldap implements LdapInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function controlPagedResult($pageSize = 1000, $isCritical = false, $cookie = '') - { - return $this->executeFailableOperation(function () use ($pageSize, $isCritical, $cookie) { - return ldap_control_paged_result($this->connection, $pageSize, $isCritical, $cookie); - }); - } - - /** - * @inheritdoc - */ - public function controlPagedResultResponse($result, &$cookie, &$estimated = null) - { - return $this->executeFailableOperation(function () use ($result, &$cookie, &$estimated) { - return ldap_control_paged_result_response($this->connection, $result, $cookie, $estimated); - }); - } - - /** - * @inheritdoc - */ - public function freeResult($result) + public function freeResult(mixed $result): bool { return ldap_free_result($result); } /** - * @inheritdoc + * {@inheritdoc} */ - public function errNo() + public function errNo(): ?int { return $this->connection ? ldap_errno($this->connection) : null; } /** - * @inheritdoc + * {@inheritdoc} */ - public function err2Str($number) + public function err2Str(int $number): string { return ldap_err2str($number); } /** - * Returns the extended error hex code of the last command. - * - * @return string|null + * Get the extended error hex code of the last command. */ - public function getExtendedErrorHex() + public function getExtendedErrorHex(): ?string { if (preg_match("/(?<=data\s).*?(?=,)/", $this->getExtendedError(), $code)) { return $code[0]; } + + return null; } /** - * Returns the extended error code of the last command. - * - * @return bool|string + * Get the extended error code of the last command. */ - public function getExtendedErrorCode() + public function getExtendedErrorCode(): string|false { return $this->extractDiagnosticCode($this->getExtendedError()); } /** * Extract the diagnostic code from the message. - * - * @param string $message - * @return string|bool */ - public function extractDiagnosticCode($message) + public function extractDiagnosticCode(string $message): string|false { preg_match('/^([\da-fA-F]+):/', $message, $matches); - return isset($matches[1]) ? $matches[1] : false; + return $matches[1] ?? false; } /** - * @inheritdoc + * {@inheritdoc} */ - public function getDiagnosticMessage() + public function getDiagnosticMessage(): ?string { $this->getOption(LDAP_OPT_ERROR_STRING, $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 index fcff57f48..735f858ca 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapInterface.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapInterface.php @@ -2,15 +2,15 @@ namespace LdapRecord; +use LDAP\Connection; + +/** + * @see https://ldap.com/ldap-oid-reference-guide + * @see http://msdn.microsoft.com/en-us/library/cc223359.aspx + * @see https://help.univention.com/t/openldap-debug-level/19301 + */ interface LdapInterface { - /** - * The SSL LDAP protocol string. - * - * @var string - */ - public const PROTOCOL_SSL = 'ldaps://'; - /** * The standard LDAP protocol string. * @@ -19,136 +19,370 @@ interface LdapInterface public const PROTOCOL = 'ldap://'; /** - * The LDAP SSL port number. + * The SSL LDAP protocol string. * * @var string */ - public const PORT_SSL = 636; + public const PROTOCOL_SSL = 'ldaps://'; /** * The standard LDAP port number. * - * @var string + * @var int */ public const PORT = 389; /** - * Various useful server control OID's. + * The LDAP SSL port number. * - * @see https://ldap.com/ldap-oid-reference-guide/ - * @see http://msdn.microsoft.com/en-us/library/cc223359.aspx + * @var int + */ + public const PORT_SSL = 636; + + /** + * Print entry and exit from routines. + * + * @var int + */ + public const DEBUG_TRACE = 1; + + /** + * Print packet activity. + * + * @var int + */ + public const DEBUG_PACKETS = 2; + + /** + * Print data arguments from requests. + * + * @var int + */ + public const DEBUG_ARGS = 4; + + /** + * Print connection activity. + * + * @var int + */ + public const DEBUG_CONNS = 8; + + /** + * Print encoding and decoding of data. + * + * @var int + */ + public const DEBUG_BER = 16; + + /** + * Print search filters. + * + * @var int + */ + public const DEBUG_FILTER = 32; + + /** + * Print configuration file processing. + * + * @var int + */ + public const DEBUG_CONFIG = 64; + + /** + * Print Access Control List activities. + * + * @var int + */ + public const DEBUG_ACL = 128; + + /** + * Print operational statistics. + * + * @var int + */ + public const DEBUG_STATS = 256; + + /** + * Print more detailed statistics. + * + * @var int + */ + public const DEBUG_STATS2 = 512; + + /** + * Print communication with shell backends. + * + * @var int + */ + public const DEBUG_SHELL = 1024; + + /** + * Print entry parsing. + * + * @var int + */ + public const DEBUG_PARSE = 2048; + + /** + * Print LDAPSync replication. + * + * @var int + */ + public const DEBUG_SYNC = 16384; + + /** + * Print referral activities. + * + * @var int + */ + public const DEBUG_REFERRAL = 32768; + + /** + * Print error conditions. + * + * @var int + */ + public const DEBUG_ERROR = 32768; + + /** + * Print all levels of debug. + * + * @var int + */ + public const DEBUG_ANY = 65535; + + /** + * OID for StartTLS extended operation. Signals the server to initiate a TLS connection. + * + * @var string */ public const OID_SERVER_START_TLS = '1.3.6.1.4.1.1466.20037'; + + /** + * OID for Paged Results Control. Used to retrieve search results in pages. + * + * @var string + */ public const OID_SERVER_PAGED_RESULTS = '1.2.840.113556.1.4.319'; + + /** + * OID for Show Deleted Control. Includes deleted entries in the search results. + * + * @var string + */ public const OID_SERVER_SHOW_DELETED = '1.2.840.113556.1.4.417'; + + /** + * OID for Server Side Sort Control. Requests the server to sort the search results. + * + * @var string + */ public const OID_SERVER_SORT = '1.2.840.113556.1.4.473'; + + /** + * OID for Cross-Domain Move Target Control. Used in cross-domain move operations. + * + * @var string + */ public const OID_SERVER_CROSSDOM_MOVE_TARGET = '1.2.840.113556.1.4.521'; + + /** + * OID for LDAP Notification Control. Used to register for change notifications. + * + * @var string + */ public const OID_SERVER_NOTIFICATION = '1.2.840.113556.1.4.528'; + + /** + * OID for Extended DN Control. Requests extended DN information in search results. + * + * @var string + */ public const OID_SERVER_EXTENDED_DN = '1.2.840.113556.1.4.529'; + + /** + * OID for Lazy Commit Control. Delays the actual commit of changes until requested. + * + * @var string + */ public const OID_SERVER_LAZY_COMMIT = '1.2.840.113556.1.4.619'; + + /** + * OID for Security Descriptor Flags Control. Used to manipulate security descriptor flags. + * + * @var string + */ public const OID_SERVER_SD_FLAGS = '1.2.840.113556.1.4.801'; + + /** + * OID for Tree Delete Control. Enables the deletion of an entire subtree. + * + * @var string + */ public const OID_SERVER_TREE_DELETE = '1.2.840.113556.1.4.805'; + + /** + * OID for DirSync Control. Used for directory synchronization operations. + * + * @var string + */ public const OID_SERVER_DIRSYNC = '1.2.840.113556.1.4.841'; + + /** + * OID for Verify Name Control. Allows verification of an entry without retrieving attributes. + * + * @var string + */ public const OID_SERVER_VERIFY_NAME = '1.2.840.113556.1.4.1338'; + + /** + * OID for Domain Scope Control. Limits a search to the current domain. + * + * @var string + */ public const OID_SERVER_DOMAIN_SCOPE = '1.2.840.113556.1.4.1339'; + + /** + * OID for Search Options Control. Used to set various search options. + * + * @var string + */ public const OID_SERVER_SEARCH_OPTIONS = '1.2.840.113556.1.4.1340'; + + /** + * OID for Permissive Modify Control. Allows modifications even if some attributes are missing. + * + * @var string + */ public const OID_SERVER_PERMISSIVE_MODIFY = '1.2.840.113556.1.4.1413'; + + /** + * OID for Authentication Service Queries (ASQ) Control. Used to perform ASQ operations. + * + * @var string + */ public const OID_SERVER_ASQ = '1.2.840.113556.1.4.1504'; + + /** + * OID for Fast Bind Control. Optimizes the bind process for faster authentication. + * + * @var string + */ public const OID_SERVER_FAST_BIND = '1.2.840.113556.1.4.1781'; + + /** + * OID for Virtual List View (VLV) Request Control. Used to request a specific range of entries. + * + * @var string + */ public const OID_SERVER_CONTROL_VLVREQUEST = '2.16.840.1.113730.3.4.9'; /** - * Query OID's. - * - * @see https://ldapwiki.com/wiki/LDAP_MATCHING_RULE_IN_CHAIN + * OID for the 'matchingRuleInChain' matching rule. Used for substring searches in multi-valued attributes. */ public const OID_MATCHING_RULE_IN_CHAIN = '1.2.840.113556.1.4.1941'; /** * Set the current connection to use SSL. - * - * @param bool $enabled - * @return $this */ - public function ssl(); + public function ssl(): static; /** * Determine if the current connection instance is using SSL. - * - * @return bool */ - public function isUsingSSL(); + public function isUsingSSL(): bool; /** * Set the current connection to use TLS. - * - * @param bool $enabled - * @return $this */ - public function tls(); + public function tls(): static; /** * Determine if the current connection instance is using TLS. - * - * @return bool */ - public function isUsingTLS(); + public function isUsingTLS(): bool; /** * Determine if the connection is bound. - * - * @return bool */ - public function isBound(); + public function isBound(): bool; + + /** + * Determine if the connection is secure over TLS or SSL. + */ + public function isSecure(): bool; /** * Determine if the connection has been created. - * - * @return bool */ - public function isConnected(); + public function isConnected(): bool; /** * Determine the connection is able to modify passwords. - * - * @return bool */ - public function canChangePasswords(); + public function canChangePasswords(): bool; /** - * Returns the full LDAP host URL. + * Get the full LDAP host URL. * * Ex: ldap://192.168.1.1:386 - * - * @return string|null */ - public function getHost(); + public function getHost(): ?string; /** - * Get the underlying connection resource. - * - * @return resource|null + * Get the underlying raw LDAP connection. */ - public function getConnection(); + public function getConnection(): ?Connection; /** * Retrieve the entries from a search result. * * @see http://php.net/manual/en/function.ldap-get-entries.php * - * @param resource $searchResults - * @return array + * @param \LDAP\Result $result */ - public function getEntries($searchResults); + public function getEntries(mixed $result): array; + + /** + * Get the entry identifier for first entry in the result. + * + * @see https://www.php.net/manual/en/function.ldap-first-entry.php + * + * @param \LDAP\Result $result + */ + public function getFirstEntry(mixed $result): mixed; + + /** + * Retrieve the next result entry. + * + * @see https://www.php.net/manual/en/function.ldap-next-entry.php + * + * @param \LDAP\Result $entry + */ + public function getNextEntry(mixed $entry): mixed; + + /** + * Reads attributes and values from an entry in the search result. + * + * @see https://www.php.net/manual/en/function.ldap-get-attributes.php + * + * @param \LDAP\Result $entry + */ + public function getAttributes(mixed $entry): array|false; + + /** + * Reads all the values of the attribute in the entry in the result. + * + * @param \LDAP\Result $entry + */ + public function getValuesLen(mixed $entry, string $attribute): array|false; /** * Retrieve the last error on the current connection. * * @see http://php.net/manual/en/function.ldap-error.php - * - * @return string|null */ - public function getLastError(); + public function getLastError(): ?string; /** * Return detailed information about an error. @@ -156,62 +390,64 @@ interface LdapInterface * Returns null when there was a successful last request. * * Returns DetailedError when there was an error. - * - * @return DetailedError|null */ - public function getDetailedError(); + public function getDetailedError(): ?DetailedError; + + /** + * Count the number of entries in a search. + * + * @see https://www.php.net/manual/en/function.ldap-count-entries.php + * + * @param \LDAP\Result $result + */ + public function countEntries(mixed $result): int; + + /** + * Compare value of attribute found in entry specified with DN. + */ + public function compare(string $dn, string $attribute, string $value, ?array $controls = null): bool|int; /** * Set an option on the current connection. * * @see http://php.net/manual/en/function.ldap-set-option.php - * - * @param int $option - * @param mixed $value - * @return bool */ - public function setOption($option, $value); + public function setOption(int $option, mixed $value): bool; /** - * Set options on the current connection. - * - * @param array $options - * @return void + * Set multiple options on the current connection. */ - public function setOptions(array $options = []); + public function setOptions(array $options = []): void; + + /** + * Set a callback function to do re-binds on referral chasing. + * + * @see https://www.php.net/manual/en/function.ldap-set-rebind-proc.php + */ + public function setRebindCallback(callable $callback): bool; /** * Get the value for the LDAP option. * * @see https://www.php.net/manual/en/function.ldap-get-option.php - * - * @param int $option - * @param mixed $value - * @return mixed */ - public function getOption($option, &$value = null); + public function getOption(int $option, mixed &$value = null): mixed; /** * Starts a connection using TLS. * * @see http://php.net/manual/en/function.ldap-start-tls.php * - * @return bool - * * @throws LdapRecordException */ - public function startTLS(); + public function startTLS(): bool; /** * Connects to the specified hostname using the specified port. * * @see http://php.net/manual/en/function.ldap-start-tls.php - * - * @param string|array $hosts - * @param int $port - * @return resource|false */ - public function connect($hosts = [], $port = 389); + public function connect(string|array $hosts = [], int $port = 389, ?string $protocol = null): bool; /** * Closes the current connection. @@ -219,275 +455,176 @@ interface LdapInterface * Returns false if no connection is present. * * @see http://php.net/manual/en/function.ldap-close.php - * - * @return bool */ - public function close(); + public function close(): bool; /** * Performs a search on the current connection. * * @see http://php.net/manual/en/function.ldap-search.php * - * @param string $dn - * @param string $filter - * @param array $fields - * @param bool $onlyAttributes - * @param int $size - * @param int $time - * @param int $deref - * @param array $serverControls - * @return resource + * @return \LDAP\Result */ - public function search($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = LDAP_DEREF_NEVER, $serverControls = []); + 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; /** * Performs a single level search on the current connection. * * @see http://php.net/manual/en/function.ldap-list.php * - * @param string $dn - * @param string $filter - * @param array $fields - * @param bool $onlyAttributes - * @param int $size - * @param int $time - * @param int $deref - * @param array $serverControls - * @return resource + * @return \LDAP\Result */ - public function listing($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = LDAP_DEREF_NEVER, $serverControls = []); + 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; /** * Reads an entry on the current connection. * * @see http://php.net/manual/en/function.ldap-read.php * - * @param string $dn - * @param string $filter - * @param array $fields - * @param bool $onlyAttributes - * @param int $size - * @param int $time - * @param int $deref - * @param array $serverControls - * @return resource + * @return \LDAP\Result */ - public function read($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = LDAP_DEREF_NEVER, $serverControls = []); + 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; /** * Extract information from an LDAP result. * * @see 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 + * @param \LDAP\Result $result */ - public function parseResult($result, &$errorCode, &$dn, &$errorMessage, &$referrals, &$serverControls = []); + public function parseResult(mixed $result, int &$errorCode = 0, ?string &$dn = null, ?string &$errorMessage = null, ?array &$referrals = null, ?array &$controls = null): LdapResultResponse|false; /** - * Binds to the current connection using the specified username and password. - * If sasl is true, the current connection is bound using SASL. + * Bind to the LDAP directory. * * @see http://php.net/manual/en/function.ldap-bind.php * - * @param string $username - * @param string $password - * @return bool - * * @throws LdapRecordException */ - public function bind($username, $password); + public function bind(?string $dn = null, ?string $password = null, ?array $controls = null): LdapResultResponse; + + /** + * Bind to the LDAP directory using SASL. + * + * SASL options: + * - mech: Mechanism (Defaults: null) + * - realm: Realm (Defaults: null) + * - authc_id: Verification Identity (Defaults: null) + * - authz_id: Authorization Identity (Defaults: null) + * - props: Options for Authorization Identity (Defaults: null) + * + * @see https://php.net/manual/en/function.ldap-sasl-bind.php + * @see https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml + */ + public function saslBind(?string $dn = null, ?string $password = null, array $options = []): bool; /** * Adds an entry to the current connection. * * @see http://php.net/manual/en/function.ldap-add.php * - * @param string $dn - * @param array $entry - * @return bool - * * @throws LdapRecordException */ - public function add($dn, array $entry); + public function add(string $dn, array $entry): bool; /** * Deletes an entry on the current connection. * * @see http://php.net/manual/en/function.ldap-delete.php * - * @param string $dn - * @return bool - * * @throws LdapRecordException */ - public function delete($dn); + public function delete(string $dn): bool; /** * Modify the name of an entry on the current connection. * * @see http://php.net/manual/en/function.ldap-rename.php * - * @param string $dn - * @param string $newRdn - * @param string $newParent - * @param bool $deleteOldRdn - * @return bool - * * @throws LdapRecordException */ - public function rename($dn, $newRdn, $newParent, $deleteOldRdn = false); + public function rename(string $dn, string $newRdn, string $newParent, bool $deleteOldRdn = false): bool; /** * Modifies an existing entry on the current connection. * * @see http://php.net/manual/en/function.ldap-modify.php * - * @param string $dn - * @param array $entry - * @return bool - * * @throws LdapRecordException */ - public function modify($dn, array $entry); + public function modify(string $dn, array $entry): bool; /** * Batch modifies an existing entry on the current connection. * * @see http://php.net/manual/en/function.ldap-modify-batch.php * - * @param string $dn - * @param array $values - * @return bool - * * @throws LdapRecordException */ - public function modifyBatch($dn, array $values); + public function modifyBatch(string $dn, array $values): bool; /** * Add attribute values to current attributes. * * @see http://php.net/manual/en/function.ldap-mod-add.php * - * @param string $dn - * @param array $entry - * @return bool - * * @throws LdapRecordException */ - public function modAdd($dn, array $entry); + public function modAdd(string $dn, array $entry): bool; /** * Replaces attribute values with new ones. * * @see http://php.net/manual/en/function.ldap-mod-replace.php * - * @param string $dn - * @param array $entry - * @return bool - * * @throws LdapRecordException */ - public function modReplace($dn, array $entry); + public function modReplace(string $dn, array $entry): bool; /** * Delete attribute values from current attributes. * * @see http://php.net/manual/en/function.ldap-mod-del.php * - * @param string $dn - * @param array $entry - * @return bool - * * @throws LdapRecordException */ - public function modDelete($dn, array $entry); - - /** - * Send LDAP pagination control. - * - * @see http://php.net/manual/en/function.ldap-control-paged-result.php - * - * @param int $pageSize - * @param bool $isCritical - * @param string $cookie - * @return bool - */ - public function controlPagedResult($pageSize = 1000, $isCritical = false, $cookie = ''); - - /** - * Retrieve the LDAP pagination cookie. - * - * @see http://php.net/manual/en/function.ldap-control-paged-result-response.php - * - * @param resource $result - * @param string $cookie - * @return bool - */ - public function controlPagedResultResponse($result, &$cookie); + public function modDelete(string $dn, array $entry): bool; /** * Frees up the memory allocated internally to store the result. * * @see https://www.php.net/manual/en/function.ldap-free-result.php * - * @param resource $result - * @return bool + * @param \LDAP\Result $result */ - public function freeResult($result); + public function freeResult(mixed $result): bool; /** - * Returns the error number of the last command executed. + * Get the error number of the last command executed. * * @see http://php.net/manual/en/function.ldap-errno.php - * - * @return int|null */ - public function errNo(); + public function errNo(): ?int; /** - * Returns the error string of the specified error number. + * Get the error string of the specified error number. * * @see http://php.net/manual/en/function.ldap-err2str.php - * - * @param int $number - * @return string */ - public function err2Str($number); + public function err2Str(int $number): string; /** - * Returns the LDAP protocol to utilize for the current connection. - * - * @return string + * Get the LDAP protocol to utilize for the current connection. */ - public function getProtocol(); + public function getProtocol(): string; /** - * Returns the extended error code of the last command. - * - * @return string + * Get the extended error code of the last command. */ - public function getExtendedError(); + public function getExtendedError(): ?string; /** - * Return the diagnostic Message. - * - * @return string + * Get the diagnostic message. */ - public function getDiagnosticMessage(); - - /** - * Determine if the current PHP version supports server controls. - * - * @deprecated since v2.5.0 - * - * @return bool - */ - public function supportsServerControlsInMethods(); + public function getDiagnosticMessage(): ?string; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapRecordException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapRecordException.php index 0669b4c65..f574b9bc4 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapRecordException.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapRecordException.php @@ -8,30 +8,21 @@ class LdapRecordException extends Exception { /** * The detailed LDAP error (if available). - * - * @var DetailedError|null */ - protected $detailedError; + protected ?DetailedError $detailedError = null; /** * Create a new Bind Exception with a detailed connection error. - * - * @param Exception $e - * @param DetailedError|null $error - * @return $this */ - public static function withDetailedError(Exception $e, DetailedError $error = null) + public static function withDetailedError(Exception $e, ?DetailedError $error = null): static { return (new static($e->getMessage(), $e->getCode(), $e))->setDetailedError($error); } /** * Set the detailed error. - * - * @param DetailedError|null $error - * @return $this */ - public function setDetailedError(DetailedError $error = null) + public function setDetailedError(?DetailedError $error = null): static { $this->detailedError = $error; @@ -39,11 +30,9 @@ class LdapRecordException extends Exception } /** - * Returns the detailed error. - * - * @return DetailedError|null + * Get the detailed error. */ - public function getDetailedError() + 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 000000000..c0c1e8306 --- /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 index 72db0a0d8..3900c61a9 100644 --- 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 @@ -2,18 +2,21 @@ namespace LdapRecord\Models\ActiveDirectory; +use LdapRecord\Models\ActiveDirectory\Concerns\HasAccountControl; use LdapRecord\Models\ActiveDirectory\Concerns\HasPrimaryGroup; +use LdapRecord\Models\ActiveDirectory\Relations\HasOnePrimaryGroup; +use LdapRecord\Models\Relations\HasMany; +use LdapRecord\Models\Relations\HasOne; class Computer extends Entry { + use HasAccountControl; use HasPrimaryGroup; /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = [ + public static array $objectClasses = [ 'top', 'person', 'organizationalperson', @@ -23,32 +26,24 @@ class Computer extends Entry /** * The groups relationship. - * - * Retrieves groups that the current computer is apart of. - * - * @return \LdapRecord\Models\Relations\HasMany */ - public function groups() + public function groups(): HasMany { return $this->hasMany(Group::class, 'member')->with($this->primaryGroup()); } /** * The primary group relationship. - * - * @return Relations\HasOnePrimaryGroup */ - public function primaryGroup() + public function primaryGroup(): HasOnePrimaryGroup { return $this->hasOnePrimaryGroup(Group::class, 'primarygroupid'); } /** * The managed by relationship. - * - * @return \LdapRecord\Models\Relations\HasOne */ - public function managedBy() + 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 000000000..425cc548f --- /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 index b7138d30c..c8e69915a 100644 --- 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 @@ -8,13 +8,8 @@ trait HasPrimaryGroup { /** * Returns a new has one primary group relationship. - * - * @param mixed $related - * @param string $relationKey - * @param string $foreignKey - * @return HasOnePrimaryGroup */ - public function hasOnePrimaryGroup($related, $relationKey, $foreignKey = 'primarygroupid') + public function hasOnePrimaryGroup(string $related, string $relationKey, string $foreignKey = 'primarygroupid'): HasOnePrimaryGroup { return new HasOnePrimaryGroup($this->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 index 52c451fe4..9087f058e 100644 --- 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 @@ -2,14 +2,14 @@ namespace LdapRecord\Models\ActiveDirectory; +use LdapRecord\Models\Relations\HasMany; + class Contact extends Entry { /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = [ + public static array $objectClasses = [ 'top', 'person', 'organizationalperson', @@ -18,12 +18,8 @@ class Contact extends Entry /** * The groups relationship. - * - * Retrieves groups that the current contact is apart of. - * - * @return \LdapRecord\Models\Relations\HasMany */ - public function groups() + public function groups(): HasMany { return $this->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 index 1636cf393..e17fb1f70 100644 --- 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 @@ -6,10 +6,8 @@ class Container extends Entry { /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = [ + public static array $objectClasses = [ 'top', 'container', ]; diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Entry.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Entry.php index e1a0233cc..e9924f433 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Entry.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Entry.php @@ -16,10 +16,8 @@ class Entry extends BaseEntry implements ActiveDirectory { /** * The default attributes that should be mutated to dates. - * - * @var array */ - protected $defaultDates = [ + protected array $defaultDates = [ 'whenchanged' => 'windows', 'whencreated' => 'windows', 'dscorepropagationdata' => 'windows', @@ -27,83 +25,73 @@ class Entry extends BaseEntry implements ActiveDirectory /** * The attribute key that contains the Object SID. - * - * @var string */ - protected $sidKey = 'objectsid'; + protected string $sidKey = 'objectsid'; /** - * @inheritdoc + * {@inheritdoc} */ - public function getObjectSidKey() + public function getObjectSidKey(): string { return $this->sidKey; } /** - * @inheritdoc + * {@inheritdoc} */ - public function getObjectSid() + public function getObjectSid(): ?string { return $this->getFirstAttribute($this->sidKey); } /** - * @inheritdoc + * {@inheritdoc} */ - public function getConvertedSid($sid = null) + public function getConvertedSid($sid = null): ?string { try { - return (string) $this->newObjectSid( - $sid ?? $this->getObjectSid() + return $this->newObjectSid( + (string) ($sid ?? $this->getObjectSid()) ); - } catch (InvalidArgumentException $e) { - return; + } catch (InvalidArgumentException) { + return null; } } /** - * @inheritdoc + * {@inheritdoc} */ - public function getBinarySid($sid = null) + public function getBinarySid($sid = null): ?string { try { return $this->newObjectSid( $sid ?? $this->getObjectSid() )->getBinary(); - } catch (InvalidArgumentException $e) { - return; + } catch (InvalidArgumentException) { + return null; } } /** * Make a new object Sid instance. - * - * @param string $value - * @return Sid */ - protected function newObjectSid($value) + protected function newObjectSid(string $value): Sid { return new Sid($value); } /** * Create a new query builder. - * - * @param Connection $connection - * @return ActiveDirectoryBuilder */ - public function newQueryBuilder(Connection $connection) + public function newQueryBuilder(Connection $connection): ActiveDirectoryBuilder { return new ActiveDirectoryBuilder($connection); } /** * Determine if the object is deleted. - * - * @return bool */ - public function isDeleted() + public function isDeleted(): bool { return strtoupper((string) $this->getFirstAttribute('isDeleted')) === 'TRUE'; } @@ -111,12 +99,9 @@ class Entry extends BaseEntry implements ActiveDirectory /** * Restore a deleted object. * - * @param string|null $newParentDn - * @return bool - * * @throws \LdapRecord\LdapRecordException */ - public function restore($newParentDn = null) + public function restore(?string $newParentDn = null): bool { if (! $this->isDeleted()) { return false; @@ -138,30 +123,27 @@ class Entry extends BaseEntry implements ActiveDirectory $this->setRawAttribute('distinguishedname', $newDn); $this->save(['isDeleted' => null]); + + return true; } /** * Get the objects restore location. - * - * @return string */ - protected function getDefaultRestoreLocation() + protected function getDefaultRestoreLocation(): ?string { return $this->getFirstAttribute('lastKnownParent') ?? $this->getParentDn($this->getParentDn($this->getDn())); } /** * Convert the attributes for JSON serialization. - * - * @param array $attributes - * @return array */ - protected function convertAttributesForJson(array $attributes = []) + 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 + // 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( @@ -174,23 +156,11 @@ class Entry extends BaseEntry implements ActiveDirectory /** * Convert the attributes from JSON serialization. - * - * @param array $attributes - * @return array */ - protected function convertAttributesFromJson(array $attributes = []) + protected function convertAttributesFromJson(array $attributes = []): array { $attributes = parent::convertAttributesFromJson($attributes); - // 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]) - )]; - } - if (isset($attributes[$this->sidKey])) { $attributes[$this->sidKey] = [$this->getBinarySid( Arr::first($attributes[$this->sidKey]) 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 index 77abbbcf5..e32f82009 100644 --- 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 @@ -5,17 +5,17 @@ namespace LdapRecord\Models\ActiveDirectory; class ExchangeDatabase extends Entry { /** - * @inheritdoc + * {@inheritdoc} */ - public static $objectClasses = ['msExchMDB']; + public static array $objectClasses = ['msExchMDB']; /** - * @inheritdoc + * {@inheritdoc} */ - public static function boot() + public static function boot(): void { parent::boot(); - static::addGlobalScope(new Scopes\InConfigurationContext()); + static::addGlobalScope(new Scopes\InConfigurationContext); } } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/ExchangeServer.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/ExchangeServer.php index d304876ab..5e13d405d 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/ExchangeServer.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/ExchangeServer.php @@ -5,18 +5,18 @@ namespace LdapRecord\Models\ActiveDirectory; class ExchangeServer extends Entry { /** - * @inheritdoc + * {@inheritdoc} */ - public static $objectClasses = ['msExchExchangeServer']; + public static array $objectClasses = ['msExchExchangeServer']; /** - * @inheritdoc + * {@inheritdoc} */ - public static function boot() + public static function boot(): void { parent::boot(); - static::addGlobalScope(new Scopes\HasServerRoleAttribute()); - static::addGlobalScope(new Scopes\InConfigurationContext()); + static::addGlobalScope(new Scopes\HasServerRoleAttribute); + static::addGlobalScope(new Scopes\InConfigurationContext); } } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/ForeignSecurityPrincipal.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/ForeignSecurityPrincipal.php index 25287ae4a..34e6c3358 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/ForeignSecurityPrincipal.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/ForeignSecurityPrincipal.php @@ -2,23 +2,23 @@ namespace LdapRecord\Models\ActiveDirectory; +use LdapRecord\Models\Relations\HasMany; + class ForeignSecurityPrincipal extends Entry { /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = ['foreignsecurityprincipal']; + public static array $objectClasses = [ + 'foreignsecurityprincipal', + ]; /** * The groups relationship. * - * Retrieves groups that the current security principal is apart of. - * - * @return \LdapRecord\Models\Relations\HasMany + * Retrieves groups that the current security principal is a part of. */ - public function groups() + public function groups(): HasMany { return $this->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 index 784588771..165bd90f8 100644 --- 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 @@ -2,14 +2,14 @@ namespace LdapRecord\Models\ActiveDirectory; +use LdapRecord\Models\Relations\HasMany; + class Group extends Entry { /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = [ + public static array $objectClasses = [ 'top', 'group', ]; @@ -17,23 +17,17 @@ class Group extends Entry /** * The groups relationship. * - * Retrieves groups that the current group is apart of. - * - * @return \LdapRecord\Models\Relations\HasMany + * Retrieves groups that the current group is a part of. */ - public function groups() + public function groups(): HasMany { return $this->hasMany(static::class, 'member'); } /** * The members relationship. - * - * Retrieves members that are apart of the group. - * - * @return \LdapRecord\Models\Relations\HasMany */ - public function members() + public function members(): HasMany { return $this->hasMany([ static::class, User::class, Contact::class, Computer::class, @@ -44,12 +38,8 @@ class Group extends Entry /** * The primary group members relationship. - * - * Retrieves members that are apart the primary group. - * - * @return \LdapRecord\Models\Relations\HasMany */ - public function primaryGroupMembers() + public function primaryGroupMembers(): HasMany { return $this->hasMany([ static::class, User::class, Contact::class, Computer::class, @@ -58,13 +48,11 @@ class Group extends Entry /** * Get the RID of the group. - * - * @return array */ - public function getRidAttribute() + public function getRidAttribute(): array { - $objectSidComponents = explode('-', (string) $this->getConvertedSid()); - - return [end($objectSidComponents)]; + 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 index 80aae9f47..0801d1080 100644 --- 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 @@ -6,20 +6,16 @@ class OrganizationalUnit extends Entry { /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = [ + public static array $objectClasses = [ 'top', 'organizationalunit', ]; /** * Get the creatable RDN attribute name. - * - * @return string */ - public function getCreatableRdnAttribute() + public function getCreatableRdnAttribute(): string { return 'ou'; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Printer.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Printer.php index df7421610..71534981d 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Printer.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Printer.php @@ -6,8 +6,6 @@ class Printer extends Entry { /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = ['printqueue']; + public static array $objectClasses = ['printqueue']; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Relations/HasOnePrimaryGroup.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Relations/HasOnePrimaryGroup.php index 40350c97a..971f79066 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Relations/HasOnePrimaryGroup.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Relations/HasOnePrimaryGroup.php @@ -9,11 +9,8 @@ class HasOnePrimaryGroup extends HasOne { /** * Get the foreign model by the given value. - * - * @param string $value - * @return Model|null */ - protected function getForeignModelByValue($value) + protected function getForeignModelByValue(string $value): ?Model { return $this->query->findBySid( $this->getParentModelObjectSid() @@ -24,11 +21,8 @@ class HasOnePrimaryGroup extends HasOne * Get the foreign value from the given model. * * Retrieves the last RID from the models Object SID. - * - * @param Model $model - * @return string */ - protected function getForeignValueFromModel(Model $model) + protected function getForeignValueFromModel(Model $model): ?string { $objectSidComponents = explode('-', $model->getConvertedSid()); @@ -37,10 +31,8 @@ class HasOnePrimaryGroup extends HasOne /** * Get the parent relationship models converted object sid. - * - * @return string */ - protected function getParentModelObjectSid() + protected function getParentModelObjectSid(): string { return preg_replace( '/\d+$/', 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 index 76df79ca3..24a2387d3 100644 --- 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 @@ -10,12 +10,8 @@ class HasServerRoleAttribute implements Scope { /** * Includes condition of having a serverRole attribute. - * - * @param Builder $query - * @param Model $model - * @return void */ - public function apply(Builder $query, Model $model) + public function apply(Builder $query, Model $model): void { $query->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 index dc8a4d21b..b7942cf4d 100644 --- 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 @@ -12,13 +12,9 @@ class InConfigurationContext implements Scope /** * Refines the base dn to be inside the configuration context. * - * @param Builder $query - * @param Model $model - * @return void - * * @throws \LdapRecord\Models\ModelNotFoundException */ - public function apply(Builder $query, Model $model) + public function apply(Builder $query, Model $model): void { $query->in($this->getConfigurationNamingContext($model)); } @@ -26,9 +22,6 @@ class InConfigurationContext implements Scope /** * Get the LDAP server configuration naming context distinguished name. * - * @param Model $model - * @return mixed - * * @throws \LdapRecord\Models\ModelNotFoundException */ protected function getConfigurationNamingContext(Model $model) 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 index 9d00cf8fa..30545b802 100644 --- 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 @@ -10,12 +10,8 @@ class RejectComputerObjectClass implements Scope { /** * Prevent computer objects from being included in results. - * - * @param Builder $query - * @param Model $model - * @return void */ - public function apply(Builder $query, Model $model) + public function apply(Builder $query, Model $model): void { $query->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 index c2ea0325d..3faec3882 100644 --- 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 @@ -4,39 +4,36 @@ namespace LdapRecord\Models\ActiveDirectory; use Carbon\Carbon; use Illuminate\Contracts\Auth\Authenticatable; +use LdapRecord\Models\ActiveDirectory\Concerns\HasAccountControl; use LdapRecord\Models\ActiveDirectory\Concerns\HasPrimaryGroup; use LdapRecord\Models\ActiveDirectory\Scopes\RejectComputerObjectClass; -use LdapRecord\Models\Attributes\AccountControl; use LdapRecord\Models\Concerns\CanAuthenticate; use LdapRecord\Models\Concerns\HasPassword; +use LdapRecord\Models\Relations\HasMany; +use LdapRecord\Models\Relations\HasOne; use LdapRecord\Query\Model\Builder; class User extends Entry implements Authenticatable { + use CanAuthenticate; + use HasAccountControl; use HasPassword; use HasPrimaryGroup; - use CanAuthenticate; /** * The password's attribute name. - * - * @var string */ - protected $passwordAttribute = 'unicodepwd'; + protected string $passwordAttribute = 'unicodepwd'; /** * The password's hash method. - * - * @var string */ - protected $passwordHashMethod = 'encode'; + protected string $passwordHashMethod = 'encode'; /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = [ + public static array $objectClasses = [ 'top', 'person', 'organizationalperson', @@ -45,10 +42,8 @@ class User extends Entry implements Authenticatable /** * The attributes that should be mutated to dates. - * - * @var array */ - protected $dates = [ + protected array $dates = [ 'lastlogon' => 'windows-int', 'lastlogoff' => 'windows-int', 'pwdlastset' => 'windows-int', @@ -59,9 +54,9 @@ class User extends Entry implements Authenticatable ]; /** - * @inheritdoc + * {@inheritdoc} */ - protected static function boot() + protected static function boot(): void { parent::boot(); @@ -69,49 +64,23 @@ class User extends Entry implements Authenticatable // 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()); + static::addGlobalScope(new RejectComputerObjectClass); } /** - * Determine if the user's account is enabled. - * - * @return bool + * Get the unique identifier for the user. */ - public function isEnabled() + public function getAuthIdentifier(): ?string { - return ! $this->isDisabled(); - } - - /** - * Determine if the user's account is disabled. - * - * @return bool - */ - public function isDisabled() - { - return $this->accountControl()->has(AccountControl::ACCOUNTDISABLE); - } - - /** - * Get the user's account control. - * - * @return AccountControl - */ - public function accountControl() - { - return new AccountControl( - $this->getFirstAttribute('userAccountControl') - ); + return $this->getConvertedGuid(); } /** * The groups relationship. * - * Retrieves groups that the user is apart of. - * - * @return \LdapRecord\Models\Relations\HasMany + * Retrieves groups that the user is a part of. */ - public function groups() + public function groups(): HasMany { return $this->hasMany(Group::class, 'member')->with($this->primaryGroup()); } @@ -120,10 +89,8 @@ class User extends Entry implements Authenticatable * The manager relationship. * * Retrieves the manager of the user. - * - * @return \LdapRecord\Models\Relations\HasOne */ - public function manager() + public function manager(): HasOne { return $this->hasOne(static::class, 'manager'); } @@ -131,33 +98,25 @@ class User extends Entry implements Authenticatable /** * The primary group relationship of the current user. * - * Retrieves the primary group the user is apart of. - * - * @return \LdapRecord\Models\Relations\HasOne + * Retrieves the primary group the user is a part of. */ - public function primaryGroup() + public function primaryGroup(): HasOne { return $this->hasOnePrimaryGroup(Group::class, 'primarygroupid'); } /** * Scopes the query to exchange mailbox users. - * - * @param Builder $query - * @return Builder */ - public function scopeWhereHasMailbox(Builder $query) + public function scopeWhereHasMailbox(Builder $query): Builder { return $query->whereHas('msExchMailboxGuid'); } /** * Scopes the query to users having a lockout value set. - * - * @param Builder $query - * @return Builder */ - public function scopeWhereHasLockout(Builder $query) + public function scopeWhereHasLockout(Builder $query): Builder { return $query->where('lockoutTime', '>=', 1); } @@ -167,12 +126,8 @@ class User extends Entry implements Authenticatable * * @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 - * - * @param string|int $localTimezone - * @param int|null $durationInMinutes - * @return bool */ - public function isLockedOut($localTimezone, $durationInMinutes = null) + public function isLockedOut(string|int $localTimezone, ?int $durationInMinutes = null): bool { $time = $this->getFirstAttribute('lockouttime'); 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 index 45fae214d..9024b934e 100644 --- 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 @@ -3,8 +3,9 @@ namespace LdapRecord\Models\Attributes; use ReflectionClass; +use Stringable; -class AccountControl +class AccountControl implements Stringable { public const SCRIPT = 1; @@ -51,151 +52,122 @@ class AccountControl public const PARTIAL_SECRETS_ACCOUNT = 67108864; /** - * The account control flag values. + * The account control flags. * * @var array */ - protected $values = []; + protected array $flags = []; /** * Constructor. - * - * @param ?int $flag */ - public function __construct($flag = null) + public function __construct(?int $flag = null) { if (! is_null($flag)) { - $this->apply($flag); + $this->applyFlags($flag); } } /** - * Get the value when casted to string. - * - * @return string + * Get the value when cast to string. */ - public function __toString() + public function __toString(): string { return (string) $this->getValue(); } /** - * Get the value when casted to int. - * - * @return int + * Get the value when cast to int. */ - public function __toInt() + public function __toInt(): int { return $this->getValue(); } /** - * Add the flag to the account control values. - * - * @param int $flag - * @return $this + * Set a flag on the account control. */ - public function add($flag) + 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->values[$flag] = $flag; + $this->flags[$flag] = $flag; return $this; } /** - * Remove the flag from the account control. - * - * @param int $flag - * @return $this + * Unset a flag from the account control. */ - public function remove($flag) + public function unsetFlag(int $flag): static { - unset($this->values[$flag]); + unset($this->flags[$flag]); return $this; } /** - * Extract and apply the flag. - * - * @param int $flag - * @return void + * Extract and apply several flags. */ - public function apply($flag) + public function applyFlags(int $flags): void { - $this->setValues($this->extractFlags($flag)); + $this->setFlags($this->extractFlags($flags)); } /** - * Determine if the account control contains the given UAC flag(s). - * - * @param int $flag - * @return bool + * Determine if the account control contains the given flag(s). */ - public function has($flag) + 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->values + $this->flags ); return in_array($flag, $flagsUsed); } /** - * Determine if the account control does not contain the given UAC flag(s). - * - * @param int $flag - * @return bool + * Determine if the account control does not contain the given flag(s). */ - public function doesntHave($flag) + public function doesntHaveFlag(int $flag): bool { - return ! $this->has($flag); + return ! $this->hasFlag($flag); } /** * Generate an LDAP filter based on the current value. - * - * @return string */ - public function filter() + public function filter(): string { - return sprintf('(UserAccountControl:1.2.840.113556.1.4.803:=%s)', $this->getValue()); + return sprintf('(UserAccountControl:1.2.840.113556.1.4.803:=%s)', $this); } /** * The logon script will be run. - * - * @return $this */ - public function runLoginScript() + public function setRunLoginScript(): static { - return $this->add(static::SCRIPT); + return $this->setFlag(static::SCRIPT); } /** * The user account is locked. - * - * @return $this */ - public function accountIsLocked() + public function setAccountIsLocked(): static { - return $this->add(static::LOCKOUT); + return $this->setFlag(static::LOCKOUT); } /** * The user account is disabled. - * - * @return $this */ - public function accountIsDisabled() + public function setAccountIsDisabled(): static { - return $this->add(static::ACCOUNTDISABLE); + return $this->setFlag(static::ACCOUNTDISABLE); } /** @@ -203,117 +175,95 @@ class AccountControl * * 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 $this */ - public function accountIsTemporary() + public function setAccountIsTemporary(): static { - return $this->add(static::TEMP_DUPLICATE_ACCOUNT); + return $this->setFlag(static::TEMP_DUPLICATE_ACCOUNT); } /** * This is a default account type that represents a typical user. - * - * @return $this */ - public function accountIsNormal() + public function setAccountIsNormal(): static { - return $this->add(static::NORMAL_ACCOUNT); + return $this->setFlag(static::NORMAL_ACCOUNT); } /** * This is a permit to trust an account for a system domain that trusts other domains. - * - * @return $this */ - public function accountIsForInterdomain() + public function setAccountIsForInterdomain(): static { - return $this->add(static::INTERDOMAIN_TRUST_ACCOUNT); + 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. - * - * @return $this */ - public function accountIsForWorkstation() + public function setAccountIsForWorkstation(): static { - return $this->add(static::WORKSTATION_TRUST_ACCOUNT); + return $this->setFlag(static::WORKSTATION_TRUST_ACCOUNT); } /** * This is a computer account for a domain controller that is a member of this domain. - * - * @return $this */ - public function accountIsForServer() + public function setAccountIsForServer(): static { - return $this->add(static::SERVER_TRUST_ACCOUNT); + return $this->setFlag(static::SERVER_TRUST_ACCOUNT); } /** * This is an MNS logon account. - * - * @return $this */ - public function accountIsMnsLogon() + public function setAccountIsMnsLogon(): static { - return $this->add(static::MNS_LOGON_ACCOUNT); + return $this->setFlag(static::MNS_LOGON_ACCOUNT); } /** * (Windows 2000/Windows Server 2003) This account does * not require Kerberos pre-authentication for logging on. - * - * @return $this */ - public function accountDoesNotRequirePreAuth() + public function setAccountDoesNotRequirePreAuth(): static { - return $this->add(static::DONT_REQ_PREAUTH); + return $this->setFlag(static::DONT_REQ_PREAUTH); } /** * When this flag is set, it forces the user to log on by using a smart card. - * - * @return $this */ - public function accountRequiresSmartCard() + public function setAccountRequiresSmartCard(): static { - return $this->add(static::SMARTCARD_REQUIRED); + 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. - * - * @return $this */ - public function accountIsReadOnly() + public function setAccountIsReadOnly(): static { - return $this->add(static::PARTIAL_SECRETS_ACCOUNT); + return $this->setFlag(static::PARTIAL_SECRETS_ACCOUNT); } /** * The home folder is required. - * - * @return $this */ - public function homeFolderIsRequired() + public function setHomeFolderIsRequired(): static { - return $this->add(static::HOMEDIR_REQUIRED); + return $this->setFlag(static::HOMEDIR_REQUIRED); } /** * No password is required. - * - * @return $this */ - public function passwordIsNotRequired() + public function setPasswordIsNotRequired(): static { - return $this->add(static::PASSWD_NOTREQD); + return $this->setFlag(static::PASSWD_NOTREQD); } /** @@ -322,42 +272,34 @@ class AccountControl * For information about how to programmatically set this permission, visit the following link: * * @see http://msdn2.microsoft.com/en-us/library/aa746398.aspx - * - * @return $this */ - public function passwordCannotBeChanged() + public function setPasswordCannotBeChanged(): static { - return $this->add(static::PASSWD_CANT_CHANGE); + return $this->setFlag(static::PASSWD_CANT_CHANGE); } /** * Represents the password, which should never expire on the account. - * - * @return $this */ - public function passwordDoesNotExpire() + public function setPasswordDoesNotExpire(): static { - return $this->add(static::DONT_EXPIRE_PASSWORD); + return $this->setFlag(static::DONT_EXPIRE_PASSWORD); } /** * (Windows 2000/Windows Server 2003) The user's password has expired. - * - * @return $this */ - public function passwordIsExpired() + public function setPasswordIsExpired(): static { - return $this->add(static::PASSWORD_EXPIRED); + return $this->setFlag(static::PASSWORD_EXPIRED); } /** * The user can send an encrypted password. - * - * @return $this */ - public function allowEncryptedTextPassword() + public function setAllowEncryptedTextPassword(): static { - return $this->add(static::ENCRYPTED_TEXT_PWD_ALLOWED); + return $this->setFlag(static::ENCRYPTED_TEXT_PWD_ALLOWED); } /** @@ -368,12 +310,10 @@ class AccountControl * * To enable a service for Kerberos delegation, you must set this * flag on the userAccountControl property of the service account. - * - * @return $this */ - public function trustForDelegation() + public function setTrustForDelegation(): static { - return $this->add(static::TRUSTED_FOR_DELEGATION); + return $this->setFlag(static::TRUSTED_FOR_DELEGATION); } /** @@ -383,44 +323,36 @@ class AccountControl * 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 $this */ - public function trustToAuthForDelegation() + public function setTrustToAuthForDelegation(): static { - return $this->add(static::TRUSTED_TO_AUTH_FOR_DELEGATION); + 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. - * - * @return $this */ - public function doNotTrustForDelegation() + public function setDoNotTrustForDelegation(): static { - return $this->add(static::NOT_DELEGATED); + 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. - * - * @return $this */ - public function useDesKeyOnly() + public function setUseDesKeyOnly(): static { - return $this->add(static::USE_DES_KEY_ONLY); + return $this->setFlag(static::USE_DES_KEY_ONLY); } /** * Get the account control value. - * - * @return int */ - public function getValue() + public function getValue(): int { - return array_sum($this->values); + return array_sum($this->flags); } /** @@ -428,64 +360,56 @@ class AccountControl * * @return array */ - public function getValues() + public function getFlags(): array { - return $this->values; + return $this->flags; } /** * Set the account control values. * * @param array $flags - * @return void */ - public function setValues(array $flags) + public function setFlags(array $flags): void { - $this->values = $flags; + $this->flags = $flags; } /** * Get all flags that are currently applied to the value. - * - * @return array */ - public function getAppliedFlags() + public function getAppliedFlags(): array { $flags = $this->getAllFlags(); - $exists = []; + $applied = []; foreach ($flags as $name => $flag) { - if ($this->has($flag)) { - $exists[$name] = $flag; + if ($this->hasFlag($flag)) { + $applied[$name] = $flag; } } - return $exists; + return $applied; } /** * Get all possible account control flags. - * - * @return array */ - public function getAllFlags() + public function getAllFlags(): array { return (new ReflectionClass(__CLASS__))->getConstants(); } /** * Extracts the given flag into an array of flags used. - * - * @param int $flag - * @return array */ - public function extractFlags($flag) + protected function extractFlags(int $flag): array { $flags = []; for ($i = 0; $i <= 26; $i++) { - if ((int) $flag & (1 << $i)) { + if ($flag & (1 << $i)) { $flags[1 << $i] = 1 << $i; } } 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 index fc981299a..09391091b 100644 --- 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 @@ -4,89 +4,69 @@ namespace LdapRecord\Models\Attributes; use LdapRecord\EscapesValues; use LdapRecord\Support\Arr; +use Stringable; -class DistinguishedName +class DistinguishedName implements Stringable { use EscapesValues; /** - * The underlying raw value. - * - * @var string + * The underlying raw distinguished name value. */ - protected $value; + protected string $value; /** * Constructor. - * - * @param string|null $value */ - public function __construct($value = null) + public function __construct(?string $value = null) { $this->value = trim((string) $value); } /** * Get the distinguished name value. - * - * @return string */ - public function __toString() + public function __toString(): string { - return (string) $this->value; + return $this->value; } /** * Alias of the "build" method. - * - * @param string|null $value - * @return DistinguishedNameBuilder */ - public static function of($value = null) + public static function of(?string $value = null): DistinguishedNameBuilder { return static::build($value); } /** * Get a new DN builder object from the given DN. - * - * @param string|null $value - * @return DistinguishedNameBuilder */ - public static function build($value = null) + public static function build(?string $value = null): DistinguishedNameBuilder { return new DistinguishedNameBuilder($value); } /** * Make a new distinguished name instance. - * - * @param string|null $value - * @return static */ - public static function make($value = null) + public static function make(?string $value = null): static { return new static($value); } /** * Determine if the given value is a valid distinguished name. - * - * @param string $value - * @return bool */ - public static function isValid($value) + public static function isValid(?string $value = null): bool { return ! static::make($value)->isEmpty(); } /** * Explode a distinguished name into relative distinguished names. - * - * @param string $dn - * @return array */ - public static function explode($dn) + public static function explode(string $dn): array { $components = ldap_explode_dn($dn, (int) $withoutAttributes = false); @@ -103,58 +83,34 @@ class DistinguishedName return $components; } - /** - * 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); - } - /** * Explode the RDN into an attribute and value. - * - * @param string $rdn - * @return array */ - public static function explodeRdn($rdn) + public static function explodeRdn(string $rdn): array { return explode('=', $rdn, $limit = 2); } /** * Implode the component attribute and value into an RDN. - * - * @param string $rdn - * @return string */ - public static function makeRdn(array $component) + public static function makeRdn(array $component): string { return implode('=', $component); } /** * Get the underlying value. - * - * @return string|null */ - public function get() + public function get(): ?string { return $this->value; } /** * Set the underlying value. - * - * @param string|null $value - * @return $this */ - public function set($value) + public function set(?string $value = null): static { $this->value = $value; @@ -163,15 +119,13 @@ class DistinguishedName /** * Get the distinguished name values without attributes. - * - * @return array */ - public function values() + public function values(): array { $values = []; foreach ($this->multi() as [, $value]) { - $values[] = static::unescape($value); + $values[] = EscapedValue::unescape($value); } return $values; @@ -179,10 +133,8 @@ class DistinguishedName /** * Get the distinguished name attributes without values. - * - * @return array */ - public function attributes() + public function attributes(): array { $attributes = []; @@ -195,10 +147,8 @@ class DistinguishedName /** * Get the distinguished name components with attributes. - * - * @return array */ - public function components() + public function components(): array { $components = []; @@ -207,7 +157,7 @@ class DistinguishedName // 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(static::unescape($value))->dn(); + $value = $this->escape(EscapedValue::unescape($value))->forDn(); $components[] = static::makeRdn([$attribute, $value]); } @@ -217,10 +167,8 @@ class DistinguishedName /** * Convert the distinguished name into an associative array. - * - * @return array */ - public function assoc() + public function assoc(): array { $map = []; @@ -237,52 +185,40 @@ class DistinguishedName /** * Split the RDNs into a multi-dimensional array. - * - * @return array */ - public function multi() + public function multi(): array { - return array_map(function ($rdn) { - return static::explodeRdn($rdn); - }, $this->rdns()); + return array_map(fn ($rdn) => static::explodeRdn($rdn), $this->rdns()); } /** * Split the distinguished name into an array of unescaped RDN's. - * - * @return array */ - public function rdns() + public function rdns(): array { return static::explode($this->value); } /** * Get the first RDNs value. - * - * @return string|null */ - public function name() + public function name(): ?string { return Arr::first($this->values()); } /** * Get the first RDNs attribute. - * - * @return string|null */ - public function head() + public function head(): ?string { return Arr::first($this->attributes()); } /** * Get the relative distinguished name. - * - * @return string|null */ - public function relative() + public function relative(): ?string { return Arr::first($this->components()); } @@ -291,20 +227,16 @@ class DistinguishedName * Alias of relative(). * * Get the first RDN from the distinguished name. - * - * @return string|null */ - public function first() + public function first(): ?string { return $this->relative(); } /** * Get the parent distinguished name. - * - * @return string|null */ - public function parent() + public function parent(): ?string { $components = $this->components(); @@ -315,112 +247,91 @@ class DistinguishedName /** * Determine if the distinguished name is empty. - * - * @return bool */ - public function isEmpty() + public function isEmpty(): bool { return empty( - array_filter($this->values()) + array_filter( + array_map('trim', $this->values()) + ) ); } /** - * Determine if the current distinguished name is a parent of the given child. - * - * @param DistinguishedName $child - * @return bool + * Determine if the distinguished name is not empty. */ - public function isParentOf(self $child) + 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. - * - * @param DistinguishedName $parent - * @return bool */ - public function isChildOf(self $parent) + public function isChildOf(self $parent): bool { - if ( - empty($components = $this->components()) || - empty($parentComponents = $parent->components()) - ) { + if (! $this->isComparable($this->parent(), $parent->get())) { return false; } - array_shift($components); - - return $this->compare($components, $parentComponents); + return $this->normalize($this->parent()) === $this->normalize($parent->get()); } /** * Determine if the current distinguished name is an ancestor of the descendant. - * - * @param DistinguishedName $descendant - * @return bool */ - public function isAncestorOf(self $descendant) + public function isAncestorOf(self $descendant): bool { return $descendant->isDescendantOf($this); } /** * Determine if the current distinguished name is a descendant of the ancestor. - * - * @param DistinguishedName $ancestor - * @return bool */ - public function isDescendantOf(self $ancestor) + public function isDescendantOf(self $ancestor): bool { - if ( - empty($components = $this->components()) || - empty($ancestorComponents = $ancestor->components()) - ) { + if (! $this->isComparable($this->parent(), $ancestor->get())) { return false; } - if (! $length = count($components) - count($ancestorComponents)) { + 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; } - array_splice($components, $offset = 0, $length); - - return $this->compare($components, $ancestorComponents); + return $this->normalize($this->parent()) === $this->normalize($sibling->parent()); } /** - * Compare whether the two distinguished name values are equal. - * - * @param array $values - * @param array $other - * @return bool + * Determine if the distinguished names are comparable. */ - protected function compare(array $values, array $other) + protected function isComparable(?string $first, ?string $second): bool { - return $this->recase($values) == $this->recase($other); - } - - /** - * Recase the array values. - * - * @param array $values - * @return array - */ - protected function recase(array $values) - { - return array_map([$this, 'normalize'], $values); + return static::make($first)->isNotEmpty() && static::make($second)->isNotEmpty(); } /** * Normalize the string value. - * - * @param string $value - * @return string */ - protected function normalize($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 index a0d84d828..7345cb92d 100644 --- 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 @@ -4,67 +4,53 @@ namespace LdapRecord\Models\Attributes; use LdapRecord\EscapesValues; use LdapRecord\Support\Arr; +use Stringable; -class DistinguishedNameBuilder +class DistinguishedNameBuilder implements Stringable { use EscapesValues; /** * The components of the DN. - * - * @var array */ - protected $components = []; + protected array $components = []; /** * Whether to output the DN in reverse. - * - * @var bool */ - protected $reverse = false; + protected bool $reverse = false; /** * Constructor. - * - * @param string|null $value */ public function __construct($dn = null) { - $this->components = array_map(function ($rdn) { - return DistinguishedName::explodeRdn($rdn); - }, DistinguishedName::make($dn)->components()); + $this->components = array_map( + fn ($rdn) => DistinguishedName::explodeRdn($rdn), + DistinguishedName::make($dn)->components() + ); } /** * Forward missing method calls onto the Distinguished Name object. - * - * @param string $method - * @param array $args - * @return mixed */ - public function __call($method, $args) + public function __call(string $method, array $args): mixed { return $this->get()->{$method}(...$args); } /** * Get the distinguished name value. - * - * @return string */ - public function __toString() + public function __toString(): string { return (string) $this->get(); } /** * Prepend an RDN onto the DN. - * - * @param string|array $attribute - * @param string|null $value - * @return $this */ - public function prepend($attribute, $value = null) + public function prepend(array|string $attribute, ?string $value = null): static { array_unshift( $this->components, @@ -76,12 +62,8 @@ class DistinguishedNameBuilder /** * Append an RDN onto the DN. - * - * @param string|array $attribute - * @param string|null $value - * @return $this */ - public function append($attribute, $value = null) + public function append(array|string $attribute, ?string $value = null): static { array_push( $this->components, @@ -93,12 +75,8 @@ class DistinguishedNameBuilder /** * Componentize the attribute and value. - * - * @param string|array $attribute - * @param string|null $value - * @return array */ - protected function componentize($attribute, $value = null) + 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 @@ -120,65 +98,50 @@ class DistinguishedNameBuilder /** * Make a componentized array by exploding the value if it's a string. - * - * @param string $value - * @return array */ - protected function makeComponentizedArray($value) + 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. - * - * @param string|array $attribute - * @param string|null $value - * @return array */ - protected function makeAppendableComponent($attribute, $value = null) + protected function makeAppendableComponent(string|array $attribute, ?string $value = null): array { - return [trim($attribute), $this->escape(trim($value))->dn()]; + return [trim($attribute), $this->escape(trim($value))->forDn()]; } /** * Pop an RDN off of the end of the DN. - * - * @param int $amount - * @param array $removed - * @return $this */ - public function pop($amount = 1, &$removed = []) + public function pop(int $amount = 1, ?array &$removed = null): static { - $removed = array_map(function ($component) { - return DistinguishedName::makeRdn($component); - }, array_splice($this->components, -$amount, $amount)); + $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. - * - * @param int $amount - * @param array $removed - * @return $this */ - public function shift($amount = 1, &$removed = []) + public function shift(int $amount = 1, ?array &$removed = null): static { - $removed = array_map(function ($component) { - return DistinguishedName::makeRdn($component); - }, array_splice($this->components, 0, $amount)); + $removed = array_map( + fn ($component) => DistinguishedName::makeRdn($component), + array_splice($this->components, 0, $amount) + ); return $this; } /** * Whether to output the DN in reverse. - * - * @return $this */ - public function reverse() + public function reverse(): static { $this->reverse = true; @@ -187,11 +150,8 @@ class DistinguishedNameBuilder /** * Get the components of the DN. - * - * @param null|string $type - * @return array */ - public function components($type = null) + public function components(?string $type = null): array { return is_null($type) ? $this->components @@ -200,42 +160,36 @@ class DistinguishedNameBuilder /** * Get the components of a particular type. - * - * @param string $type - * @return array */ - protected function componentsOfType($type) + protected function componentsOfType(string $type): array { - $components = array_filter($this->components, function ($component) use ($type) { - return ([$name] = $component) && strtolower($name) === strtolower($type); - }); + $components = array_filter($this->components, fn ($component) => ( + ([$name] = $component) && strtolower($name) === strtolower($type) + )); return array_values($components); } /** * Get the fully qualified DN. - * - * @return DistinguishedName */ - public function get() + public function get(): DistinguishedName { return new DistinguishedName($this->build()); } /** * Build the distinguished name from the components. - * - * @return string */ - protected function build() + protected function build(): string { $components = $this->reverse ? array_reverse($this->components) : $this->components; - return implode(',', array_map(function ($component) { - return DistinguishedName::makeRdn($component); - }, $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 index 3c9d4db0a..c28d95c20 100644 --- 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 @@ -2,80 +2,75 @@ namespace LdapRecord\Models\Attributes; -class EscapedValue +use Stringable; + +class EscapedValue implements Stringable { /** * The value to be escaped. - * - * @var string */ - protected $value; + protected mixed $value; /** * The characters to ignore when escaping. - * - * @var string */ - protected $ignore; + protected string $ignore; /** * The escape flags. - * - * @var int */ - protected $flags; + protected int $flags; /** * Constructor. - * - * @param string $value - * @param string $ignore - * @param int $flags */ - public function __construct($value, $ignore = '', $flags = 0) + public function __construct(mixed $value, string $ignore = '', int $flags = 0) { - $this->value = (string) $value; + $this->value = $value; $this->ignore = $ignore; $this->flags = $flags; } /** - * Get the escaped value. - * - * @return string + * Un-escapes a hexadecimal string into its original string representation. */ - public function __toString() + public static function unescape(string $value): string { - return (string) $this->get(); + return preg_replace_callback( + '/\\\([0-9A-Fa-f]{2})/', + fn ($matches) => chr(hexdec($matches[1])), + $value + ); } /** * Get the escaped value. - * - * @return mixed */ - public function get() + public function __toString(): string { - return ldap_escape($this->value, $this->ignore, $this->flags); + 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. - * - * @return mixed */ - public function raw() + public function getRaw(): mixed { return $this->value; } /** * Set the characters to exclude from being escaped. - * - * @param string $characters - * @return $this */ - public function ignore($characters) + public function ignore(string $characters): static { $this->ignore = $characters; @@ -84,10 +79,8 @@ class EscapedValue /** * Prepare the value to be escaped for use in a distinguished name. - * - * @return $this */ - public function dn() + public function forDn(): static { $this->flags = LDAP_ESCAPE_DN; @@ -96,10 +89,8 @@ class EscapedValue /** * Prepare the value to be escaped for use in a filter. - * - * @return $this */ - public function filter() + public function forFilter(): static { $this->flags = LDAP_ESCAPE_FILTER; @@ -108,10 +99,8 @@ class EscapedValue /** * Prepare the value to be escaped for use in a distinguished name and filter. - * - * @return $this */ - public function both() + public function forDnAndFilter(): static { $this->flags = LDAP_ESCAPE_FILTER + LDAP_ESCAPE_DN; 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 index e04b5cd3f..dfde16429 100644 --- 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 @@ -3,33 +3,14 @@ namespace LdapRecord\Models\Attributes; use InvalidArgumentException; -use LdapRecord\Utilities; +use Stringable; -class Guid +class Guid implements Stringable { /** * The string GUID value. - * - * @var string */ - protected $value; - - /** - * The guid structure in order by section to parse using substr(). - * - * @author Chad Sikorra - * - * @see 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]], - ]; + protected ?string $value = null; /** * The hexadecimal octet order based on string position. @@ -37,10 +18,8 @@ class Guid * @author Chad Sikorra * * @see https://github.com/ldaptools/ldaptools - * - * @var array */ - protected $octetSections = [ + protected array $octetSections = [ [6, 4, 2, 0], [10, 8], [14, 12], @@ -48,24 +27,19 @@ class Guid ]; /** - * Determines if the specified GUID is valid. - * - * @param string $guid - * @return bool + * Determine if the specified GUID is valid. */ - public static function isValid($guid) + public static function isValid(string $guid): bool { - return Utilities::isValidGuid($guid); + return (bool) preg_match('/^([0-9a-fA-F]){8}(-([0-9a-fA-F]){4}){3}-([0-9a-fA-F]){12}$/', $guid); } /** * Constructor. * - * @param mixed $value - * * @throws InvalidArgumentException */ - public function __construct($value) + public function __construct(string $value) { if (static::isValid($value)) { $this->value = $value; @@ -77,66 +51,83 @@ class Guid } /** - * Returns the string value of the GUID. - * - * @return string + * Get the string value of the GUID. */ - public function __toString() + public function __toString(): string { return $this->getValue(); } /** - * Returns the string value of the SID. - * - * @return string + * Get the string value of the GUID. */ - public function getValue() + public function getValue(): string { return $this->value; } /** * Get the binary representation of the GUID string. - * - * @return string */ - public function getBinary() + public function getBinary(): string { return hex2bin($this->getHex()); } /** - * Get the hexadecimal representation of the GUID string. - * - * @return string + * Get the encoded hexadecimal representation of the GUID string. */ - public function getHex() + public function getEncodedHex(): string { - $data = ''; + 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) { - $data .= $this->parseSection($guid, $section, $octet = true); + $sections[] = $this->parseSection($guid, $section, true); } - return $data; + return $sections; } /** - * Returns the string variant of a binary GUID. - * - * @param string $binary - * @return string|null + * Get the string variant of a binary GUID. */ - protected function binaryGuidToString($binary) + protected function binaryGuidToString(string $binary): ?string { - return Utilities::binaryGuidToString($binary); + 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); } /** - * Return the specified section of the hexadecimal string. + * Get the specified section of the hexadecimal string. * * @author Chad Sikorra * @@ -147,7 +138,7 @@ class Guid * @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) + protected function parseSection(string $hex, array $sections, bool $octet = false): string { $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 index 72d4c6f48..c6eb4985e 100644 --- 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 @@ -6,11 +6,8 @@ class MbString { /** * Get the integer value of a specific character. - * - * @param $string - * @return int */ - public static function ord($string) + public static function ord(string $string): int { if (static::isLoaded()) { $result = unpack('N', mb_convert_encoding($string, 'UCS-4BE', 'UTF-8')); @@ -25,11 +22,8 @@ class MbString /** * Get the character for a specific integer value. - * - * @param $int - * @return string */ - public static function chr($int) + public static function chr(int $int): string { if (static::isLoaded()) { return mb_convert_encoding(pack('n', $int), 'UTF-8', 'UTF-16BE'); @@ -40,36 +34,28 @@ class MbString /** * Split a string into its individual characters and return it as an array. - * - * @param string $value - * @return string[] */ - public static function split($value) + public static function split(string $value): array { return preg_split('/(? ['$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. - * - * @param string $password - * @return string|void */ - public static function getHashMethod($password) + public static function getHashMethod(string $password): ?string { if (! preg_match('/^\{(\w+)\}/', $password, $matches)) { - return; + return null; } return $matches[1]; @@ -272,14 +198,11 @@ class Password /** * Attempt to retrieve the hash method and algorithm used for the password. - * - * @param string $password - * @return array|void */ - public static function getHashMethodAndAlgo($password) + public static function getHashMethodAndAlgo(string $password): ?array { if (! preg_match('/^\{(\w+)\}\$([0-9a-z]{1})\$/', $password, $matches)) { - return; + return null; } return [$matches[1], $matches[2]]; @@ -288,11 +211,9 @@ class Password /** * Attempt to retrieve a salt from the encrypted password. * - * @return string - * * @throws LdapRecordException */ - public static function getSalt($encryptedPassword) + public static function getSalt(string $encryptedPassword): string { // crypt() methods. if (preg_match('/^\{(\w+)\}(\$.*\$).*$/', $encryptedPassword, $matches)) { @@ -310,12 +231,9 @@ class Password /** * Determine if the hash method requires a salt to be given. * - * @param string $method - * @return bool - * * @throws \ReflectionException */ - public static function hashMethodRequiresSalt($method): bool + public static function hashMethodRequiresSalt(string $method): bool { $parameters = (new ReflectionMethod(static::class, $method))->getParameters(); 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 index 7760453ce..08c36267c 100644 --- 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 @@ -3,36 +3,29 @@ namespace LdapRecord\Models\Attributes; use InvalidArgumentException; -use LdapRecord\Utilities; +use Stringable; -class Sid +class Sid implements Stringable { /** * The string SID value. - * - * @var string */ - protected $value; + protected string $value; /** - * Determines if the specified SID is valid. - * - * @param string $sid - * @return bool + * Determine if the specified SID is valid. */ - public static function isValid($sid) + public static function isValid(string $sid): bool { - return Utilities::isValidSid($sid); + return (bool) preg_match("/^S-\d(-\d{1,10}){1,16}$/i", $sid); } /** * Constructor. * - * @param mixed $value - * * @throws InvalidArgumentException */ - public function __construct($value) + public function __construct(string $value) { if (static::isValid($value)) { $this->value = $value; @@ -44,31 +37,25 @@ class Sid } /** - * Returns the string value of the SID. - * - * @return string + * Get the string value of the SID. */ - public function __toString() + public function __toString(): string { return $this->getValue(); } /** - * Returns the string value of the SID. - * - * @return string + * Get the string value of the SID. */ - public function getValue() + public function getValue(): string { return $this->value; } /** - * Returns the binary variant of the SID. - * - * @return string + * Get the binary variant of the SID. */ - public function getBinary() + public function getBinary(): string { $sid = explode('-', ltrim($this->value, 'S-')); @@ -87,13 +74,50 @@ class Sid } /** - * Returns the string variant of a binary SID. - * - * @param string $binary - * @return string|null + * Get the string variant of a binary SID. */ - protected function binarySidToString($binary) + protected function binarySidToString(string $binary): ?string { - return Utilities::binarySidToString($binary); + 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 index 499579f82..dd2d0ebda 100644 --- 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 @@ -29,10 +29,8 @@ class TSProperty * 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 = [ + protected array $propTypes = [ 'string' => [ 'CtxWFHomeDir', 'CtxWFHomeDirW', @@ -63,31 +61,23 @@ class TSProperty /** * The property name. - * - * @var string */ - protected $name; + protected ?string $name = null; /** * The property value. - * - * @var string|int */ - protected $value; + protected string|int|null $value = null; /** * The property value type. - * - * @var int */ - protected $valueType = 1; + protected int $valueType = 1; /** * Pass binary TSProperty data to construct its object representation. - * - * @param string|null $value */ - public function __construct($value = null) + public function __construct(string|int|null $value = null) { if ($value) { $this->decode(bin2hex($value)); @@ -96,11 +86,8 @@ class TSProperty /** * Set the name for the TSProperty. - * - * @param string $name - * @return TSProperty */ - public function setName($name) + public function setName(string $name): static { $this->name = $name; @@ -109,21 +96,16 @@ class TSProperty /** * Get the name for the TSProperty. - * - * @return string */ - public function getName() + public function getName(): ?string { return $this->name; } /** * Set the value for the TSProperty. - * - * @param string|int $value - * @return TSProperty */ - public function setValue($value) + public function setValue(string|int $value): static { $this->value = $value; @@ -132,10 +114,8 @@ class TSProperty /** * Get the value for the TSProperty. - * - * @return string|int */ - public function getValue() + public function getValue(): string|int|null { return $this->value; } @@ -143,10 +123,8 @@ class TSProperty /** * Convert the TSProperty name/value back to its binary * representation for the userParameters blob. - * - * @return string */ - public function toBinary() + public function toBinary(): string { $name = bin2hex($this->name); @@ -166,10 +144,8 @@ class TSProperty /** * Given a TSProperty blob, decode the name/value/type/etc. - * - * @param string $tsProperty */ - protected function decode($tsProperty) + protected function decode(string $tsProperty): void { $nameLength = hexdec(substr($tsProperty, 0, 2)); @@ -183,12 +159,8 @@ class TSProperty /** * 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) + 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 @@ -206,12 +178,8 @@ class TSProperty /** * 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) + protected function getDecodedValueForProp(string $propName, string $propValue): string|int { if (in_array($propName, $this->propTypes['string'])) { // Strip away null terminators. I think this should @@ -234,11 +202,9 @@ class TSProperty * 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 + * @param bool $string Whether this is simple string data. */ - protected function decodePropValue($hex, $string = false) + protected function decodePropValue(string $hex, bool $string = false): string { $decodePropValue = ''; @@ -266,12 +232,8 @@ class TSProperty /** * Get the encoded property value as a binary blob. - * - * @param string $value - * @param bool $string - * @return string */ - protected function encodePropValue($value, $string = false) + 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 @@ -307,12 +269,8 @@ class TSProperty * 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) + protected function packBitString(string $bits, int $len): string { $bits = substr($bits, 0, $len); // Pad input with zeros to next multiple of 4 above $len @@ -329,12 +287,8 @@ class TSProperty /** * Based on the control, adjust the nibble accordingly. - * - * @param string $nibble - * @param string $control - * @return string */ - protected function nibbleControl($nibble, $control) + 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 @@ -355,10 +309,8 @@ class TSProperty * 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) + protected function getNibbleWithControl(string $nibbleType, string $nibble): string { $dec = bindec($nibble); @@ -375,11 +327,9 @@ class TSProperty /** * 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) + 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 index 8320dd8ed..a81018143 100644 --- 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 @@ -36,33 +36,31 @@ class TSPropertyArray ]; /** - * @var string The default data that occurs before the TSPropertyArray (CtxCfgPresent with a bunch of spaces...?) + * The default data that occurs before the TSPropertyArray (CtxCfgPresent with a bunch of spaces...?). */ - protected $defaultPreBinary = '43747843666750726573656e742020202020202020202020202020202020202020202020202020202020202020202020'; + protected string $defaultPreBinary = '43747843666750726573656e742020202020202020202020202020202020202020202020202020202020202020202020'; /** + * The TSProperty array. + * * @var TSProperty[] */ - protected $tsProperty = []; + protected array $tsProperty = []; /** - * @var string + * The TSProperty signature. */ - protected $signature = self::VALID_SIGNATURE; + protected string $signature = self::VALID_SIGNATURE; /** * Binary data that occurs before the TSPropertyArray data in userParameters. - * - * @var string */ - protected $preBinary = ''; + protected string $preBinary = ''; /** * Binary data that occurs after the TSPropertyArray data in userParameters. - * - * @var string */ - protected $postBinary = ''; + protected string $postBinary = ''; /** * Construct in one of the following ways:. @@ -70,10 +68,8 @@ class TSPropertyArray * - 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) + public function __construct(mixed $tsPropertyArray = null) { $this->preBinary = hex2bin($this->defaultPreBinary); @@ -81,7 +77,7 @@ class TSPropertyArray $tsPropertyArray = $tsPropertyArray ?: self::DEFAULTS; foreach ($tsPropertyArray as $key => $value) { - $tsProperty = new TSProperty(); + $tsProperty = new TSProperty; $this->tsProperty[$key] = $tsProperty->setName($key)->setValue($value); } @@ -92,22 +88,16 @@ class TSPropertyArray /** * Check if a specific TSProperty exists by its property name. - * - * @param string $propName - * @return bool */ - public function has($propName) + 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). - * - * @param string $propName - * @return TSProperty */ - public function get($propName) + public function get(string $propName): TSProperty { $this->validateProp($propName); @@ -116,11 +106,8 @@ class TSPropertyArray /** * Add a TSProperty object. If it already exists, it will be overwritten. - * - * @param TSProperty $tsProperty - * @return $this */ - public function add(TSProperty $tsProperty) + public function add(TSProperty $tsProperty): static { $this->tsProperty[$tsProperty->getName()] = $tsProperty; @@ -128,12 +115,9 @@ class TSPropertyArray } /** - * Remove a TSProperty by its property name (ie. CtxMinEncryptionLevel). - * - * @param string $propName - * @return $this + * Remove a TSProperty by its property name (i.e. CtxMinEncryptionLevel). */ - public function remove($propName) + public function remove(string $propName): static { foreach (array_keys($this->tsProperty) as $property) { if (strtolower($propName) == strtolower($property)) { @@ -146,12 +130,8 @@ class TSPropertyArray /** * Set the value for a specific TSProperty by its name. - * - * @param string $propName - * @param mixed $propValue - * @return $this */ - public function set($propName, $propValue) + public function set(string $propName, string|int $propValue): static { $this->validateProp($propName); @@ -162,10 +142,8 @@ class TSPropertyArray /** * Get the full binary representation of the userParameters containing the TSPropertyArray data. - * - * @return string */ - public function toBinary() + public function toBinary(): string { $binary = $this->preBinary; @@ -182,10 +160,8 @@ class TSPropertyArray /** * Get a simple associative array containing of all TSProperty names and values. - * - * @return array */ - public function toArray() + public function toArray(): array { $userParameters = []; @@ -201,17 +177,15 @@ class TSPropertyArray * * @return TSProperty[] */ - public function getTSProperties() + public function getTSProperties(): array { return $this->tsProperty; } /** * Validates that the given property name exists. - * - * @param string $propName */ - protected function validateProp($propName) + protected function validateProp(string $propName): void { if (! $this->has($propName)) { throw new InvalidArgumentException(sprintf('TSProperty for "%s" does not exist.', $propName)); @@ -219,21 +193,17 @@ class TSPropertyArray } /** - * @param string $propName - * @return TSProperty + * Get the TS property object for the given property. */ - protected function getTsPropObj($propName) + protected function getTsPropObj(string $propName): TSProperty { 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 + * Get an associative array with all the userParameters property names and values. */ - protected function decodeUserParameters($userParameters) + protected function decodeUserParameters(string $userParameters): void { $userParameters = bin2hex($userParameters); @@ -263,11 +233,9 @@ class TSPropertyArray * 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) + protected function addTSPropData(string $tsPropertyArray, int $tsPropCount): int { $length = 0; 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 index 0f07bc5a0..4046eb715 100644 --- 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 @@ -7,38 +7,37 @@ use Carbon\CarbonInterface; use DateTime; use DateTimeZone; use LdapRecord\LdapRecordException; -use LdapRecord\Utilities; class Timestamp { + public const TYPE_LDAP = 'ldap'; + + public const TYPE_WINDOWS = 'windows'; + + public const TYPE_WINDOWS_INT = 'windows-int'; + public const WINDOWS_INT_MAX = 9223372036854775807; /** * The current timestamp type. - * - * @var string */ - protected $type; + protected string $type; /** * The available timestamp types. - * - * @var array */ - protected $types = [ - 'ldap', - 'windows', - 'windows-int', + protected array $types = [ + Timestamp::TYPE_LDAP, + Timestamp::TYPE_WINDOWS, + Timestamp::TYPE_WINDOWS_INT, ]; /** * Constructor. * - * @param string $type - * * @throws LdapRecordException */ - public function __construct($type) + public function __construct(string $type) { $this->setType($type); } @@ -46,11 +45,9 @@ class Timestamp /** * Set the type of timestamp to convert from / to. * - * @param string $type - * * @throws LdapRecordException */ - public function setType($type) + public function setType(string $type): void { if (! in_array($type, $this->types)) { throw new LdapRecordException("Unrecognized LDAP date type [$type]"); @@ -62,18 +59,15 @@ class Timestamp /** * Converts the value to an LDAP date string. * - * @param mixed $value - * @return float|string - * * @throws LdapRecordException */ - public function fromDateTime($value) + 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 + // 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 == 'windows-int' && $this->valueIsWindowsIntegerType($value)) { + 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. @@ -89,30 +83,18 @@ class Timestamp $value = Carbon::instance($value); } - switch ($this->type) { - case 'ldap': - $value = $this->convertDateTimeToLdapTime($value); - break; - case 'windows': - $value = $this->convertDateTimeToWindows($value); - break; - case 'windows-int': - $value = $this->convertDateTimeToWindowsInteger($value); - break; - default: - throw new LdapRecordException("Unrecognized date type [{$this->type}]"); - } - - return $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. - * - * @param int|string $value - * @return bool */ - protected function valueIsWindowsIntegerType($value) + protected function valueIsWindowsIntegerType(mixed $value): bool { return is_numeric($value) && in_array(strlen((string) $value), [18, 19]); } @@ -120,12 +102,9 @@ class Timestamp /** * Converts the LDAP timestamp value to a Carbon instance. * - * @param mixed $value - * @return Carbon|int|false - * * @throws LdapRecordException */ - public function toDateTime($value) + public function toDateTime(mixed $value): Carbon|int|false { $value = is_array($value) ? reset($value) : $value; @@ -133,87 +112,70 @@ class Timestamp return Carbon::instance($value); } - switch ($this->type) { - case 'ldap': - $value = $this->convertLdapTimeToDateTime($value); - break; - case 'windows': - $value = $this->convertWindowsTimeToDateTime($value); - break; - case 'windows-int': - $value = $this->convertWindowsIntegerTimeToDateTime($value); - break; - default: - throw new LdapRecordException("Unrecognized date type [{$this->type}]"); - } + $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. - * - * @param string $value - * @return DateTime|false */ - protected function convertLdapTimeToDateTime($value) + protected function convertLdapTimeToDateTime(string $value): DateTime|false { - return DateTime::createFromFormat( - str_contains((string) $value, 'Z') ? 'YmdHis\Z' : 'YmdHisT', - $value - ); + 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. - * - * @param DateTime $date - * @return string */ - protected function convertDateTimeToLdapTime(DateTime $date) + protected function convertDateTimeToLdapTime(DateTime $date): string { return $date->format( - $date->getOffset() == 0 ? 'YmdHis\Z' : 'YmdHisO' + $date->getOffset() == 0 + ? 'YmdHis\Z' + : 'YmdHisO' ); } /** * Converts standard windows timestamps to a date time object. - * - * @param string $value - * @return DateTime|false */ - protected function convertWindowsTimeToDateTime($value) + protected function convertWindowsTimeToDateTime(string $value): DateTime|false { - return DateTime::createFromFormat( - str_contains((string) $value, '0Z') ? 'YmdHis.0\Z' : 'YmdHis.0T', - $value, - new DateTimeZone('UTC') - ); + 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. - * - * @param DateTime $date - * @return string */ - protected function convertDateTimeToWindows(DateTime $date) + protected function convertDateTimeToWindows(DateTime $date): string { return $date->format( - $date->getOffset() == 0 ? 'YmdHis.0\Z' : 'YmdHis.0O' + $date->getOffset() == 0 + ? 'YmdHis.0\Z' + : 'YmdHis.0O' ); } /** * Converts standard windows integer dates to a date time object. * - * @param int $value - * @return DateTime|int|false - * * @throws \Exception */ - protected function convertWindowsIntegerTimeToDateTime($value) + protected function convertWindowsIntegerTimeToDateTime(string|int|null $value = null): DateTime|int|false { if (is_null($value) || $value === '') { return false; @@ -227,19 +189,16 @@ class Timestamp return (int) $value; } - return (new DateTime())->setTimestamp( - Utilities::convertWindowsTimeToUnixTime($value) + return (new DateTime)->setTimestamp( + (int) ($value / 10000000) - 11644473600 ); } /** * Converts date objects to a windows integer timestamp. - * - * @param DateTime $date - * @return float */ - protected function convertDateTimeToWindowsInteger(DateTime $date) + protected function convertDateTimeToWindowsInteger(DateTime $date): int { - return Utilities::convertUnixTimeToWindowsTime($date->getTimestamp()); + 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 index 954c58fcf..3b153095d 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/BatchModification.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/BatchModification.php @@ -9,48 +9,38 @@ class BatchModification use DetectsResetIntegers; /** - * The array keys to be used in batch modifications. + * The array key identifiers. */ public const KEY_ATTRIB = 'attrib'; + public const KEY_MODTYPE = 'modtype'; + public const KEY_VALUES = 'values'; /** * The attribute of the modification. - * - * @var string|null */ - protected $attribute; + protected ?string $attribute = null; /** * The original value of the attribute before modification. - * - * @var array */ - protected $original = []; + protected array $original = []; /** * The values of the modification. - * - * @var array */ - protected $values = []; + protected array $values = []; /** * The modtype integer of the batch modification. - * - * @var int|null */ - protected $type; + protected ?int $type = null; /** * Constructor. - * - * @param string|null $attribute - * @param string|int|null $type - * @param array $values */ - public function __construct($attribute = null, $type = null, array $values = []) + public function __construct(?string $attribute = null, ?int $type = null, array $values = []) { $this->setAttribute($attribute) ->setType($type) @@ -58,12 +48,17 @@ class BatchModification } /** - * Set the original value of the attribute before modification. - * - * @param array|string $original - * @return $this + * Make a new batch modification instance. */ - public function setOriginal($original = []) + 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); @@ -71,22 +66,17 @@ class BatchModification } /** - * Returns the original value of the attribute before modification. - * - * @return array + * Get the original value of the attribute before modification. */ - public function getOriginal() + public function getOriginal(): array { return $this->original; } /** * Set the attribute of the modification. - * - * @param string $attribute - * @return $this */ - public function setAttribute($attribute) + public function setAttribute(?string $attribute = null): static { $this->attribute = $attribute; @@ -94,64 +84,51 @@ class BatchModification } /** - * Returns the attribute of the modification. - * - * @return string + * Get the attribute of the modification. */ - public function getAttribute() + public function getAttribute(): string { return $this->attribute; } /** * Set the values of the modification. - * - * @param array $values - * @return $this */ - public function setValues(array $values = []) + 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 is_numeric($value) && $this->valueIsResetInteger((int) $value) || ! empty($value); }); return $this; } /** - * Normalize all of the attribute values. - * - * @param array|string $values - * @return array + * Normalize all the attribute values. */ - protected function normalizeAttributeValues($values = []) + protected function normalizeAttributeValues(array|string $values = []): array { - // We must convert all of the values to strings. Only strings can + // 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); } /** - * Returns the values of the modification. - * - * @return array + * Get the values of the modification. */ - public function getValues() + public function getValues(): array { return $this->values; } /** * Set the type of the modification. - * - * @param int|null $type - * @return $this */ - public function setType($type = null) + public function setType(?int $type = null): static { if (is_null($type)) { return $this; @@ -167,21 +144,17 @@ class BatchModification } /** - * Returns the type of the modification. - * - * @return int + * Get the type of the modification. */ - public function getType() + public function getType(): ?int { return $this->type; } /** - * Determines if the batch modification is valid in its current state. - * - * @return bool + * Determine if the batch modification is valid in its current state. */ - public function isValid() + public function isValid(): bool { return ! is_null($this->get()); } @@ -189,51 +162,43 @@ class BatchModification /** * Builds the type of modification automatically * based on the current and original values. - * - * @return $this */ - public function build() + public function build(): static { - switch (true) { - case empty($this->original) && empty($this->values): - return $this; - case ! empty($this->original) && empty($this->values): - return $this->setType(LDAP_MODIFY_BATCH_REMOVE_ALL); - case empty($this->original) && ! empty($this->values): - return $this->setType(LDAP_MODIFY_BATCH_ADD); - default: - return $this->determineBatchTypeFromOriginal(); - } + 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. - * - * @return $this */ - protected function determineBatchTypeFromOriginal() + protected function determineBatchTypeFromOriginal(): static { $added = $this->getAddedValues(); $removed = $this->getRemovedValues(); - switch (true) { - case ! empty($added) && ! empty($removed): - return $this->setType(LDAP_MODIFY_BATCH_REPLACE); - case ! empty($added): - return $this->setValues($added)->setType(LDAP_MODIFY_BATCH_ADD); - case ! empty($removed): - return $this->setValues($removed)->setType(LDAP_MODIFY_BATCH_REMOVE); - default: - return $this; - } + 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. - * - * @return array */ - protected function getAddedValues() + protected function getAddedValues(): array { return array_values( array_diff($this->values, $this->original) @@ -242,10 +207,8 @@ class BatchModification /** * Get the values that were removed from the attribute. - * - * @return array */ - protected function getRemovedValues() + protected function getRemovedValues(): array { return array_values( array_diff($this->original, $this->values) @@ -253,43 +216,30 @@ class BatchModification } /** - * Returns the built batch modification array. - * - * @return array|null + * Get the batch modification array. */ - public function get() + public function get(): ?array { - 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; - } + 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, + }; } /** - * Determines if the given modtype is valid. - * - * @param int $type - * @return bool + * Determine if the given modtype is valid. */ - protected function isValidType($type) + protected function isValidType(int $type): bool { return in_array($type, [ LDAP_MODIFY_BATCH_REMOVE_ALL, 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 index 11b4ca522..a0d8126a0 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Collection.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Collection.php @@ -11,10 +11,8 @@ class Collection extends QueryCollection { /** * Get a collection of the model's distinguished names. - * - * @return static */ - public function modelDns() + public function modelDns(): static { return $this->map(function (Model $model) { return $model->getDn(); @@ -22,12 +20,11 @@ class Collection extends QueryCollection } /** - * Determine if the collection contains all of the given models, or any models. + * Determine if the collection contains all the given models, or any models. * - * @param mixed $models - * @return bool + * @param QueryCollection|Model|array|string|null $models */ - public function exists($models = null) + public function exists(mixed $models = null): bool { $models = $this->getArrayableModels($models); @@ -57,19 +54,11 @@ class Collection extends QueryCollection /** * Determine if any of the given models are contained in the collection. - * - * @param mixed $key - * @param mixed $operator - * @param mixed $value - * @return bool */ - public function contains($key, $operator = null, $value = null) + public function contains($key, $operator = null, $value = null): bool { if (func_num_args() > 1 || $key instanceof Closure) { - // If we are supplied with more than one argument, or - // we were passed a closure, we will utilize the - // parents contains method, for compatibility. - return parent::contains($key, $operator, $value); + return parent::contains(...func_get_args()); } foreach ($this->getArrayableModels($key) as $model) { @@ -87,25 +76,20 @@ class Collection extends QueryCollection /** * Get the provided models as an array. - * - * @param mixed $models - * @return array */ - protected function getArrayableModels($models = null) + protected function getArrayableModels(mixed $models = null): array { - return $models instanceof QueryCollection - ? $models->toArray() - : Arr::wrap($models); + if ($models instanceof QueryCollection) { + return $models->all(); + } + + return Arr::wrap($models); } /** * Compare the related model with the given. - * - * @param Model|string $model - * @param Model $related - * @return bool */ - protected function compareModelWithRelated($model, $related) + protected function compareModelWithRelated(Model|string $model, Model $related): bool { if (is_string($model)) { return $this->isValidDn($model) @@ -118,11 +102,8 @@ class Collection extends QueryCollection /** * Determine if the given string is a valid distinguished name. - * - * @param string $dn - * @return bool */ - protected function isValidDn($dn) + 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 index 6e8d4fb5d..e52657a44 100644 --- 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 @@ -7,60 +7,58 @@ trait CanAuthenticate { /** * Get the name of the unique identifier for the user. - * - * @return string */ - public function getAuthIdentifierName() + public function getAuthIdentifierName(): string { return $this->guidKey; } /** * Get the unique identifier for the user. - * - * @return string */ - public function getAuthIdentifier() + public function getAuthIdentifier(): string { - return $this->getConvertedGuid(); + return $this->getConvertedGuid( + $this->getFirstAttribute($this->getAuthIdentifierName()) + ); } /** * Get the password for the user. - * - * @return string */ - public function getAuthPassword() + public function getAuthPassword(): string { return ''; } /** - * Get the token value for the "remember me" session. - * - * @return string + * Get the name of the password attribute for the user. */ - public function getRememberToken() + 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. - * - * @param string $value - * @return void */ - public function setRememberToken($value) + public function setRememberToken($value): void { + // Do nothing. } /** * Get the column name for the "remember me" token. - * - * @return string */ - public function getRememberTokenName() + 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 index a3d9df7d9..67b4cf703 100644 --- 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 @@ -10,6 +10,7 @@ use LdapRecord\Models\Attributes\MbString; use LdapRecord\Models\Attributes\Timestamp; use LdapRecord\Models\DetectsResetIntegers; use LdapRecord\Support\Arr; +use RuntimeException; trait HasAttributes { @@ -17,69 +18,56 @@ trait HasAttributes /** * The models original attributes. - * - * @var array */ - protected $original = []; + protected array $original = []; + + /** + * The models changed attributes. + */ + protected array $changes = []; /** * The models attributes. - * - * @var array */ - protected $attributes = []; + protected array $attributes = []; /** * The attributes that should be mutated to dates. - * - * @var array */ - protected $dates = []; + protected array $dates = []; /** * The attributes that should be cast to their native types. - * - * @var array */ - protected $casts = []; + protected array $casts = []; /** * The accessors to append to the model's array form. - * - * @var array */ - protected $appends = []; + protected array $appends = []; /** * The format that dates must be output to for serialization. - * - * @var string */ - protected $dateFormat; + protected ?string $dateFormat = null; /** * The default attributes that should be mutated to dates. - * - * @var array */ - protected $defaultDates = [ + protected array $defaultDates = [ 'createtimestamp' => 'ldap', 'modifytimestamp' => 'ldap', ]; /** * The cache of the mutated attributes for each class. - * - * @var array */ - protected static $mutatorCache = []; + protected static array $mutatorCache = []; /** * Convert the model's original attributes to an array. - * - * @return array */ - public function originalToArray() + public function originalToArray(): array { return $this->encodeAttributes( $this->convertAttributesForJson($this->original) @@ -88,16 +76,14 @@ trait HasAttributes /** * Convert the model's attributes to an array. - * - * @return array */ - public function attributesToArray() + 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( - $attributes = $this->getArrayableAttributes() + $this->getArrayableAttributes() ); $attributes = $this->addMutatedAttributesToArray( @@ -125,39 +111,30 @@ trait HasAttributes /** * Convert the model's serialized original attributes to their original form. - * - * @param array $attributes - * @return array */ - public function arrayToOriginal(array $attributes) + public function arrayToOriginal(array $attributes): array { - return $this->decodeAttributes( - $this->convertAttributesFromJson($attributes) - ); + $attributes = $this->decodeAttributes($attributes); + + return $this->convertAttributesFromJson($attributes); } /** * Convert the model's serialized attributes to their original form. - * - * @param array $attributes - * @return array */ - public function arrayToAttributes(array $attributes) + public function arrayToAttributes(array $attributes): array { $attributes = $this->restoreDateAttributesFromArray($attributes); - return $this->decodeAttributes( - $this->convertAttributesFromJson($attributes) - ); + $attributes = $this->decodeAttributes($attributes); + + return $this->convertAttributesFromJson($attributes); } /** * Add the date attributes to the attributes array. - * - * @param array $attributes - * @return array */ - protected function addDateAttributesToArray(array $attributes) + protected function addDateAttributesToArray(array $attributes): array { foreach ($this->getDates() as $attribute => $type) { if (! isset($attributes[$attribute])) { @@ -176,18 +153,15 @@ trait HasAttributes /** * Restore the date attributes to their true value from serialized attributes. - * - * @param array $attributes - * @return array */ - protected function restoreDateAttributesFromArray(array $attributes) + protected function restoreDateAttributesFromArray(array $attributes): array { foreach ($this->getDates() as $attribute => $type) { if (! isset($attributes[$attribute])) { continue; } - $date = $this->fromDateTime($type, $attributes[$attribute]); + $date = $this->fromDateTime($attributes[$attribute], $type); $attributes[$attribute] = Arr::wrap($date); } @@ -197,21 +171,16 @@ trait HasAttributes /** * Prepare a date for array / JSON serialization. - * - * @param DateTimeInterface $date - * @return string */ - protected function serializeDate(DateTimeInterface $date) + protected function serializeDate(DateTimeInterface $date): string { return $date->format($this->getDateFormat()); } /** * Recursively UTF-8 encode the given attributes. - * - * @return array */ - public function encodeAttributes($attributes) + protected function encodeAttributes($attributes): array { array_walk_recursive($attributes, function (&$value) { $value = $this->encodeValue($value); @@ -222,11 +191,8 @@ trait HasAttributes /** * Recursively UTF-8 decode the given attributes. - * - * @param array $attributes - * @return array */ - public function decodeAttributes($attributes) + public function decodeAttributes(array $attributes): array { array_walk_recursive($attributes, function (&$value) { $value = $this->decodeValue($value); @@ -237,11 +203,8 @@ trait HasAttributes /** * Encode the value for serialization. - * - * @param string $value - * @return string */ - protected function encodeValue($value) + protected function encodeValue(string $value): string { // If we are able to detect the encoding, we will // encode only the attributes that need to be, @@ -250,18 +213,15 @@ trait HasAttributes return $value; } - return utf8_encode($value); + return mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1'); } /** * Decode the value from serialization. - * - * @param string $value - * @return string */ - protected function decodeValue($value) + protected function decodeValue(string $value): string { - if (MbString::isLoaded() && MbString::isUtf8($value)) { + if (MbString::isLoaded() && ! MbString::isUtf8($value)) { return mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1'); } @@ -270,12 +230,8 @@ trait HasAttributes /** * Add the mutated attributes to the attributes array. - * - * @param array $attributes - * @param array $mutatedAttributes - * @return array */ - protected function addMutatedAttributesToArray(array $attributes, array $mutatedAttributes) + 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 @@ -299,10 +255,8 @@ trait HasAttributes /** * Set the model's original attributes with the model's current attributes. - * - * @return $this */ - public function syncOriginal() + public function syncOriginal(): static { $this->original = $this->attributes; @@ -310,12 +264,19 @@ trait HasAttributes } /** - * Fills the entry with the supplied attributes. - * - * @param array $attributes - * @return $this + * Sync the changed attributes. */ - public function fill(array $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); @@ -325,29 +286,21 @@ trait HasAttributes } /** - * Returns the models attribute by its key. - * - * @param int|string $key - * @param mixed $default - * @return mixed + * Get the models attribute by its key. */ - public function getAttribute($key, $default = null) + public function getAttribute(?string $key = null, mixed $default = null): mixed { if (! $key) { - return; + return null; } return $this->getAttributeValue($key, $default); } /** - * Get an attributes value. - * - * @param string $key - * @param mixed $default - * @return mixed + * Get an attribute's value. */ - public function getAttributeValue($key, $default = null) + public function getAttributeValue(string $key, mixed $default = null): mixed { $key = $this->normalizeAttributeKey($key); $value = $this->getAttributeFromArray($key); @@ -368,42 +321,56 @@ trait HasAttributes } /** - * Determine if the given attribute is a date. - * - * @param string $key - * @return bool + * Get the model's raw attribute value. */ - public function isDateAttribute($key) + 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. - * - * @return array */ - public function getDates() + 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, CASE_LOWER), - array_change_key_case($this->dates, CASE_LOWER) + 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. * - * @param string $type - * @param mixed $value - * @return float|string - * * @throws LdapRecordException */ - public function fromDateTime($type, $value) + public function fromDateTime(mixed $value, string $type): float|int|string { return (new Timestamp($type))->fromDateTime($value); } @@ -411,28 +378,20 @@ trait HasAttributes /** * Convert the given LDAP date value to a Carbon instance. * - * @param mixed $value - * @param string $type - * @return Carbon|false - * * @throws LdapRecordException */ - public function asDateTime($value, $type) + 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. - * - * @param string $key - * @param array|string|null $types - * @return bool */ - public function hasCast($key, $types = null) + 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) : true; + return ! $types || in_array($this->getCastType($key), (array) $types, true); } return false; @@ -440,32 +399,24 @@ trait HasAttributes /** * Get the attributes that should be cast to their native types. - * - * @return array */ - protected function getCasts() + protected function getCasts(): array { - return array_change_key_case($this->casts, CASE_LOWER); + return array_change_key_case($this->casts); } /** * Determine whether a value is JSON castable for inbound manipulation. - * - * @param string $key - * @return bool */ - protected function isJsonCastable($key) + protected function isJsonCastable(string $key): bool { return $this->hasCast($key, ['array', 'json', 'object', 'collection']); } /** * Get the type of cast for a model attribute. - * - * @param string $key - * @return string */ - protected function getCastType($key) + protected function getCastType(string $key): string { if ($this->isDecimalCast($this->getCasts()[$key])) { return 'decimal'; @@ -480,45 +431,32 @@ trait HasAttributes /** * Determine if the cast is a decimal. - * - * @param string $cast - * @return bool */ - protected function isDecimalCast($cast) + protected function isDecimalCast(string $cast): bool { return strncmp($cast, 'decimal:', 8) === 0; } /** * Determine if the cast is a datetime. - * - * @param string $cast - * @return bool */ - protected function isDateTimeCast($cast) + protected function isDateTimeCast(string $cast): bool { return strncmp($cast, 'datetime:', 8) === 0; } /** * Determine if the given attribute must be casted. - * - * @param string $key - * @return bool */ - protected function isCastedAttribute($key) + protected function isCastedAttribute(string $key): bool { - return array_key_exists($key, array_change_key_case($this->casts, CASE_LOWER)); + return array_key_exists($key, array_change_key_case($this->casts)); } /** * Cast an attribute to a native PHP type. - * - * @param string $key - * @param array|null $value - * @return mixed */ - protected function castAttribute($key, $value) + protected function castAttribute(string $key, ?array $value): mixed { $value = $this->castRequiresArrayValue($key) ? $value : Arr::first($value); @@ -542,7 +480,7 @@ trait HasAttributes case 'boolean': return $this->asBoolean($value); case 'object': - return $this->fromJson($value, $asObject = true); + return $this->fromJson($value, true); case 'array': case 'json': return $this->fromJson($value); @@ -557,22 +495,16 @@ trait HasAttributes /** * Determine if the cast type requires the first attribute value. - * - * @return bool */ - protected function castRequiresArrayValue($key) + protected function castRequiresArrayValue(string $key): bool { - return in_array($this->getCastType($key), ['collection']); + return $this->getCastType($key) === 'collection'; } /** * Cast the given attribute to JSON. - * - * @param string $key - * @param mixed $value - * @return string */ - protected function castAttributeAsJson($key, $value) + protected function castAttributeAsJson(string $key, mixed $value): string { $value = $this->asJson($value); @@ -580,107 +512,104 @@ trait HasAttributes $class = get_class($this); $message = json_last_error_msg(); - throw new Exception("Unable to encode attribute [{$key}] for model [{$class}] to JSON: {$message}."); + throw new Exception("Unable to encode attribute [$key] for model [$class] to JSON: $message."); } return $value; } /** - * Convert the model to its JSON representation. - * - * @return string + * Cast the given attribute to an LDAP primitive type. */ - public function toJson() + 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. - * - * @param mixed $value - * @return string */ - protected function asJson($value) + protected function asJson(mixed $value): string|false { return json_encode($value); } /** * Decode the given JSON back into an array or object. - * - * @param string $value - * @param bool $asObject - * @return mixed */ - public function fromJson($value, $asObject = false) + public function fromJson(string $value, bool $asObject = false): mixed { return json_decode($value, ! $asObject); } /** * Decode the given float. - * - * @param mixed $value - * @return mixed */ - public function fromFloat($value) + public function fromFloat(float $value): float { - switch ((string) $value) { - case 'Infinity': - return INF; - case '-Infinity': - return -INF; - case 'NaN': - return NAN; - default: - return (float) $value; - } + return match ((string) $value) { + 'NaN' => NAN, + 'Infinity' => INF, + '-Infinity' => -INF, + default => $value, + }; } /** - * Cast the value to a boolean. - * - * @param mixed $value - * @return bool + * Cast the value from an LDAP boolean string to a primitive boolean. */ - protected function asBoolean($value) + protected function asBoolean(mixed $value): bool { - $map = ['true' => true, 'false' => false]; + return match (strtolower($value)) { + 'true' => true, + 'false' => false, + default => (bool) $value, + }; + } - return $map[strtolower($value)] ?? (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. - * - * @param float $value - * @param int $decimals - * @return string */ - protected function asDecimal($value, $decimals) + protected function asDecimal(float $value, int $decimals): string { return number_format($value, $decimals, '.', ''); } /** * Get an attribute array of all arrayable attributes. - * - * @return array */ - protected function getArrayableAttributes() + protected function getArrayableAttributes(): array { return $this->getArrayableItems($this->attributes); } /** * Get an attribute array of all arrayable values. - * - * @param array $values - * @return array */ - protected function getArrayableItems(array $values) + protected function getArrayableItems(array $values): array { if (count($visible = $this->getVisible()) > 0) { $values = array_intersect_key($values, array_flip($visible)); @@ -694,11 +623,9 @@ trait HasAttributes } /** - * Get all of the appendable values that are arrayable. - * - * @return array + * Get all the appendable values that are arrayable. */ - protected function getArrayableAppends() + protected function getArrayableAppends(): array { if (empty($this->appends)) { return []; @@ -711,21 +638,16 @@ trait HasAttributes /** * Get the format for date serialization. - * - * @return string */ - public function getDateFormat() + public function getDateFormat(): string { return $this->dateFormat ?: DateTimeInterface::ISO8601; } /** * Set the date format used by the model for serialization. - * - * @param string $format - * @return $this */ - public function setDateFormat($format) + public function setDateFormat(string $format): static { $this->dateFormat = $format; @@ -734,33 +656,24 @@ trait HasAttributes /** * Get an attribute from the $attributes array. - * - * @param string $key - * @return mixed */ - protected function getAttributeFromArray($key) + protected function getAttributeFromArray(string $key): mixed { return $this->getNormalizedAttributes()[$key] ?? null; } /** * Get the attributes with their keys normalized. - * - * @return array */ - protected function getNormalizedAttributes() + protected function getNormalizedAttributes(): array { - return array_change_key_case($this->attributes, CASE_LOWER); + return array_change_key_case($this->attributes); } /** - * Returns the first attribute by the specified key. - * - * @param string $key - * @param mixed $default - * @return mixed + * Get the first attribute by the specified key. */ - public function getFirstAttribute($key, $default = null) + public function getFirstAttribute(string $key, mixed $default = null): mixed { return Arr::first( Arr::wrap($this->getAttribute($key, $default)), @@ -768,38 +681,36 @@ trait HasAttributes } /** - * Returns all of the models attributes. - * - * @return array + * Returns all the model's attributes. */ - public function getAttributes() + public function getAttributes(): array { return $this->attributes; } /** * Set an attribute value by the specified key. - * - * @param string $key - * @param mixed $value - * @return $this */ - public function setAttribute($key, $value) + public function setAttribute(string $key, mixed $value): static { $key = $this->normalizeAttributeKey($key); if ($this->hasSetMutator($key)) { return $this->setMutatedAttributeValue($key, $value); - } elseif ( + } + + if ( $value && $this->isDateAttribute($key) && ! $this->valueIsResetInteger($value) ) { - $value = $this->fromDateTime($this->getDates()[$key], $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); @@ -809,12 +720,8 @@ trait HasAttributes /** * Set an attribute on the model. No checking is done. - * - * @param string $key - * @param mixed $value - * @return $this */ - public function setRawAttribute($key, $value) + public function setRawAttribute(string $key, mixed $value): static { $key = $this->normalizeAttributeKey($key); @@ -825,75 +732,71 @@ trait HasAttributes /** * Set the models first attribute value. - * - * @param string $key - * @param mixed $value - * @return $this */ - public function setFirstAttribute($key, $value) + public function setFirstAttribute(string $key, mixed $value): static { return $this->setAttribute($key, Arr::wrap($value)); } /** * Add a unique value to the given attribute. - * - * @param string $key - * @param mixed $value - * @return $this */ - public function addAttributeValue($key, $value) + public function addAttributeValue(string $key, mixed $value): static { - return $this->setAttribute($key, array_unique( - array_merge( - Arr::wrap($this->getAttribute($key)), - Arr::wrap($value) - ) - )); + 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. - * - * @param string $key - * @return bool */ - public function hasGetMutator($key) + public function hasGetMutator(string $key): bool { return method_exists($this, 'get'.$this->getMutatorMethodName($key).'Attribute'); } /** * Determine if a set mutator exists for an attribute. - * - * @param string $key - * @return bool */ - public function hasSetMutator($key) + public function hasSetMutator(string $key): bool { return method_exists($this, 'set'.$this->getMutatorMethodName($key).'Attribute'); } /** * Set the value of an attribute using its mutator. - * - * @param string $key - * @param mixed $value - * @return mixed */ - protected function setMutatedAttributeValue($key, $value) + protected function setMutatedAttributeValue(string $key, mixed $value): static { - return $this->{'set'.$this->getMutatorMethodName($key).'Attribute'}($value); + $this->{'set'.$this->getMutatorMethodName($key).'Attribute'}($value); + + return $this; } /** * Get the value of an attribute using its mutator. - * - * @param string $key - * @param mixed $value - * @return mixed */ - protected function getMutatedAttributeValue($key, $value) + protected function getMutatedAttributeValue(string $key, mixed $value): mixed { return $this->{'get'.$this->getMutatorMethodName($key).'Attribute'}($value); } @@ -902,11 +805,8 @@ trait HasAttributes * Get the mutator attribute method name. * * Hyphenated attributes will use pascal cased methods. - * - * @param string $key - * @return mixed */ - protected function getMutatorMethodName($key) + protected function getMutatorMethodName(string $key): string { $key = ucwords(str_replace('-', ' ', $key)); @@ -915,12 +815,8 @@ trait HasAttributes /** * Get the value of an attribute using its mutator for array conversion. - * - * @param string $key - * @param mixed $value - * @return array */ - protected function mutateAttributeForArray($key, $value) + protected function mutateAttributeForArray(string $key, mixed $value): array { return Arr::wrap( $this->getMutatedAttributeValue($key, $value) @@ -928,27 +824,22 @@ trait HasAttributes } /** - * Set the attributes property. + * Set the raw model attributes. * * Used when constructing an existing LDAP record. - * - * @param array $attributes - * @return $this */ - public function setRawAttributes(array $attributes = []) + 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), CASE_LOWER); + $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, function ($key) { - return ! is_int($key); - }, ARRAY_FILTER_USE_KEY); + $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, @@ -971,12 +862,8 @@ trait HasAttributes /** * Filters the count key recursively from raw LDAP attributes. - * - * @param array $attributes - * @param array $keys - * @return array */ - public function filterRawAttributes(array $attributes = [], array $keys = ['count', 'dn']) + public function filterRawAttributes(array $attributes = [], array $keys = ['count', 'dn']): array { foreach ($keys as $key) { unset($attributes[$key]); @@ -993,41 +880,40 @@ trait HasAttributes /** * Determine if the model has the given attribute. - * - * @param int|string $key - * @return bool */ - public function hasAttribute($key) + public function hasAttribute(int|string $key): bool { - return [] !== ($this->attributes[$this->normalizeAttributeKey($key)] ?? []); + return ($this->attributes[$this->normalizeAttributeKey($key)] ?? []) !== []; } /** - * Returns the number of attributes. - * - * @return int + * Get the number of attributes. */ - public function countAttributes() + public function countAttributes(): int { return count($this->getAttributes()); } /** - * Returns the models original attributes. - * - * @return array + * Get the model's original attributes. */ - public function getOriginal() + public function getOriginal(): array { return $this->original; } /** - * Get the attributes that have been changed since last sync. - * - * @return array + * Get the model's raw original attribute values. */ - public function getDirty() + 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 = []; @@ -1044,33 +930,69 @@ trait HasAttributes } /** - * Determine if the given attribute is dirty. - * - * @param string $key - * @return bool + * Get the attributes that have been changed since the model was last saved. */ - public function isDirty($key) + public function getChanges(): array + { + return $this->changes; + } + + /** + * Determine if the given attribute is dirty. + */ + public function isDirty(string $key): bool { return ! $this->originalIsEquivalent($key); } /** - * Get the accessors being appended to the models array form. - * - * @return array + * Determine if given attribute has remained the same. */ - public function getAppends() + 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. - * - * @param array $appends - * @return $this */ - public function setAppends(array $appends) + public function setAppends(array $appends): static { $this->appends = $appends; @@ -1079,22 +1001,16 @@ trait HasAttributes /** * Return whether the accessor attribute has been appended. - * - * @param string $attribute - * @return bool */ - public function hasAppended($attribute) + public function hasAppended(string $attribute): bool { return in_array($attribute, $this->appends); } /** * Returns a normalized attribute key. - * - * @param string $key - * @return string */ - public function normalizeAttributeKey($key) + public function normalizeAttributeKey(string $key): string { // Since LDAP supports hyphens in attribute names, // we'll convert attributes being retrieved by @@ -1106,11 +1022,8 @@ trait HasAttributes /** * Determine if the new and old values for a given key are equivalent. - * - * @param string $key - * @return bool */ - protected function originalIsEquivalent($key) + protected function originalIsEquivalent(string $key): bool { if (! array_key_exists($key, $this->original)) { return false; @@ -1123,17 +1036,15 @@ trait HasAttributes return true; } - return is_numeric($current) && + return is_numeric($current) && is_numeric($original) && strcmp((string) $current, (string) $original) === 0; } /** * Get the mutated attributes for a given instance. - * - * @return array */ - public function getMutatedAttributes() + public function getMutatedAttributes(): array { $class = static::class; @@ -1146,26 +1057,19 @@ trait HasAttributes /** * Extract and cache all the mutated attributes of a class. - * - * @param string $class - * @return void */ - public static function cacheMutatedAttributes($class) + public static function cacheMutatedAttributes(string $class): void { - static::$mutatorCache[$class] = collect(static::getMutatorMethods($class))->reject(function ($match) { - return $match === 'First'; - })->map(function ($match) { - return lcfirst($match); - })->all(); + 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. - * - * @param mixed $class - * @return array */ - protected static function getMutatorMethods($class) + protected static function getMutatorMethods(string $class): array { preg_match_all('/(?<=^|;)get([^;]+?)Attribute(;|$)/', implode(';', get_class_methods($class)), $matches); 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 index d9a41b892..7537f62f6 100644 --- 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 @@ -13,18 +13,15 @@ trait HasEvents { /** * Execute the callback without raising any events. - * - * @param Closure $callback - * @return mixed */ - protected static function withoutEvents(Closure $callback) + protected static function withoutEvents(Closure $callback): mixed { $container = static::getConnectionContainer(); - $dispatcher = $container->getEventDispatcher(); + $dispatcher = $container->getDispatcher(); if ($dispatcher) { - $container->setEventDispatcher( + $container->setDispatcher( new NullDispatcher($dispatcher) ); } @@ -33,19 +30,15 @@ trait HasEvents return $callback(); } finally { if ($dispatcher) { - $container->setEventDispatcher($dispatcher); + $container->setDispatcher($dispatcher); } } } /** * Dispatch the given model events. - * - * @param string|array $events - * @param array $args - * @return void */ - protected function dispatch($events, array $args = []) + protected function dispatch(array|string $events, array $args = []): void { foreach (Arr::wrap($events) as $name) { $this->fireCustomModelEvent($name, $args); @@ -54,38 +47,27 @@ trait HasEvents /** * Fire a custom model event. - * - * @param string $name - * @param array $args - * @return mixed */ - protected function fireCustomModelEvent($name, array $args = []) + protected function fireCustomModelEvent(string $name, array $args = []): void { $event = implode('\\', [Events::class, ucfirst($name)]); - return $this->fireModelEvent(new $event($this, ...$args)); + $this->fireModelEvent(new $event($this, ...$args)); } /** * Fire a model event. - * - * @param Event $event - * @return mixed */ - protected function fireModelEvent(Event $event) + protected function fireModelEvent(Event $event): void { - return static::getConnectionContainer()->getEventDispatcher()->fire($event); + static::getConnectionContainer()->getDispatcher()->fire($event); } /** * Listen to a model event. - * - * @param string $event - * @param Closure $listener - * @return mixed */ - protected function listenForModelEvent($event, Closure $listener) + protected function listenForModelEvent(string $event, Closure $listener): void { - return static::getConnectionContainer()->getEventDispatcher()->listen($event, $listener); + 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 index 4d0f2968f..855ba37c3 100644 --- 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 @@ -12,43 +12,33 @@ trait HasGlobalScopes /** * Register a new global scope on the model. * - * @param Scope|Closure|string $scope - * @param Closure|null $implementation - * @return mixed - * * @throws InvalidArgumentException */ - public static function addGlobalScope($scope, Closure $implementation = null) + public static function addGlobalScope(Scope|Closure|string $scope, ?Closure $implementation = null): void { if (is_string($scope) && ! is_null($implementation)) { - return static::$globalScopes[static::class][$scope] = $implementation; + static::$globalScopes[static::class][$scope] = $implementation; } elseif ($scope instanceof Closure) { - return static::$globalScopes[static::class][spl_object_hash($scope)] = $scope; + static::$globalScopes[static::class][spl_object_hash($scope)] = $scope; } elseif ($scope instanceof Scope) { - return static::$globalScopes[static::class][get_class($scope)] = $scope; + static::$globalScopes[static::class][get_class($scope)] = $scope; + } else { + throw new InvalidArgumentException('Global scope must be an instance of Closure or Scope.'); } - - throw new InvalidArgumentException('Global scope must be an instance of Closure or Scope.'); } /** * Determine if a model has a global scope. - * - * @param Scope|string $scope - * @return bool */ - public static function hasGlobalScope($scope) + public static function hasGlobalScope(Scope|string $scope): bool { return ! is_null(static::getGlobalScope($scope)); } /** * Get a global scope registered with the model. - * - * @param Scope|string $scope - * @return Scope|Closure|null */ - public static function getGlobalScope($scope) + public static function getGlobalScope(Scope|string $scope): Scope|Closure|null { if (array_key_exists(static::class, static::$globalScopes)) { $scopeName = is_string($scope) ? $scope : get_class($scope); @@ -57,14 +47,14 @@ trait HasGlobalScopes ? static::$globalScopes[static::class][$scopeName] : null; } + + return null; } /** * Get the global scopes for this class instance. - * - * @return array */ - public function getGlobalScopes() + public function getGlobalScopes(): array { return array_key_exists(static::class, static::$globalScopes) ? static::$globalScopes[static::class] diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasPassword.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasPassword.php index 194efa8cf..c84bd977d 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasPassword.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasPassword.php @@ -12,13 +12,11 @@ trait HasPassword /** * Set the password on the user. * - * @param string|array $password - * * @throws ConnectionException */ - public function setPasswordAttribute($password) + public function setPasswordAttribute(array|string $password): void { - $this->validateSecureConnection(); + $this->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 @@ -49,31 +47,25 @@ trait HasPassword /** * Alias for setting the password on the user. * - * @param string|array $password - * * @throws ConnectionException */ - public function setUnicodepwdAttribute($password) + public function setUnicodepwdAttribute(array|string $password): void { $this->setPasswordAttribute($password); } /** * An accessor for retrieving the user's hashed password value. - * - * @return string|null */ - public function getPasswordAttribute() + public function getPasswordAttribute(): ?string { return $this->getAttribute($this->getPasswordAttributeName())[0] ?? null; } /** * Get the name of the attribute that contains the user's password. - * - * @return string */ - public function getPasswordAttributeName() + public function getPasswordAttributeName(): string { if (property_exists($this, 'passwordAttribute')) { return $this->passwordAttribute; @@ -88,10 +80,8 @@ trait HasPassword /** * Get the name of the method to use for hashing the user's password. - * - * @return string */ - public function getPasswordHashMethod() + public function getPasswordHashMethod(): string { if (property_exists($this, 'passwordHashMethod')) { return $this->passwordHashMethod; @@ -106,13 +96,8 @@ trait HasPassword /** * Set the changed password. - * - * @param string $oldPassword - * @param string $newPassword - * @param string $attribute - * @return void */ - protected function setChangedPassword($oldPassword, $newPassword, $attribute) + protected function setChangedPassword(string $oldPassword, string $newPassword, string $attribute): void { // Create batch modification for removing the old password. $this->addModification( @@ -135,13 +120,15 @@ trait HasPassword /** * Set the password on the model. - * - * @param string $password - * @param string $attribute - * @return void */ - protected function setPassword($password, $attribute) + protected function setPassword(string $password, string $attribute): void { + if (! $this->exists) { + $this->setRawAttribute($attribute, $password); + + return; + } + $this->addModification( $this->newBatchModification( $attribute, @@ -154,14 +141,9 @@ trait HasPassword /** * Encode / hash the given password. * - * @param string $method - * @param string $password - * @param string $salt - * @return string - * * @throws LdapRecordException */ - protected function getHashedPassword($method, $password, $salt = null) + 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."); @@ -177,18 +159,22 @@ trait HasPassword /** * Validates that the current LDAP connection is secure. * - * @return void - * * @throws ConnectionException */ - protected function validateSecureConnection() + 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 = $connection->getConfiguration()->get('use_ssl') || $connection->getConfiguration()->get('use_tls'); + $secure = $config->get('use_ssl') || $config->get('use_tls'); } if (! $secure) { @@ -200,14 +186,11 @@ trait HasPassword /** * Attempt to retrieve the password's salt. - * - * @param string $method - * @return string|null */ - public function getPasswordSalt($method) + public function getPasswordSalt(string $method): ?string { if (! Password::hashMethodRequiresSalt($method)) { - return; + return null; } return Password::getSalt($this->password); @@ -234,15 +217,11 @@ trait HasPassword $value = null ); - switch ($algo) { - case Password::CRYPT_SALT_TYPE_MD5: - return 'md5'.$method; - case Password::CRYPT_SALT_TYPE_SHA256: - return 'sha256'.$method; - case Password::CRYPT_SALT_TYPE_SHA512: - return 'sha512'.$method; - default: - return $method; - } + 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 index 87c0a7cc8..e81c3a15e 100644 --- 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 @@ -12,61 +12,47 @@ trait HasRelationships { /** * Returns a new has one relationship. - * - * @param mixed $related - * @param string $relationKey - * @param string $foreignKey - * @return HasOne */ - public function hasOne($related, $relationKey, $foreignKey = 'dn') + public function hasOne(array|string $related, string $relationKey, string $foreignKey = 'dn'): HasOne { return new HasOne($this->newQuery(), $this, $related, $relationKey, $foreignKey); } /** * Returns a new has many relationship. - * - * @param mixed $related - * @param string $relationKey - * @param string $foreignKey - * @return HasMany */ - public function hasMany($related, $relationKey, $foreignKey = 'dn') + 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. - * - * @param mixed $related - * @param string $relationKey - * @param string $foreignKey - * @return HasManyIn */ - public function hasManyIn($related, $relationKey, $foreignKey = 'dn') + 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. - * - * @param string $relationName - * @return Relation|null */ - public function getRelation($relationName) + public function getRelation(?string $relationName = null): ?Relation { + if (is_null($relationName)) { + return null; + } + if (! method_exists($this, $relationName)) { - return; + return null; } if (! $relation = $this->{$relationName}()) { - return; + return null; } if (! $relation instanceof Relation) { - return; + return null; } return $relation; @@ -74,10 +60,8 @@ trait HasRelationships /** * Get the relationships name. - * - * @return string|null */ - protected function guessRelationshipName() + 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 index cb227fecc..7420620ce 100644 --- 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 @@ -2,38 +2,34 @@ namespace LdapRecord\Models\Concerns; +use LdapRecord\Query\Model\Builder; + /** @mixin \LdapRecord\Models\Model */ trait HasScopes { /** * Begin querying the direct descendants of the model. - * - * @return \LdapRecord\Query\Model\Builder */ - public function descendants() + public function descendants(): Builder { - return $this->in($this->getDn())->listing(); + return $this->in($this->getDn())->list(); } /** * Begin querying the direct ancestors of the model. - * - * @return \LdapRecord\Query\Model\Builder */ - public function ancestors() + public function ancestors(): Builder { $parent = $this->getParentDn($this->getDn()); - return $this->in($this->getParentDn($parent))->listing(); + return $this->in($this->getParentDn($parent))->list(); } /** * Begin querying the direct siblings of the model. - * - * @return \LdapRecord\Query\Model\Builder */ - public function siblings() + public function siblings(): Builder { - return $this->in($this->getParentDn($this->getDn()))->listing(); + 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 index ecd328dab..533e8fc58 100644 --- 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 @@ -13,37 +13,29 @@ trait HidesAttributes { /** * The attributes that should be hidden for serialization. - * - * @var array */ - protected $hidden = []; + protected array $hidden = []; /** * The attributes that should be visible in serialization. - * - * @var array */ - protected $visible = []; + protected array $visible = []; /** * Get the hidden attributes for the model. - * - * @return array */ - public function getHidden() + public function getHidden(): array { - return array_map(function ($key) { - return $this->normalizeAttributeKey($key); - }, $this->hidden); + return array_map( + $this->normalizeAttributeKey(...), + $this->hidden + ); } /** * Set the hidden attributes for the model. - * - * @param array $hidden - * @return $this */ - public function setHidden(array $hidden) + public function setHidden(array $hidden): static { $this->hidden = $hidden; @@ -52,11 +44,8 @@ trait HidesAttributes /** * Add hidden attributes for the model. - * - * @param array|string|null $attributes - * @return void */ - public function addHidden($attributes = null) + public function addHidden(array|string|null $attributes = null): void { $this->hidden = array_merge( $this->hidden, @@ -66,23 +55,19 @@ trait HidesAttributes /** * Get the visible attributes for the model. - * - * @return array */ - public function getVisible() + public function getVisible(): array { - return array_map(function ($key) { - return $this->normalizeAttributeKey($key); - }, $this->visible); + return array_map( + $this->normalizeAttributeKey(...), + $this->visible + ); } /** * Set the visible attributes for the model. - * - * @param array $visible - * @return $this */ - public function setVisible(array $visible) + public function setVisible(array $visible): static { $this->visible = $visible; @@ -91,11 +76,8 @@ trait HidesAttributes /** * Add visible attributes for the model. - * - * @param array|string|null $attributes - * @return void */ - public function addVisible($attributes = null) + public function addVisible(array|string|null $attributes = null): void { $this->visible = array_merge( $this->visible, @@ -105,11 +87,8 @@ trait HidesAttributes /** * Make the given, typically hidden, attributes visible. - * - * @param array|string $attributes - * @return $this */ - public function makeVisible($attributes) + public function makeVisible(array|string $attributes): static { $this->hidden = array_diff($this->hidden, (array) $attributes); @@ -122,11 +101,8 @@ trait HidesAttributes /** * Make the given, typically visible, attributes hidden. - * - * @param array|string $attributes - * @return $this */ - public function makeHidden($attributes) + public function makeHidden(array|string $attributes): static { $attributes = (array) $attributes; 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 index cf550c749..bc4fa28cd 100644 --- 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 @@ -7,12 +7,8 @@ trait SerializesAndRestoresPropertyValues { /** * Get the property value prepared for serialization. - * - * @param string $property - * @param mixed $value - * @return mixed */ - protected function getSerializedPropertyValue($property, $value) + protected function getSerializedPropertyValue(string $property, mixed $value): mixed { if ($property === 'original') { return $this->originalToArray(); @@ -27,12 +23,8 @@ trait SerializesAndRestoresPropertyValues /** * Get the unserialized property value after deserialization. - * - * @param string $property - * @param mixed $value - * @return mixed */ - protected function getUnserializedPropertyValue($property, $value) + protected function getUnserializedPropertyValue(string $property, mixed $value): mixed { if ($property === 'original') { return $this->arrayToOriginal($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 index 91f4bcfe3..65428fa40 100644 --- 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 @@ -11,10 +11,8 @@ trait SerializesProperties /** * Prepare the attributes for serialization. - * - * @return array */ - public function __sleep() + public function __sleep(): array { $properties = (new ReflectionClass($this))->getProperties(); @@ -25,17 +23,15 @@ trait SerializesProperties )); } - return array_values(array_filter(array_map(function ($p) { - return $p->isStatic() ? null : $p->getName(); - }, $properties))); + return array_values(array_filter( + array_map(fn ($p) => $p->isStatic() ? null : $p->getName(), $properties) + )); } /** * Restore the attributes after serialization. - * - * @return void */ - public function __wakeup() + public function __wakeup(): void { foreach ((new ReflectionClass($this))->getProperties() as $property) { if ($property->isStatic()) { @@ -51,10 +47,8 @@ trait SerializesProperties /** * Prepare the model for serialization. - * - * @return array */ - public function __serialize() + public function __serialize(): array { $values = []; @@ -92,11 +86,8 @@ trait SerializesProperties /** * Restore the model after serialization. - * - * @param array $values - * @return void */ - public function __unserialize(array $values) + public function __unserialize(array $values): void { $properties = (new ReflectionClass($this))->getProperties(); @@ -130,11 +121,8 @@ trait SerializesProperties /** * Get the property value for the given property. - * - * @param ReflectionProperty $property - * @return mixed */ - protected function getPropertyValue(ReflectionProperty $property) + protected function getPropertyValue(ReflectionProperty $property): mixed { $property->setAccessible(true); 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 index 7b8d1830e..1f2e826bf 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DetectsResetIntegers.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DetectsResetIntegers.php @@ -10,12 +10,9 @@ trait DetectsResetIntegers * The integer values '0' and '-1' can be used on certain * LDAP attributes to instruct the server to reset the * value to an 'unset' or 'cleared' state. - * - * @param mixed $value - * @return bool */ - protected function valueIsResetInteger($value) + protected function valueIsResetInteger(mixed $value): bool { - return in_array($value, [0, -1], $strict = true); + return in_array($value, [0, -1], true); } } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DirectoryServer/Entry.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DirectoryServer/Entry.php index a67200e94..947daca1a 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DirectoryServer/Entry.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DirectoryServer/Entry.php @@ -9,8 +9,6 @@ class Entry extends Model implements DirectoryServer { /** * The attribute key that contains the models object GUID. - * - * @var string */ - protected $guidKey = 'gidNumber'; + protected string $guidKey = 'gidNumber'; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DirectoryServer/Group.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DirectoryServer/Group.php index 49a6e0aae..c5b0c17d8 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DirectoryServer/Group.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DirectoryServer/Group.php @@ -6,10 +6,8 @@ class Group extends Entry { /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = [ + public static array $objectClasses = [ 'top', 'groupOfUniqueNames', 'posixGroup', diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DirectoryServer/User.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DirectoryServer/User.php index 430588b0a..4a3bc143c 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DirectoryServer/User.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/DirectoryServer/User.php @@ -6,10 +6,8 @@ class User extends Entry { /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = [ + public static array $objectClasses = [ 'top', 'nsPerson', 'nsAccount', diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Events/Event.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Events/Event.php index 8dbd1d29a..340bc2ea5 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Events/Event.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Events/Event.php @@ -8,15 +8,11 @@ abstract class Event { /** * The model that the event is being triggered on. - * - * @var Model */ - protected $model; + protected Model $model; /** * Constructor. - * - * @param Model $model */ public function __construct(Model $model) { @@ -24,11 +20,9 @@ abstract class Event } /** - * Returns the model that generated the event. - * - * @return Model + * Get the model that generated the event. */ - public function getModel() + public function getModel(): Model { return $this->model; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Events/Renaming.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Events/Renaming.php index 5e7be97d7..4890e2bfc 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Events/Renaming.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Events/Renaming.php @@ -8,26 +8,18 @@ class Renaming extends Event { /** * The models RDN. - * - * @var string */ - protected $rdn; + protected string $rdn; /** * The models new parent DN. - * - * @var string */ - protected $newParentDn; + protected string $newParentDn; /** * Constructor. - * - * @param Model $model - * @param string $rdn - * @param string $newParentDn */ - public function __construct(Model $model, $rdn, $newParentDn) + public function __construct(Model $model, string $rdn, string $newParentDn) { parent::__construct($model); @@ -37,20 +29,16 @@ class Renaming extends Event /** * Get the models RDN. - * - * @return string */ - public function getRdn() + public function getRdn(): string { return $this->rdn; } /** * Get the models parent DN. - * - * @return string */ - public function getNewParentDn() + public function getNewParentDn(): string { return $this->newParentDn; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/FreeIPA/Entry.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/FreeIPA/Entry.php index be6322b15..4f29ef033 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/FreeIPA/Entry.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/FreeIPA/Entry.php @@ -13,41 +13,34 @@ class Entry extends BaseEntry implements FreeIPA { /** * The attribute key that contains the models object GUID. - * - * @var string */ - protected $guidKey = 'ipauniqueid'; + protected string $guidKey = 'ipauniqueid'; /** * The default attributes that should be mutated to dates. - * - * @var array */ - protected $defaultDates = [ + protected array $defaultDates = [ 'krblastpwdchange' => 'ldap', 'krbpasswordexpiration' => 'ldap', ]; /** - * @inheritdoc + * {@inheritdoc} */ - protected static function boot() + 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()); + static::addGlobalScope(new AddEntryUuidToSelects); } /** * Create a new query builder. - * - * @param Connection $connection - * @return FreeIpaBuilder */ - public function newQueryBuilder(Connection $connection) + 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 index 10fd93404..941977595 100644 --- 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 @@ -2,14 +2,14 @@ namespace LdapRecord\Models\FreeIPA; +use LdapRecord\Models\Relations\HasMany; + class Group extends Entry { /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = [ + public static array $objectClasses = [ 'top', 'groupofnames', 'nestedgroup', @@ -20,21 +20,17 @@ class Group extends Entry /** * The groups relationship. * - * Retrieves groups that the current group is apart of. - * - * @return \LdapRecord\Models\Relations\HasMany + * Retrieves groups that the current group is a part of. */ - public function groups() + public function groups(): HasMany { return $this->hasMany(self::class, 'member'); } /** * Retrieve the members of the group. - * - * @return \LdapRecord\Models\Relations\HasMany */ - public function members() + 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 index 581b5beed..d35ed9141 100644 --- 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 @@ -10,12 +10,8 @@ class AddEntryUuidToSelects implements Scope { /** * Add the entry UUID to the selected attributes. - * - * @param Builder $query - * @param Model $model - * @return void */ - public function apply(Builder $query, Model $model) + public function apply(Builder $query, Model $model): void { empty($query->columns) ? $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 index 24c7f3b7d..47964f010 100644 --- 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 @@ -2,14 +2,14 @@ namespace LdapRecord\Models\FreeIPA; +use LdapRecord\Models\Relations\HasMany; + class User extends Entry { /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = [ + public static array $objectClasses = [ 'top', 'person', 'inetorgperson', @@ -17,11 +17,9 @@ class User extends Entry ]; /** - * Retrieve groups that the current user is apart of. - * - * @return \LdapRecord\Models\Relations\HasMany + * Retrieve groups that the current user is a part of. */ - public function groups() + public function groups(): HasMany { return $this->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 index 932c1a3b1..b17d0793a 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Model.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Model.php @@ -11,110 +11,92 @@ use LdapRecord\Container; use LdapRecord\EscapesValues; use LdapRecord\Models\Attributes\DistinguishedName; use LdapRecord\Models\Attributes\Guid; +use LdapRecord\Query\Builder as BaseBuilder; use LdapRecord\Query\Model\Builder; use LdapRecord\Support\Arr; +use RuntimeException; +use Stringable; use UnexpectedValueException; /** @mixin Builder */ -abstract class Model implements ArrayAccess, Arrayable, JsonSerializable +abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, Stringable { - use EscapesValues; - use Concerns\HasEvents; - use Concerns\HasScopes; use Concerns\HasAttributes; + use Concerns\HasEvents; use Concerns\HasGlobalScopes; - use Concerns\HidesAttributes; use Concerns\HasRelationships; + use Concerns\HasScopes; + use Concerns\HidesAttributes; use Concerns\SerializesProperties; + use EscapesValues; /** * Indicates if the model exists in the directory. - * - * @var bool */ - public $exists = false; + public bool $exists = false; /** * Indicates whether the model was created during the current request lifecycle. - * - * @var bool */ - public $wasRecentlyCreated = false; + public bool $wasRecentlyCreated = false; /** * Indicates whether the model was renamed during the current request lifecycle. - * - * @var bool */ - public $wasRecentlyRenamed = false; + public bool $wasRecentlyRenamed = false; /** * The models distinguished name. - * - * @var string|null */ - protected $dn; + protected ?string $dn = null; /** * The base DN of where the model should be created in. - * - * @var string|null */ - protected $in; + protected ?string $in = null; /** * The object classes of the model. - * - * @var array */ - public static $objectClasses = []; + public static array $objectClasses = []; /** * The connection container instance. - * - * @var Container */ - protected static $container; + protected static ?Container $container = null; /** * The connection name for the model. - * - * @var string|null */ - protected $connection; + protected ?string $connection = null; /** - * The attribute key that contains the models object GUID. - * - * @var string + * The attribute key containing the models object GUID. */ - protected $guidKey = 'objectguid'; + protected string $guidKey = 'objectguid'; /** - * Contains the models modifications. - * - * @var array + * The array of the model's modifications. */ - protected $modifications = []; - - /** - * The array of global scopes on the model. - * - * @var array - */ - protected static $globalScopes = []; + protected array $modifications = []; /** * The array of booted models. - * - * @var array */ - protected static $booted = []; + protected static array $booted = []; + + /** + * The array of global scopes on the model. + */ + protected static array $globalScopes = []; + + /** + * The morph model cache containing object classes and their corresponding models. + */ + protected static array $morphCache = []; /** * Constructor. - * - * @param array $attributes */ public function __construct(array $attributes = []) { @@ -125,10 +107,8 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Check if the model needs to be booted and if so, do it. - * - * @return void */ - protected function bootIfNotBooted() + protected function bootIfNotBooted(): void { if (! isset(static::$booted[static::class])) { static::$booted[static::class] = true; @@ -139,20 +119,16 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * The "boot" method of the model. - * - * @return void */ - protected static function boot() + protected static function boot(): void { // } /** - * Clear the list of booted models so they will be re-booted. - * - * @return void + * Clear the list of booted models, so they will be re-booted. */ - public static function clearBootedModels() + public static function clearBootedModels(): void { static::$booted = []; @@ -161,12 +137,8 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Handle dynamic method calls into the model. - * - * @param string $method - * @param array $parameters - * @return mixed */ - public function __call($method, $parameters) + public function __call(string $method, array $parameters): mixed { if (method_exists($this, $method)) { return $this->$method(...$parameters); @@ -177,88 +149,66 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Handle dynamic static method calls into the method. - * - * @param string $method - * @param array $parameters - * @return mixed */ - public static function __callStatic($method, $parameters) + public static function __callStatic(string $method, array $parameters): mixed { - return (new static())->$method(...$parameters); + return (new static)->$method(...$parameters); } /** - * Returns the models distinguished name. - * - * @return string|null + * Get the models distinguished name. */ - public function getDn() + public function getDn(): ?string { return $this->dn; } /** - * Set the models distinguished name. - * - * @param string $dn - * @return $this + * Set the model's distinguished name. */ - public function setDn($dn) + public function setDn(?string $dn = null): static { - $this->dn = (string) $dn; + $this->dn = $dn; return $this; } /** - * A mutator for setting the models distinguished name. - * - * @param string $dn - * @return $this + * A mutator for setting the model's distinguished name. */ - public function setDnAttribute($dn) + public function setDnAttribute(string $dn): static { return $this->setRawAttribute('dn', $dn)->setDn($dn); } /** - * A mutator for setting the models distinguished name. - * - * @param string $dn - * @return $this + * A mutator for setting the model's distinguished name. */ - public function setDistinguishedNameAttribute($dn) + public function setDistinguishedNameAttribute(string $dn): static { return $this->setRawAttribute('distinguishedname', $dn)->setDn($dn); } /** * Get the connection for the model. - * - * @return Connection */ - public function getConnection() + public function getConnection(): Connection { return static::resolveConnection($this->getConnectionName()); } /** * Get the current connection name for the model. - * - * @return string */ - public function getConnectionName() + public function getConnectionName(): ?string { return $this->connection; } /** * Set the connection associated with the model. - * - * @param string $name - * @return $this */ - public function setConnection($name) + public function setConnection(?string $name = null): static { $this->connection = $name; @@ -267,24 +217,18 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Make a new model instance. - * - * @param array $attributes - * @return static */ - public static function make($attributes = []) + public static function make(array $attributes = []): static { return new static($attributes); } /** * Begin querying the model on a given connection. - * - * @param string|null $connection - * @return Builder */ - public static function on($connection = null) + public static function on(?string $connection = null): Builder { - $instance = new static(); + $instance = new static; $instance->setConnection($connection); @@ -293,11 +237,8 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Get all the models from the directory. - * - * @param array|mixed $attributes - * @return Collection|static[] */ - public static function all($attributes = ['*']) + public static function all(array|string $attributes = ['*']): array|Collection { return static::query()->select($attributes)->paginate(); } @@ -305,17 +246,15 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Get the RootDSE (AD schema) record from the directory. * - * @param string|null $connection - * @return Model - * * @throws \LdapRecord\Models\ModelNotFoundException */ - public static function getRootDse($connection = null) + public static function getRootDse(?string $connection = null): Model { + /** @var Model $model */ $model = static::getRootDseModel(); return $model::on($connection ?? (new $model)->getConnectionName()) - ->in(null) + ->in() ->read() ->whereHas('objectclass') ->firstOrFail(); @@ -326,40 +265,31 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable * * @return class-string */ - protected static function getRootDseModel() + protected static function getRootDseModel(): string { - $instance = (new static); + $instance = new static; - switch (true) { - case $instance instanceof Types\ActiveDirectory: - return ActiveDirectory\Entry::class; - case $instance instanceof Types\DirectoryServer: - return OpenLDAP\Entry::class; - case $instance instanceof Types\OpenLDAP: - return OpenLDAP\Entry::class; - case $instance instanceof Types\FreeIPA: - return FreeIPA\Entry::class; - default: - return Entry::class; - } + 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. - * - * @return Builder */ - public static function query() + public static function query(): Builder { - return (new static())->newQuery(); + return (new static)->newQuery(); } /** * Get a new query for builder filtered by the current models object classes. - * - * @return Builder */ - public function newQuery() + public function newQuery(): Builder { return $this->registerModelScopes( $this->newQueryWithoutScopes() @@ -368,10 +298,8 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Get a new query builder that doesn't have any global scopes. - * - * @return Builder */ - public function newQueryWithoutScopes() + public function newQueryWithoutScopes(): Builder { return static::resolveConnection( $this->getConnectionName() @@ -380,85 +308,64 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Create a new query builder. - * - * @param Connection $connection - * @return Builder */ - public function newQueryBuilder(Connection $connection) + public function newQueryBuilder(Connection $connection): Builder { return new Builder($connection); } /** * Create a new model instance. - * - * @param array $attributes - * @return static */ - public function newInstance(array $attributes = []) + public function newInstance(array $attributes = []): static { return (new static($attributes))->setConnection($this->getConnectionName()); } /** * Resolve a connection instance. - * - * @param string|null $connection - * @return Connection */ - public static function resolveConnection($connection = null) + public static function resolveConnection(?string $connection = null): Connection { - return static::getConnectionContainer()->get($connection); + return static::getConnectionContainer()->getConnection($connection); } /** * Get the connection container. - * - * @return Container */ - public static function getConnectionContainer() + public static function getConnectionContainer(): Container { return static::$container ?? static::getDefaultConnectionContainer(); } /** * Get the default singleton container instance. - * - * @return Container */ - public static function getDefaultConnectionContainer() + public static function getDefaultConnectionContainer(): Container { return Container::getInstance(); } /** * Set the connection container. - * - * @param Container $container - * @return void */ - public static function setConnectionContainer(Container $container) + public static function setConnectionContainer(Container $container): void { static::$container = $container; } /** * Unset the connection container. - * - * @return void */ - public static function unsetConnectionContainer() + public static function unsetConnectionContainer(): void { static::$container = null; } /** * Register the query scopes for this builder instance. - * - * @param Builder $builder - * @return Builder */ - public function registerModelScopes($builder) + public function registerModelScopes(Builder $builder): Builder { $this->applyObjectClassScopes($builder); @@ -469,11 +376,8 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Register the global model scopes. - * - * @param Builder $builder - * @return Builder */ - public function registerGlobalScopes($builder) + public function registerGlobalScopes(Builder $builder): Builder { foreach ($this->getGlobalScopes() as $identifier => $scope) { $builder->withGlobalScope($identifier, $scope); @@ -484,11 +388,8 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Apply the model object class scopes to the given builder instance. - * - * @param Builder $query - * @return void */ - public function applyObjectClassScopes(Builder $query) + public function applyObjectClassScopes(Builder $query): void { foreach (static::$objectClasses as $objectClass) { $query->where('objectclass', '=', $objectClass); @@ -496,164 +397,121 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable } /** - * Returns the models distinguished name when the model is converted to a string. - * - * @return null|string + * Get the models distinguished name when the model is converted to a string. */ - public function __toString() + public function __toString(): string { - return $this->getDn(); + return (string) $this->getDn(); } /** * 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 = []) + 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. - * - * @param mixed $items - * @return Collection */ - public function newCollection($items = []) + public function newCollection(mixed $items = []): Collection { return new Collection($items); } /** * Dynamically retrieve attributes on the object. - * - * @param string $key - * @return mixed */ - public function __get($key) + public function __get(string $key): mixed { return $this->getAttribute($key); } /** * Dynamically set attributes on the object. - * - * @param string $key - * @param mixed $value - * @return $this */ - public function __set($key, $value) + public function __set(string $key, mixed $value): void { - return $this->setAttribute($key, $value); + $this->setAttribute($key, $value); } /** * Determine if the given offset exists. - * - * @param string $offset - * @return bool */ #[\ReturnTypeWillChange] - public function offsetExists($offset) + public function offsetExists(mixed $offset): bool { return ! is_null($this->getAttribute($offset)); } /** * Get the value for a given offset. - * - * @param string $offset - * @return mixed */ #[\ReturnTypeWillChange] - public function offsetGet($offset) + public function offsetGet(mixed $offset): mixed { return $this->getAttribute($offset); } /** * Set the value at the given offset. - * - * @param string $offset - * @param mixed $value - * @return void */ #[\ReturnTypeWillChange] - public function offsetSet($offset, $value) + public function offsetSet(mixed $offset, mixed $value): void { $this->setAttribute($offset, $value); } /** * Unset the value at the given offset. - * - * @param string $offset - * @return void */ #[\ReturnTypeWillChange] - public function offsetUnset($offset) + public function offsetUnset(mixed $offset): void { unset($this->attributes[$offset]); } /** * Determine if an attribute exists on the model. - * - * @param string $key - * @return bool */ - public function __isset($key) + public function __isset(string $key): bool { return $this->offsetExists($key); } /** * Unset an attribute on the model. - * - * @param string $key - * @return void */ - public function __unset($key) + public function __unset(string $key): void { $this->offsetUnset($key); } /** * Convert the model to its JSON encodeable array form. - * - * @return array */ - public function toArray() + public function toArray(): array { return $this->attributesToArray(); } /** * Convert the model's attributes into JSON encodeable values. - * - * @return array */ #[\ReturnTypeWillChange] - public function jsonSerialize() + public function jsonSerialize(): array { return $this->toArray(); } /** * Convert the attributes for JSON serialization. - * - * @param array $attributes - * @return array */ - protected function convertAttributesForJson(array $attributes = []) + 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 + // 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( @@ -666,21 +524,25 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Convert the attributes from JSON serialization. - * - * @param array $attributes - * @return array */ - protected function convertAttributesFromJson(array $attributes = []) + 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. - * - * @return static|false */ - public function fresh() + public function fresh(): static|false { if (! $this->exists) { return false; @@ -691,50 +553,106 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Determine if two models have the same distinguished name and belong to the same connection. - * - * @param Model|null $model - * @return bool */ - public function is($model) + public function is(?Model $model = null): bool { return ! is_null($model) - && $this->dn == $model->getDn() - && $this->getConnectionName() == $model->getConnectionName(); + && ! empty($this->dn) + && ! empty($model->getDn()) + && $this->dn == $model->getDn() + && $this->getConnectionName() == $model->getConnectionName(); } /** * Determine if two models are not the same. - * - * @param Model|null $model - * @return bool */ - public function isNot($model) + public function isNot(?Model $model = null): bool { return ! $this->is($model); } /** * Hydrate a new collection of models from search results. - * - * @param array $records - * @return Collection */ - public function hydrate($records) + public function hydrate(array $records): Collection { return $this->newCollection($records)->transform(function ($attributes) { - return $attributes instanceof static - ? $attributes - : static::newInstance()->setRawAttributes($attributes); + if ($attributes instanceof static) { + return $attributes; + } + + return static::newInstance()->setRawAttributes($attributes); }); } /** - * Converts the current model into the given model. - * - * @param Model $into - * @return Model + * Morph the model into a one of matching models using their object classes. */ - public function convert(self $into) + 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()); @@ -748,10 +666,8 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Refreshes the current models attributes with the directory values. - * - * @return bool */ - public function refresh() + public function refresh(): bool { if ($model = $this->fresh()) { $this->setRawAttributes($model->getAttributes()); @@ -764,10 +680,8 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Get the model's batch modifications to be processed. - * - * @return array */ - public function getModifications() + public function getModifications(): array { $builtModifications = []; @@ -780,11 +694,8 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Set the models batch modifications. - * - * @param array $modifications - * @return $this */ - public function setModifications(array $modifications = []) + public function setModifications(array $modifications = []): static { $this->modifications = []; @@ -798,12 +709,9 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Adds a batch modification to the model. * - * @param array|BatchModification $mod - * @return $this - * * @throws InvalidArgumentException */ - public function addModification($mod = []) + public function addModification(BatchModification|array $mod = []): static { if ($mod instanceof BatchModification) { $mod = $mod->get(); @@ -822,85 +730,68 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Get the model's guid attribute key name. - * - * @return string */ - public function getGuidKey() + public function getGuidKey(): string { return $this->guidKey; } /** * Get the model's ANR attributes for querying when incompatible with ANR. - * - * @return array */ - public function getAnrAttributes() + public function getAnrAttributes(): array { return ['cn', 'sn', 'uid', 'name', 'mail', 'givenname', 'displayname']; } /** * Get the name of the model, or the given DN. - * - * @param string|null $dn - * @return string|null */ - public function getName($dn = null) + 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. - * - * @param string|null $dn - * @return string|null */ - public function getHead($dn = null) + public function getHead(?string $dn = null): ?string { return $this->newDn($dn ?? $this->dn)->head(); } /** * Get the RDN of the model, of the given DN. - * - * @param string|null - * @return string|null */ - public function getRdn($dn = null) + 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. - * - * @param string|null - * @return string|null */ - public function getParentDn($dn = null) + public function getParentDn(?string $dn = null): ?string { return $this->newDn($dn ?? $this->dn)->parent(); } /** - * Create a new Distinguished Name object. - * - * @param string|null $dn - * @return DistinguishedName + * Create a new distinguished name. */ - public function newDn($dn = null) + 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. - * - * @return string */ - public function getObjectGuidKey() + public function getObjectGuidKey(): string { return $this->guidKey; } @@ -909,76 +800,60 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable * Get the model's binary object GUID. * * @see https://msdn.microsoft.com/en-us/library/ms679021(v=vs.85).aspx - * - * @return string|null */ - public function getObjectGuid() + public function getObjectGuid(): ?string { return $this->getFirstAttribute($this->guidKey); } /** * Get the model's object classes. - * - * @return array */ - public function getObjectClasses() + public function getObjectClasses(): array { return $this->getAttribute('objectclass', static::$objectClasses); } /** * Get the model's string GUID. - * - * @param string|null $guid - * @return string|null */ - public function getConvertedGuid($guid = null) + public function getConvertedGuid(?string $guid = null): ?string { try { - return (string) $this->newObjectGuid( - $guid ?? $this->getObjectGuid() + return $this->newObjectGuid( + (string) ($guid ?? $this->getObjectGuid()) ); - } catch (InvalidArgumentException $e) { - return; + } catch (InvalidArgumentException) { + return null; } } /** * Get the model's binary GUID. - * - * @param string|null $guid - * @return string|null */ - public function getBinaryGuid($guid = null) + public function getBinaryGuid(?string $guid = null): ?string { try { return $this->newObjectGuid( $guid ?? $this->getObjectGuid() )->getBinary(); - } catch (InvalidArgumentException $e) { - return; + } catch (InvalidArgumentException) { + return null; } } /** * Make a new object Guid instance. - * - * @param string $value - * @return Guid */ - protected function newObjectGuid($value) + protected function newObjectGuid(string $value): Guid { return new Guid($value); } /** * Determine if the current model is a direct descendant of the given. - * - * @param static|string $parent - * @return bool */ - public function isChildOf($parent) + public function isChildOf(Model|string|null $parent = null): bool { return $this->newDn($this->getDn())->isChildOf( $this->newDn((string) $parent) @@ -986,12 +861,19 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable } /** - * Determine if the current model is a direct ascendant of the given. - * - * @param static|string $child - * @return bool + * Determine if the current model is a sibling of the given. */ - public function isParentOf($child) + 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) @@ -1000,34 +882,24 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Determine if the current model is a descendant of the given. - * - * @param static|string $model - * @return bool */ - public function isDescendantOf($model) + 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. - * - * @param static|string $model - * @return bool */ - public function isAncestorOf($model) + public function isAncestorOf(Model|string|null $model = null): bool { return $this->dnIsInside($model, $this->getDn()); } /** - * Determines if the DN is inside of the parent DN. - * - * @param static|string $dn - * @param static|string $parentDn - * @return bool + * Determine if the DN is inside the parent DN. */ - protected function dnIsInside($dn, $parentDn) + protected function dnIsInside(Model|string|null $dn = null, Model|string|null $parentDn = null): bool { return $this->newDn((string) $dn)->isDescendantOf( $this->newDn($parentDn) @@ -1036,11 +908,8 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Set the base DN of where the model should be created in. - * - * @param static|string $dn - * @return $this */ - public function inside($dn) + public function inside(Model|string $dn): static { $this->in = $dn instanceof self ? $dn->getDn() : $dn; @@ -1050,12 +919,9 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Save the model to the directory without raising any events. * - * @param array $attributes - * @return void - * * @throws \LdapRecord\LdapRecordException */ - public function saveQuietly(array $attributes = []) + public function saveQuietly(array $attributes = []): void { static::withoutEvents(function () use ($attributes) { $this->save($attributes); @@ -1065,12 +931,9 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Save the model to the directory. * - * @param array $attributes The attributes to update or create for the current entry. - * @return void - * * @throws \LdapRecord\LdapRecordException */ - public function save(array $attributes = []) + public function save(array $attributes = []): void { $this->fill($attributes); @@ -1088,11 +951,9 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Inserts the model into the directory. * - * @return void - * * @throws \LdapRecord\LdapRecordException */ - protected function performInsert() + protected function performInsert(): void { // Here we will populate the models object classes if it // does not already have any set. An LDAP object cannot @@ -1112,11 +973,18 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable $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. - $query->insert($this->getDn(), array_filter($this->getAttributes())); + $this->dn = $query->insertAndGetDn($this->getDn(), $attributes); + + $this->attributes = $attributes; $this->dispatch('created'); @@ -1130,11 +998,9 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Updates the model in the directory. * - * @return void - * * @throws \LdapRecord\LdapRecordException */ - protected function performUpdate() + protected function performUpdate(): void { if (! count($modifications = $this->getModifications())) { return; @@ -1146,18 +1012,17 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable $this->dispatch('updated'); + $this->syncChanges(); + $this->syncOriginal(); } /** * Create the model in the directory. * - * @param array $attributes The attributes for the new entry. - * @return Model - * * @throws \LdapRecord\LdapRecordException */ - public static function create(array $attributes = []) + public static function create(array $attributes = []): static { $instance = new static($attributes); @@ -1167,22 +1032,18 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable } /** - * Create an attribute on the model. - * - * @param string $attribute The attribute to create - * @param mixed $value The value of the new attribute - * @return void + * Add an attribute on the model with the given value. * * @throws ModelDoesNotExistException * @throws \LdapRecord\LdapRecordException */ - public function createAttribute($attribute, $value) + public function addAttribute(string $attribute, mixed $value): void { $this->assertExists(); $this->dispatch(['saving', 'updating']); - $this->newQuery()->insertAttributes($this->dn, [$attribute => (array) $value]); + $this->newQuery()->add($this->dn, [$attribute => (array) $value]); $this->addAttributeValue($attribute, $value); @@ -1192,13 +1053,10 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Update the model. * - * @param array $attributes The attributes to update for the current entry. - * @return void - * * @throws ModelDoesNotExistException * @throws \LdapRecord\LdapRecordException */ - public function update(array $attributes = []) + public function update(array $attributes = []): void { $this->assertExists(); @@ -1208,20 +1066,16 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Update the model attribute with the specified value. * - * @param string $attribute The attribute to modify - * @param mixed $value The new value for the attribute - * @return void - * * @throws ModelDoesNotExistException * @throws \LdapRecord\LdapRecordException */ - public function updateAttribute($attribute, $value) + public function replaceAttribute(string $attribute, mixed $value): void { $this->assertExists(); $this->dispatch(['saving', 'updating']); - $this->newQuery()->updateAttributes($this->dn, [$attribute => (array) $value]); + $this->newQuery()->replace($this->dn, [$attribute => (array) $value]); $this->addAttributeValue($attribute, $value); @@ -1231,17 +1085,13 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Destroy the models for the given distinguished names. * - * @param Collection|array|string $dns - * @param bool $recursive - * @return int - * * @throws \LdapRecord\LdapRecordException */ - public static function destroy($dns, $recursive = false) + public static function destroy(mixed $dns, bool $recursive = false): int { $count = 0; - $instance = new static(); + $instance = new static; if ($dns instanceof Collection) { $dns = $dns->modelDns()->toArray(); @@ -1270,13 +1120,10 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable * 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). - * @return void - * * @throws ModelDoesNotExistException * @throws \LdapRecord\LdapRecordException */ - public function delete($recursive = false) + public function delete(bool $recursive = false): void { $this->assertExists(); @@ -1299,38 +1146,44 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Deletes leaf nodes that are attached to the model. * - * @return void - * * @throws \LdapRecord\LdapRecordException */ - protected function deleteLeafNodes() + protected function deleteLeafNodes(): void { $this->newQueryWithoutScopes() ->in($this->dn) - ->listing() + ->list() ->each(function (Model $model) { - $model->delete($recursive = true); + $model->delete(recursive: true); }); } /** - * Delete an attribute on the model. - * - * @param string|array $attributes The attribute(s) to delete - * - * Delete specific values in attributes: - * - * ["memberuid" => "jdoe"] - * - * Delete an entire attribute: - * - * ["memberuid" => []] - * @return void + * Remove an attribute on the model. * * @throws ModelDoesNotExistException * @throws \LdapRecord\LdapRecordException */ - public function deleteAttribute($attributes) + 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(); @@ -1338,7 +1191,7 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable $this->dispatch(['saving', 'updating']); - $this->newQuery()->deleteAttributes($this->dn, $attributes); + $this->newQuery()->remove($this->dn, $attributes); foreach ($attributes as $attribute => $value) { // If the attribute value is empty, we can assume the @@ -1364,11 +1217,8 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Make a deletable attribute array. - * - * @param string|array $attributes - * @return array */ - protected function makeDeletableAttributes($attributes) + protected function makeDeletableAttributes(string|array $attributes): array { $delete = []; @@ -1386,15 +1236,11 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable * * For example: $user->move($ou); * - * @param static|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 void - * * @throws UnexpectedValueException * @throws ModelDoesNotExistException * @throws \LdapRecord\LdapRecordException */ - public function move($newParentDn, $deleteOldRdn = true) + public function move(Model|string $newParentDn, bool $deleteOldRdn = true): void { $this->assertExists(); @@ -1408,15 +1254,10 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Rename the model to a new RDN and new parent. * - * @param string $rdn The models new relative distinguished name. Example: "cn=JohnDoe" - * @param static|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 void - * * @throws ModelDoesNotExistException * @throws \LdapRecord\LdapRecordException */ - public function rename($rdn, $newParentDn = null, $deleteOldRdn = true) + public function rename(string $rdn, Model|string|null $newParentDn = null, bool $deleteOldRdn = true): void { $this->assertExists(); @@ -1428,6 +1269,13 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable $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. @@ -1438,21 +1286,12 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable return; } - // 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); - } - $this->dispatch('renaming', [$rdn, $newParentDn]); - $this->newQuery()->rename($this->dn, $rdn, $newParentDn, $deleteOldRdn); - // 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 = implode(',', [$rdn, $newParentDn]); + $this->dn = $this->newQuery()->renameAndGetDn($this->dn, $rdn, $newParentDn, $deleteOldRdn); $map = $this->newDn($this->dn)->assoc(); @@ -1471,24 +1310,17 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable } /** - * Get an updateable RDN for the model. - * - * @param string $name - * @return string + * Get an updatable RDN for the model. */ - public function getUpdateableRdn($name) + 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. - * - * @param string|null $name - * @param string|null $attribute - * @return string */ - public function getCreatableDn($name = null, $attribute = null) + public function getCreatableDn(?string $name = null, ?string $attribute = null): string { return implode(',', [ $this->getCreatableRdn($name, $attribute), @@ -1498,39 +1330,30 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Get a creatable (escaped) RDN for the model. - * - * @param string|null $name - * @param string|null $attribute - * @return string */ - public function getCreatableRdn($name = null, $attribute = null) + public function getCreatableRdn(?string $name = null, ?string $attribute = null): string { $attribute = $attribute ?? $this->getCreatableRdnAttribute(); $name = $this->escape( $name ?? $this->getFirstAttribute($attribute) - )->dn(); + )->forDn(); return "$attribute=$name"; } /** * Get the creatable RDN attribute name. - * - * @return string */ - protected function getCreatableRdnAttribute() + protected function getCreatableRdnAttribute(): string { return 'cn'; } /** - * Determines if the given modification is valid. - * - * @param mixed $mod - * @return bool + * Determine if the given modification is valid. */ - protected function isValidModification($mod) + protected function isValidModification(mixed $mod): bool { return Arr::accessible($mod) && Arr::exists($mod, BatchModification::KEY_MODTYPE) @@ -1542,7 +1365,7 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable * * @return BatchModification[] */ - protected function buildModificationsFromDirty() + protected function buildModificationsFromDirty(): array { $modifications = []; @@ -1569,25 +1392,9 @@ abstract class Model implements ArrayAccess, Arrayable, JsonSerializable /** * Throw an exception if the model does not exist. * - * @deprecated - * - * @return void - * * @throws ModelDoesNotExistException */ - protected function requireExistence() - { - return $this->assertExists(); - } - - /** - * Throw an exception if the model does not exist. - * - * @return void - * - * @throws ModelDoesNotExistException - */ - protected function assertExists() + 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 index 508414911..98efa9865 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ModelDoesNotExistException.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ModelDoesNotExistException.php @@ -7,30 +7,22 @@ use LdapRecord\LdapRecordException; class ModelDoesNotExistException extends LdapRecordException { /** - * The class name of the model that does not exist. - * - * @var Model + * The instance of the model that does not exist. */ - protected $model; + protected Model $model; /** * Create a new exception for the given model. - * - * @param Model $model - * @return ModelDoesNotExistException */ - public static function forModel(Model $model) + public static function forModel(Model $model): static { - return (new static())->setModel($model); + return (new static)->setModel($model); } /** - * Set the model that does not exist. - * - * @param Model $model - * @return ModelDoesNotExistException + * Set the model instance that does not exist. */ - public function setModel(Model $model) + public function setModel(Model $model): static { $this->model = $model; @@ -40,4 +32,12 @@ class ModelDoesNotExistException extends LdapRecordException 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/OpenLDAP/Entry.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/Entry.php index c11ca72d4..e20016a45 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/Entry.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/Entry.php @@ -13,31 +13,26 @@ class Entry extends BaseEntry implements OpenLDAP { /** * The attribute key that contains the models object GUID. - * - * @var string */ - protected $guidKey = 'entryuuid'; + protected string $guidKey = 'entryuuid'; /** - * @inheritdoc + * {@inheritdoc} */ - protected static function boot() + protected static function boot(): void { parent::boot(); // Here we'll add a global scope to all OpenLDAP 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()); + static::addGlobalScope(new AddEntryUuidToSelects); } /** * Create a new query builder. - * - * @param Connection $connection - * @return OpenLdapBuilder */ - public function newQueryBuilder(Connection $connection) + public function newQueryBuilder(Connection $connection): OpenLdapBuilder { return new OpenLdapBuilder($connection); } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/Group.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/Group.php index a9a682a06..e7d81ef28 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/Group.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/Group.php @@ -2,27 +2,23 @@ namespace LdapRecord\Models\OpenLDAP; +use LdapRecord\Models\Relations\HasManyIn; + class Group extends Entry { /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = [ + public static array $objectClasses = [ 'top', 'groupofuniquenames', ]; /** * The members relationship. - * - * Retrieves members that are apart of the group. - * - * @return \LdapRecord\Models\Relations\HasMany */ - public function members() + public function members(): HasManyIn { - return $this->hasMany([static::class, User::class], 'memberUid'); + return $this->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 index 7ae0a37a4..e304237e7 100644 --- 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 @@ -6,20 +6,16 @@ class OrganizationalUnit extends Entry { /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = [ + public static array $objectClasses = [ 'top', 'organizationalunit', ]; /** * Get the creatable RDN attribute name. - * - * @return string */ - public function getCreatableRdnAttribute() + public function getCreatableRdnAttribute(): string { return 'ou'; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/Scopes/AddEntryUuidToSelects.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/Scopes/AddEntryUuidToSelects.php index 780a633f3..78efa95f4 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/Scopes/AddEntryUuidToSelects.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/OpenLDAP/Scopes/AddEntryUuidToSelects.php @@ -10,12 +10,8 @@ class AddEntryUuidToSelects implements Scope { /** * Add the entry UUID to the selected attributes. - * - * @param Builder $query - * @param Model $model - * @return void */ - public function apply(Builder $query, Model $model) + public function apply(Builder $query, Model $model): void { empty($query->columns) ? $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 index c8626de6d..beaba9e4c 100644 --- 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 @@ -5,32 +5,27 @@ namespace LdapRecord\Models\OpenLDAP; use Illuminate\Contracts\Auth\Authenticatable; use LdapRecord\Models\Concerns\CanAuthenticate; use LdapRecord\Models\Concerns\HasPassword; +use LdapRecord\Models\Relations\HasMany; class User extends Entry implements Authenticatable { - use HasPassword; use CanAuthenticate; + use HasPassword; /** * The password's attribute name. - * - * @var string */ - protected $passwordAttribute = 'userpassword'; + protected string $passwordAttribute = 'userpassword'; /** * The password's hash method. - * - * @var string */ - protected $passwordHashMethod = 'ssha'; + protected string $passwordHashMethod = 'ssha'; /** * The object classes of the LDAP model. - * - * @var array */ - public static $objectClasses = [ + public static array $objectClasses = [ 'top', 'person', 'organizationalperson', @@ -38,14 +33,18 @@ class User extends Entry implements Authenticatable ]; /** - * The groups relationship. - * - * Retrieve groups that the user is a part of. - * - * @return \LdapRecord\Models\Relations\HasMany + * Get the unique identifier for the user. */ - public function groups() + public function getAuthIdentifier(): string { - return $this->hasMany(Group::class, 'memberuid', 'uid'); + return $this->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 index 583f81c30..16e65b14c 100644 --- 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 @@ -3,73 +3,21 @@ namespace LdapRecord\Models\Relations; use Closure; -use LdapRecord\DetectsErrors; -use LdapRecord\LdapRecordException; use LdapRecord\Models\Collection; use LdapRecord\Models\Model; -use LdapRecord\Models\ModelNotFoundException; +use LdapRecord\Query\Model\Builder; class HasMany extends OneToMany { - use DetectsErrors; - - /** - * The model to use for attaching / detaching. - * - * @var Model - */ - protected $using; - - /** - * The attribute key to use for attaching / detaching. - * - * @var string - */ - protected $usingKey; - /** * The pagination page size. - * - * @var int */ - protected $pageSize = 1000; - - /** - * The exceptions to bypass for each relation operation. - * - * @var array - */ - protected $bypass = [ - 'attach' => [ - 'Already exists', 'Type or value exists', - ], - 'detach' => [ - 'No such attribute', 'Server is unwilling to perform', - ], - ]; - - /** - * Set the model and attribute to use for attaching / detaching. - * - * @param Model $using - * @param string $usingKey - * @return $this - */ - public function using(Model $using, $usingKey) - { - $this->using = $using; - $this->usingKey = $usingKey; - - return $this; - } + protected int $pageSize = 1000; /** * Set the pagination page size of the relation query. - * - * @param int $pageSize - * @return $this */ - public function setPageSize($pageSize) + public function setPageSize(int $pageSize): static { $this->pageSize = $pageSize; @@ -78,22 +26,16 @@ class HasMany extends OneToMany /** * Paginate the relation using the given page size. - * - * @param int $pageSize - * @return Collection */ - public function paginate($pageSize = 1000) + public function paginate(int $pageSize = 1000): Collection { return $this->paginateOnceUsing($pageSize); } /** * Paginate the relation using the page size once. - * - * @param int $pageSize - * @return Collection */ - protected function paginateOnceUsing($pageSize) + protected function paginateOnceUsing(int $pageSize): Collection { $size = $this->pageSize; @@ -106,44 +48,32 @@ class HasMany extends OneToMany /** * Execute a callback over each result while chunking. - * - * @param Closure $callback - * @param int $pageSize - * @return bool */ - public function each(Closure $callback, $pageSize = 1000) + public function each(Closure $callback, int $pageSize = 1000): bool { - $this->chunk($pageSize, function ($results) use ($callback) { + 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. - * - * @param int $pageSize - * @param Closure $callback - * @param array $loaded - * @return bool */ - public function chunk($pageSize, Closure $callback) + public function chunk(int $pageSize, Closure $callback): bool { return $this->chunkRelation($pageSize, $callback); } /** * Execute the callback over chunks of relation results. - * - * @param int $pageSize - * @param Closure $callback - * @param array $loaded - * @return bool */ - protected function chunkRelation($pageSize, Closure $callback, $loaded = []) + 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) { @@ -165,15 +95,15 @@ class HasMany extends OneToMany } }); }); + + return true; }); } /** * Get the relationships results. - * - * @return Collection */ - public function getRelationResults() + public function getRelationResults(): Collection { return $this->transformResults( $this->getRelationQuery()->paginate($this->pageSize) @@ -182,10 +112,8 @@ class HasMany extends OneToMany /** * Get the prepared relationship query. - * - * @return \LdapRecord\Query\Model\Builder */ - public function getRelationQuery() + public function getRelationQuery(): Builder { $columns = $this->query->getSelects(); @@ -209,212 +137,4 @@ class HasMany extends OneToMany $this->getEscapedForeignValueFromModel($this->parent) ); } - - /** - * Attach a model to the relation. - * - * @param Model|string $model - * @return Model|string|false - */ - public function attach($model) - { - return $this->attemptFailableOperation( - $this->buildAttachCallback($model), - $this->bypass['attach'], - $model - ); - } - - /** - * Build the attach callback. - * - * @param Model|string $model - * @return \Closure - */ - protected function buildAttachCallback($model) - { - return function () use ($model) { - $foreign = $this->getAttachableForeignValue($model); - - if ($this->using) { - return $this->using->createAttribute($this->usingKey, $foreign); - } - - if (! $model instanceof Model) { - $model = $this->getForeignModelByValueOrFail($model); - } - - return $model->createAttribute($this->relationKey, $foreign); - }; - } - - /** - * Attach a collection of models to the parent instance. - * - * @param iterable $models - * @return iterable - */ - public function attachMany($models) - { - foreach ($models as $model) { - $this->attach($model); - } - - return $models; - } - - /** - * Detach the model from the relation. - * - * @param Model|string $model - * @return Model|string|false - */ - public function detach($model) - { - return $this->attemptFailableOperation( - $this->buildDetachCallback($model), - $this->bypass['detach'], - $model - ); - } - - /** - * Detach the model or delete the parent if the relation is empty. - * - * @param Model|string $model - * @return void - */ - public function detachOrDeleteParent($model) - { - $count = $this->onceWithoutMerging(function () { - return $this->count(); - }); - - if ($count <= 1) { - return $this->getParent()->delete(); - } - - return $this->detach($model); - } - - /** - * Build the detach callback. - * - * @param Model|string $model - * @return \Closure - */ - protected function buildDetachCallback($model) - { - return function () use ($model) { - $foreign = $this->getAttachableForeignValue($model); - - if ($this->using) { - return $this->using->deleteAttribute([$this->usingKey => $foreign]); - } - - if (! $model instanceof Model) { - $model = $this->getForeignModelByValueOrFail($model); - } - - return $model->deleteAttribute([$this->relationKey => $foreign]); - }; - } - - /** - * Get the attachable foreign value from the model. - * - * @param Model|string $model - * @return string - */ - protected function getAttachableForeignValue($model) - { - if ($model instanceof Model) { - return $this->using - ? $this->getForeignValueFromModel($model) - : $this->getParentForeignValue(); - } - - return $this->using ? $model : $this->getParentForeignValue(); - } - - /** - * Get the foreign model by the given value, or fail. - * - * @param string $model - * @return Model - * - * @throws ModelNotFoundException - */ - protected function getForeignModelByValueOrFail($model) - { - if (! is_null($model = $this->getForeignModelByValue($model))) { - return $model; - } - - throw ModelNotFoundException::forQuery( - $this->query->getUnescapedQuery(), - $this->query->getDn() - ); - } - - /** - * Attempt a failable operation and return the value if successful. - * - * If a bypassable exception is encountered, the value will be returned. - * - * @param callable $operation - * @param string|array $bypass - * @param mixed $value - * @return mixed - * - * @throws LdapRecordException - */ - protected function attemptFailableOperation($operation, $bypass, $value) - { - try { - $operation(); - - return $value; - } catch (LdapRecordException $e) { - if ($this->errorContainsMessage($e->getMessage(), $bypass)) { - return $value; - } - - throw $e; - } - } - - /** - * Detach all relation models. - * - * @return Collection - */ - public function detachAll() - { - return $this->onceWithoutMerging(function () { - return $this->get()->each(function (Model $model) { - $this->detach($model); - }); - }); - } - - /** - * Detach all relation models or delete the model if its relation is empty. - * - * @return Collection - */ - public function detachAllOrDelete() - { - return $this->onceWithoutMerging(function () { - return $this->get()->each(function (Model $model) { - $relation = $model->getRelation($this->relationName); - - if ($relation && $relation->count() >= 1) { - $model->delete(); - } else { - $this->detach($model); - } - }); - }); - } } 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 index 303a1440d..8b5d561f0 100644 --- 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 @@ -2,21 +2,19 @@ namespace LdapRecord\Models\Relations; -use LdapRecord\Query\Collection; +use LdapRecord\Models\Collection; class HasManyIn extends OneToMany { /** * Get the relationships results. - * - * @return Collection */ - public function getRelationResults() + public function getRelationResults(): Collection { $results = $this->parent->newCollection(); foreach ((array) $this->parent->getAttribute($this->relationKey) as $value) { - if ($foreign = $this->getForeignModelByValue($value)) { + if ($value && $foreign = $this->getForeignModelByValue($value)) { $results->push($foreign); } } 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 index 243cd2ed9..0199f2178 100644 --- 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 @@ -2,20 +2,19 @@ namespace LdapRecord\Models\Relations; +use LdapRecord\Models\Collection; use LdapRecord\Models\Model; class HasOne extends Relation { /** * Get the results of the relationship. - * - * @return \LdapRecord\Query\Collection */ - public function getResults() + public function getResults(): Collection { - $model = $this->getForeignModelByValue( - $this->getFirstAttributeValue($this->parent, $this->relationKey) - ); + $relationValue = $this->getFirstAttributeValue($this->parent, $this->relationKey); + + $model = $relationValue ? $this->getForeignModelByValue($relationValue) : null; return $this->transformResults( $this->parent->newCollection($model ? [$model] : null) @@ -24,31 +23,20 @@ class HasOne extends Relation /** * Attach a model instance to the parent model. - * - * @param Model|string $model - * @return Model|string - * - * @throws \LdapRecord\LdapRecordException */ - public function attach($model) + public function attach(Model|string $model): void { $foreign = $model instanceof Model ? $this->getForeignValueFromModel($model) : $model; $this->parent->setAttribute($this->relationKey, $foreign)->save(); - - return $model; } /** * Detach the related model from the parent. - * - * @return void - * - * @throws \LdapRecord\LdapRecordException */ - public function detach() + 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 index aa873b60b..8eb88cbcf 100644 --- 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 @@ -2,44 +2,59 @@ namespace LdapRecord\Models\Relations; +use Closure; +use LdapRecord\DetectsErrors; +use LdapRecord\LdapRecordException; +use LdapRecord\Models\Collection; use LdapRecord\Models\Model; -use LdapRecord\Query\Collection; +use LdapRecord\Models\ModelNotFoundException; use LdapRecord\Query\Model\Builder; abstract class OneToMany extends Relation { + use DetectsErrors; + + /** + * The model to use for attaching / detaching. + */ + protected ?Model $using = null; + /** * The relation to merge results with. - * - * @var OneToMany|null */ - protected $with; + protected ?Relation $with = null; + + /** + * The attribute key to use for attaching / detaching. + */ + protected ?string $usingKey = null; /** * The name of the relationship. - * - * @var string */ - protected $relationName; + protected ?string $relationName = null; /** * Whether to include recursive results. - * - * @var bool */ - protected $recursive = false; + protected bool $recursive = false; + + /** + * The exceptions to bypass for each relation operation. + */ + protected array $bypass = [ + 'attach' => [ + 'Already exists', 'Type or value exists', + ], + 'detach' => [ + 'No such attribute', 'Server is unwilling to perform', + ], + ]; /** * Constructor. - * - * @param Builder $query - * @param Model $parent - * @param string $related - * @param string $relationKey - * @param string $foreignKey - * @param string $relationName */ - public function __construct(Builder $query, Model $parent, $related, $relationKey, $foreignKey, $relationName) + public function __construct(Builder $query, Model $parent, array|string $related, string $relationKey, string $foreignKey, string $relationName) { $this->relationName = $relationName; @@ -47,12 +62,20 @@ abstract class OneToMany extends Relation } /** - * Set the relation to load with its parent. - * - * @param Relation $relation - * @return $this + * Set the model and attribute to use for attaching / detaching. */ - public function with(Relation $relation) + 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; @@ -61,30 +84,23 @@ abstract class OneToMany extends Relation /** * Whether to include recursive results. - * - * @param bool $enable - * @return $this */ - public function recursive($enable = true) + public function recursive(bool $recursive = true): static { - $this->recursive = $enable; + $this->recursive = $recursive; return $this; } /** * Get the immediate relationships results. - * - * @return Collection */ - abstract public function getRelationResults(); + abstract public function getRelationResults(): Collection; /** * Get the results of the relationship. - * - * @return Collection */ - public function getResults() + public function getResults(): Collection { $results = $this->recursive ? $this->getRecursiveResults() @@ -97,11 +113,8 @@ abstract class OneToMany extends Relation /** * Execute the callback excluding the merged query result. - * - * @param callable $callback - * @return mixed */ - protected function onceWithoutMerging($callback) + protected function onceWithoutMerging(Closure $callback): mixed { $merging = $this->with; @@ -116,20 +129,262 @@ abstract class OneToMany extends Relation /** * Get the relation name. - * - * @return string */ - public function getRelationName() + public function getRelationName(): string { return $this->relationName; } /** - * Get the results of the merging 'with' relation. - * - * @return Collection + * Attach the model in the relation. */ - protected function getMergingRelationResults() + 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() @@ -137,24 +392,23 @@ abstract class OneToMany extends Relation } /** - * Get the results for the models relation recursively. + * Get the results for the model's relation recursively. * * @param string[] $loaded The distinguished names of models already loaded - * @return Collection */ - protected function getRecursiveResults(array $loaded = []) + protected function getRecursiveResults(array $loaded = []): Collection { - $results = $this->getRelationResults()->reject(function (Model $model) use ($loaded) { + $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. - return in_array($model->getDn(), $loaded); - }); + in_array($model->getDn(), $loaded) + ); foreach ($results as $model) { $loaded[] = $model->getDn(); - // Finally, we will fetch the related models relations, + // 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( @@ -167,12 +421,8 @@ abstract class OneToMany extends Relation /** * Get the recursive relation results for given model. - * - * @param Model $model - * @param array $loaded - * @return Collection */ - protected function getRecursiveRelationResults(Model $model, array $loaded) + protected function getRecursiveRelationResults(Model $model, array $loaded): Collection { return ($relation = $model->getRelation($this->relationName)) ? $relation->getRecursiveResults($loaded) 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 index 31b0969c9..b2d26100e 100644 --- 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 @@ -3,13 +3,13 @@ namespace LdapRecord\Models\Relations; use Closure; +use LdapRecord\Models\Collection; use LdapRecord\Models\Entry; use LdapRecord\Models\Model; -use LdapRecord\Query\Collection; use LdapRecord\Query\Model\Builder; /** - * @method bool exists($models = null) Determine if the relation contains all of the given models, or any models + * @method bool exists($models = null) Determine if the relation contains all the given models, or any models * @method bool contains($models) Determine if any of the given models are contained in the relation * @method bool count() Retrieve the "count" result of the query. */ @@ -17,70 +17,48 @@ abstract class Relation { /** * The underlying LDAP query. - * - * @var Builder */ - protected $query; + protected Builder $query; /** * The parent model instance. - * - * @var Model */ - protected $parent; + protected Model $parent; /** * The related model class names. - * - * @var array */ - protected $related; + protected array $related; /** * The relation key. - * - * @var string */ - protected $relationKey; + protected string $relationKey; /** * The foreign key. - * - * @var string */ - protected $foreignKey; + protected string $foreignKey; /** * The default relation model. - * - * @var string */ - protected $default = Entry::class; + protected string $default = Entry::class; /** * The callback to use for resolving relation models. - * - * @var Closure */ - protected static $modelResolver; + protected static ?Closure $modelResolver = null; /** * The methods that should be passed along to a relation collection. - * - * @var string[] */ - protected $passthru = ['count', 'exists', 'contains']; + protected array $passthru = ['count', 'exists', 'contains']; /** * Constructor. - * - * @param Builder $query - * @param Model $parent - * @param string|array $related - * @param string $relationKey - * @param string $foreignKey */ - public function __construct(Builder $query, Model $parent, $related, $relationKey, $foreignKey) + public function __construct(Builder $query, Model $parent, array|string $related, string $relationKey, string $foreignKey) { $this->query = $query; $this->parent = $parent; @@ -88,21 +66,13 @@ abstract class Relation $this->relationKey = $relationKey; $this->foreignKey = $foreignKey; - static::$modelResolver = static::$modelResolver ?? function (array $modelObjectClasses, array $relationMap) { - return array_search($modelObjectClasses, $relationMap); - }; - $this->initRelation(); } /** * Handle dynamic method calls to the relationship. - * - * @param string $method - * @param array $parameters - * @return mixed */ - public function __call($method, $parameters) + public function __call(string $method, array $parameters): mixed { if (in_array($method, $this->passthru)) { return $this->get('objectclass')->$method(...$parameters); @@ -119,21 +89,16 @@ abstract class Relation /** * Set the callback to use for resolving models from relation results. - * - * @param Closure $callback - * @return void */ - public static function resolveModelsUsing(Closure $callback) + public static function resolveModelsUsing(?Closure $callback = null): void { static::$modelResolver = $callback; } /** * Only return objects matching the related model's object classes. - * - * @return $this */ - public function onlyRelated() + public function onlyRelated(): static { $relations = []; @@ -158,18 +123,13 @@ abstract class Relation /** * Get the results of the relationship. - * - * @return Collection */ - abstract public function getResults(); + abstract public function getResults(): Collection; /** * Execute the relationship query. - * - * @param array|string $columns - * @return Collection */ - public function get($columns = ['*']) + public function get(array|string $columns = ['*']): Collection { return $this->getResultsWithColumns($columns); } @@ -178,11 +138,8 @@ abstract class Relation * Get the results of the relationship while selecting the given columns. * * If the query columns are empty, the given columns are applied. - * - * @param array $columns - * @return Collection */ - protected function getResultsWithColumns($columns) + protected function getResultsWithColumns(array|string $columns): Collection { if (is_null($this->query->columns)) { $this->query->select($columns); @@ -193,21 +150,16 @@ abstract class Relation /** * Get the first result of the relationship. - * - * @param array|string $columns - * @return Model|null */ - public function first($columns = ['*']) + public function first(array|string $columns = ['*']): ?Model { return $this->get($columns)->first(); } /** * Prepare the relation query. - * - * @return static */ - public function initRelation() + public function initRelation(): static { $this->query ->clearFilters() @@ -219,11 +171,8 @@ abstract class Relation /** * Set the underlying query for the relation. - * - * @param Builder $query - * @return $this */ - public function setQuery(Builder $query) + public function setQuery(Builder $query): static { $this->query = $query; @@ -234,72 +183,58 @@ abstract class Relation /** * Get the underlying query for the relation. - * - * @return Builder */ - public function getQuery() + public function getQuery(): Builder { return $this->query; } /** * Get the parent model of the relation. - * - * @return Model */ - public function getParent() + public function getParent(): Model { return $this->parent; } /** * Get the relation attribute key. - * - * @return string */ - public function getRelationKey() + public function getRelationKey(): string { return $this->relationKey; } /** * Get the related model classes for the relation. - * - * @return array */ - public function getRelated() + public function getRelated(): array { return $this->related; } /** * Get the relation foreign attribute key. - * - * @return string */ - public function getForeignKey() + public function getForeignKey(): string { return $this->foreignKey; } /** * Get the class name of the default model. - * - * @return string */ - public function getDefaultModel() + public function getDefaultModel(): string { return $this->default; } /** * Get a new instance of the default model on the relation. - * - * @return Model */ - public function getNewDefaultModel() + public function getNewDefaultModel(): Model { - $model = new $this->default(); + $model = new $this->default; $model->setConnection($this->parent->getConnectionName()); @@ -308,132 +243,65 @@ abstract class Relation /** * Get the foreign model by the given value. - * - * @param string $value - * @return Model|null */ - protected function getForeignModelByValue($value) + protected function getForeignModelByValue(string $value): ?Model { return $this->foreignKeyIsDistinguishedName() - ? $this->query->find($value) - : $this->query->findBy($this->foreignKey, $value); + ? $this->query->clearFilters()->find($value) + : $this->query->clearFilters()->findBy($this->foreignKey, $value); } /** - * Returns the escaped foreign key value for use in an LDAP filter from the model. - * - * @param Model $model - * @return string + * Get the escaped foreign key value for use in an LDAP filter from the model. */ - protected function getEscapedForeignValueFromModel(Model $model) + protected function getEscapedForeignValueFromModel(Model $model): string { return $this->query->escape( $this->getForeignValueFromModel($model) - )->both(); + )->forDnAndFilter(); } /** * Get the relation parents foreign value. - * - * @return string */ - protected function getParentForeignValue() + protected function getParentForeignValue(): ?string { return $this->getForeignValueFromModel($this->parent); } /** * Get the foreign key value from the model. - * - * @param Model $model - * @return string */ - protected function getForeignValueFromModel(Model $model) + protected function getForeignValueFromModel(Model $model): ?string { return $this->foreignKeyIsDistinguishedName() - ? $model->getDn() - : $this->getFirstAttributeValue($model, $this->foreignKey); + ? $model->getDn() + : $this->getFirstAttributeValue($model, $this->foreignKey); } /** * Get the first attribute value from the model. - * - * @param Model $model - * @param string $attribute - * @return string|null */ - protected function getFirstAttributeValue(Model $model, $attribute) + protected function getFirstAttributeValue(Model $model, string $attribute): mixed { return $model->getFirstAttribute($attribute); } /** * Transforms the results by converting the models into their related. - * - * @param Collection $results - * @return Collection */ - protected function transformResults(Collection $results) + protected function transformResults(Collection $results): Collection { - $relationMap = []; - - foreach ($this->related as $relation) { - $relationMap[$relation] = $this->normalizeObjectClasses( - $relation::$objectClasses - ); - } - - return $results->transform(function (Model $entry) use ($relationMap) { - $model = $this->determineModelFromRelated($entry, $relationMap); - - return class_exists($model) ? $entry->convert(new $model()) : $entry; - }); + return $results->transform( + fn (Model $entry) => $entry->morphInto($this->related, static::$modelResolver) + ); } /** - * Determines if the foreign key is a distinguished name. - * - * @return bool + * Determine if the foreign key is a distinguished name. */ - protected function foreignKeyIsDistinguishedName() + protected function foreignKeyIsDistinguishedName(): bool { return in_array($this->foreignKey, ['dn', 'distinguishedname']); } - - /** - * Determines the model from the given relation map. - * - * @param Model $model - * @param array $relationMap - * @return class-string|bool - */ - protected function determineModelFromRelated(Model $model, array $relationMap) - { - // We must normalize all the related models object class - // names to the same case so we are able to properly - // determine the owning model from search results. - $modelObjectClasses = $this->normalizeObjectClasses( - $model->getObjectClasses() - ); - - return call_user_func( - static::$modelResolver, - $modelObjectClasses, - $relationMap, - $model, - ); - } - - /** - * Sort and normalize the object classes. - * - * @param array $classes - * @return array - */ - protected function normalizeObjectClasses($classes) - { - sort($classes); - - return array_map('strtolower', $classes); - } } 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 index f61cf686b..09c0c0c2c 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Scope.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Scope.php @@ -8,10 +8,6 @@ interface Scope { /** * Apply the scope to the given query. - * - * @param Builder $query - * @param Model $model - * @return void */ - public function apply(Builder $query, Model $model); + public function apply(Builder $query, Model $model): void; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Types/ActiveDirectory.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Types/ActiveDirectory.php index 3edcf88c9..2fc54e4a4 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Types/ActiveDirectory.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Types/ActiveDirectory.php @@ -5,34 +5,24 @@ namespace LdapRecord\Models\Types; interface ActiveDirectory extends TypeInterface { /** - * Returns the models object SID key. - * - * @return string + * Get the models object SID key. */ - public function getObjectSidKey(); + public function getObjectSidKey(): string; /** - * Returns the model's hex object SID. + * Get the model's hex object SID. * * @see https://msdn.microsoft.com/en-us/library/ms679024(v=vs.85).aspx - * - * @return string */ - public function getObjectSid(); + public function getObjectSid(): ?string; /** - * Returns the model's SID. - * - * @param string|null $sid - * @return string|null + * Get the model's SID. */ - public function getConvertedSid($sid = null); + public function getConvertedSid(?string $sid = null): ?string; /** - * Returns the model's binary SID. - * - * @param string|null $sid - * @return string|null + * Get the model's binary SID. */ - public function getBinarySid($sid = null); + public function getBinarySid(?string $sid = null): ?string; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ArrayCacheStore.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ArrayCacheStore.php index 2fb9c9a72..d4a9137c0 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ArrayCacheStore.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ArrayCacheStore.php @@ -10,15 +10,13 @@ class ArrayCacheStore implements CacheInterface /** * An array of stored values. - * - * @var array */ - protected $storage = []; + protected array $storage = []; /** - * @inheritdoc + * {@inheritdoc} */ - public function get($key, $default = null) + public function get($key, $default = null): mixed { if (! isset($this->storage[$key])) { return $default; @@ -38,9 +36,9 @@ class ArrayCacheStore implements CacheInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function set($key, $value, $ttl = null) + public function set($key, $value, $ttl = null): bool { $this->storage[$key] = [ 'value' => $value, @@ -52,30 +50,24 @@ class ArrayCacheStore implements CacheInterface /** * Get the expiration time of the key. - * - * @param int $seconds - * @return int */ - protected function calculateExpiration($seconds) + protected function calculateExpiration($seconds = null): int { return $this->toTimestamp($seconds); } /** * Get the UNIX timestamp for the given number of seconds. - * - * @param int $seconds - * @return int */ - protected function toTimestamp($seconds) + protected function toTimestamp($seconds = null): int { return $seconds > 0 ? $this->availableAt($seconds) : 0; } /** - * @inheritdoc + * {@inheritdoc} */ - public function delete($key) + public function delete($key): bool { unset($this->storage[$key]); @@ -83,9 +75,9 @@ class ArrayCacheStore implements CacheInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function clear() + public function clear(): bool { $this->storage = []; @@ -93,9 +85,9 @@ class ArrayCacheStore implements CacheInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function getMultiple($keys, $default = null) + public function getMultiple($keys, $default = null): iterable { $values = []; @@ -107,9 +99,9 @@ class ArrayCacheStore implements CacheInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function setMultiple($values, $ttl = null) + public function setMultiple($values, $ttl = null): bool { foreach ($values as $key => $value) { $this->set($key, $value, $ttl); @@ -119,9 +111,9 @@ class ArrayCacheStore implements CacheInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function deleteMultiple($keys) + public function deleteMultiple($keys): bool { foreach ($keys as $key) { $this->delete($key); @@ -131,9 +123,9 @@ class ArrayCacheStore implements CacheInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function has($key) + 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 index c93c17809..642cf32af 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Builder.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Builder.php @@ -5,6 +5,7 @@ namespace LdapRecord\Query; use BadMethodCallException; use Closure; use DateTimeInterface; +use Generator; use InvalidArgumentException; use LDAP\Result; use LdapRecord\Connection; @@ -12,31 +13,42 @@ use LdapRecord\Container; use LdapRecord\EscapesValues; use LdapRecord\LdapInterface; use LdapRecord\LdapRecordException; +use LdapRecord\Models\Attributes\EscapedValue; use LdapRecord\Models\Model; use LdapRecord\Query\Events\QueryExecuted; use LdapRecord\Query\Model\Builder as ModelBuilder; use LdapRecord\Query\Pagination\LazyPaginator; use LdapRecord\Query\Pagination\Paginator; use LdapRecord\Support\Arr; -use LdapRecord\Utilities; class Builder { use EscapesValues; + public const TYPE_SEARCH = 'search'; + + public const TYPE_READ = 'read'; + + public const TYPE_CHUNK = 'chunk'; + + public const TYPE_LIST = 'list'; + + public const TYPE_PAGINATE = 'paginate'; + + /** + * The base distinguished name placeholder. + */ + public const BASE_DN_PLACEHOLDER = '{base}'; + /** * The selected columns to retrieve on the query. - * - * @var array */ - public $columns; + public ?array $columns = null; /** * The query filters. - * - * @var array */ - public $filters = [ + public array $filters = [ 'and' => [], 'or' => [], 'raw' => [], @@ -44,120 +56,92 @@ class Builder /** * The LDAP server controls to be sent. - * - * @var array */ - public $controls = []; + public array $controls = []; /** * The LDAP server controls that were processed. - * - * @var array */ - public $controlsResponse = []; + public array $controlsResponse = []; /** * The size limit of the query. - * - * @var int */ - public $limit = 0; + public int $limit = 0; /** - * Determines whether the current query is paginated. - * - * @var bool + * Determine whether the current query is paginated. */ - public $paginated = false; + public bool $paginated = false; /** * The distinguished name to perform searches upon. - * - * @var string|null */ - protected $dn; + protected ?string $dn = null; /** * The base distinguished name to perform searches inside. - * - * @var string|null */ - protected $baseDn; + protected ?string $baseDn = null; /** * The default query type. - * - * @var string */ - protected $type = 'search'; + protected string $type = self::TYPE_SEARCH; /** - * Determines whether the query is nested. - * - * @var bool + * Determine whether the query is nested. */ - protected $nested = false; + protected bool $nested = false; /** - * Determines whether the query should be cached. - * - * @var bool + * Determine whether the query should be cached. */ - protected $caching = false; + 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. - * - * @var DateTimeInterface|null */ - protected $cacheUntil = null; + protected ?DateTimeInterface $cacheUntil = null; /** - * Determines whether the query cache must be flushed. - * - * @var bool + * Determine whether the query cache must be flushed. */ - protected $flushCache = false; + protected bool $flushCache = false; /** * The current connection instance. - * - * @var Connection */ - protected $connection; + protected Connection $connection; /** * The current grammar instance. - * - * @var Grammar */ - protected $grammar; + protected Grammar $grammar; /** * The current cache instance. - * - * @var Cache|null */ - protected $cache; + protected ?Cache $cache = null; /** * Constructor. - * - * @param Connection $connection */ public function __construct(Connection $connection) { $this->connection = $connection; - $this->grammar = new Grammar(); + $this->grammar = new Grammar; } /** * Set the current connection. - * - * @param Connection $connection - * @return $this */ - public function setConnection(Connection $connection) + public function setConnection(Connection $connection): static { $this->connection = $connection; @@ -166,11 +150,8 @@ class Builder /** * Set the current filter grammar. - * - * @param Grammar $grammar - * @return $this */ - public function setGrammar(Grammar $grammar) + public function setGrammar(Grammar $grammar): static { $this->grammar = $grammar; @@ -179,11 +160,8 @@ class Builder /** * Set the cache to store query results. - * - * @param Cache|null $cache - * @return $this */ - public function setCache(Cache $cache = null) + public function setCache(?Cache $cache = null): static { $this->cache = $cache; @@ -192,26 +170,18 @@ class Builder /** * Returns a new Query Builder instance. - * - * @param string $baseDn - * @return $this */ - public function newInstance($baseDn = null) + public function newInstance(?string $baseDn = null): Builder { - // 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))->setDn($dn); + return (new static($this->connection))->setDn( + is_null($baseDn) ? $this->getDn() : $baseDn + ); } /** * Returns a new nested Query Builder instance. - * - * @param Closure|null $closure - * @return $this */ - public function newNestedInstance(Closure $closure = null) + public function newNestedInstance(?Closure $closure = null): Builder { $query = $this->newInstance()->nested(); @@ -224,27 +194,20 @@ class Builder /** * Executes the LDAP query. - * - * @param string|array $columns - * @return Collection|array */ - public function get($columns = ['*']) + public function get(array|string $columns = ['*']): Collection|array { - return $this->onceWithColumns(Arr::wrap($columns), function () { - return $this->query($this->getQuery()); - }); + 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. - * - * @param array $columns - * @param Closure $callback - * @return mixed */ - protected function onceWithColumns($columns, Closure $callback) + protected function onceWithColumns(array $columns, Closure $callback): mixed { $original = $this->columns; @@ -260,11 +223,9 @@ class Builder } /** - * Compiles and returns the current query string. - * - * @return string + * Compile the query into an LDAP filter string. */ - public function getQuery() + public function getQuery(): string { // We need to ensure we have at least one filter, as // no query results will be returned otherwise. @@ -276,134 +237,101 @@ class Builder } /** - * Returns the unescaped query. - * - * @return string + * Get the unescaped query. */ - public function getUnescapedQuery() + public function getUnescapedQuery(): string { - return Utilities::unescape($this->getQuery()); + return EscapedValue::unescape($this->getQuery()); } /** - * Returns the current Grammar instance. - * - * @return Grammar + * Get the current Grammar instance. */ - public function getGrammar() + public function getGrammar(): Grammar { return $this->grammar; } /** - * Returns the current Cache instance. - * - * @return Cache|null + * Get the current Cache instance. */ - public function getCache() + public function getCache(): ?Cache { return $this->cache; } /** - * Returns the current Connection instance. - * - * @return Connection + * Get the current Connection instance. */ - public function getConnection() + public function getConnection(): Connection { return $this->connection; } /** - * Returns the query type. - * - * @return string + * Get the query type. */ - public function getType() + public function getType(): string { return $this->type; } /** * Set the base distinguished name of the query. - * - * @param Model|string $dn - * @return $this */ - public function setBaseDn($dn) + public function setBaseDn(Model|string|null $dn = null): static { - $this->baseDn = $this->substituteBaseInDn($dn); + $this->baseDn = $this->substituteBaseDn($dn); return $this; } /** * Get the base distinguished name of the query. - * - * @return string|null */ - public function getBaseDn() + public function getBaseDn(): ?string { return $this->baseDn; } /** * Get the distinguished name of the query. - * - * @return string */ - public function getDn() + public function getDn(): ?string { return $this->dn; } /** * Set the distinguished name for the query. - * - * @param string|Model|null $dn - * @return $this */ - public function setDn($dn = null) + public function setDn(Model|string|null $dn = null): static { - $this->dn = $this->substituteBaseInDn($dn); + $this->dn = $this->substituteBaseDn($dn); return $this; } /** * Substitute the base DN string template for the current base. - * - * @param Model|string $dn - * @return string */ - protected function substituteBaseInDn($dn) + public function substituteBaseDn(Model|string|null $dn = null): string { - return str_replace( - '{base}', - $this->baseDn ?: '', - (string) ($dn instanceof Model ? $dn->getDn() : $dn) - ); + return str_replace(static::BASE_DN_PLACEHOLDER, $this->baseDn ?? '', (string) $dn); } /** * Alias for setting the distinguished name for the query. - * - * @param string|Model|null $dn - * @return $this */ - public function in($dn = null) + public function in(Model|string|null $dn = null): static { return $this->setDn($dn); } /** * Set the size limit of the current query. - * - * @param int $limit - * @return $this */ - public function limit($limit = 0) + public function limit(int $limit = 0): static { $this->limit = $limit; @@ -412,11 +340,8 @@ class Builder /** * Returns a new query for the given model. - * - * @param Model $model - * @return ModelBuilder */ - public function model(Model $model) + public function model(Model $model): ModelBuilder { return $model->newQueryBuilder($this->connection) ->setCache($this->connection->getCache()) @@ -426,20 +351,15 @@ class Builder /** * Performs the specified query on the current LDAP connection. - * - * @param string $query - * @return Collection|array */ - public function query($query) + 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 = function () use ($query) { - return $this->parse($this->run($query)); - }; + $callback = fn () => $this->parse($this->run($query)); $results = $this->getCachedResponse($query, $callback); @@ -450,12 +370,8 @@ class Builder /** * Paginates the current LDAP query. - * - * @param int $pageSize - * @param bool $isCritical - * @return Collection|array */ - public function paginate($pageSize = 1000, $isCritical = false) + public function paginate(int $pageSize = 1000, bool $isCritical = false): Collection|array { $this->paginated = true; @@ -466,42 +382,37 @@ class Builder // 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, $pageSize, $isCritical) { - return $this->runPaginate($query, $pageSize, $isCritical); - }; + $callback = fn () => $this->runPaginate($query, $pageSize, $isCritical); $pages = $this->getCachedResponse($query, $callback); - $this->logQuery($this, 'paginate', $this->getElapsedTime($start)); + $this->logQuery($this, self::TYPE_PAGINATE, $this->getElapsedTime($start)); return $this->process($pages); } /** * 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) + protected function runPaginate(string $filter, int $perPage, bool $isCritical): array { - return $this->connection->run(function (LdapInterface $ldap) use ($filter, $perPage, $isCritical) { - return (new Paginator($this, $filter, $perPage, $isCritical))->execute($ldap); - }); + 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. - * - * @param Closure $callback - * @param int $pageSize - * @param bool $isCritical - * @param bool $isolate - * @return bool */ - public function each(Closure $callback, $pageSize = 1000, $isCritical = false, $isolate = false) + 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) { @@ -514,15 +425,11 @@ class Builder /** * Chunk the results of a paginated LDAP query. - * - * @param int $pageSize - * @param Closure $callback - * @param bool $isCritical - * @param bool $isolate - * @return bool */ - public function chunk($pageSize, Closure $callback, $isCritical = false, $isolate = false) + 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) { @@ -537,40 +444,40 @@ class Builder } }; - $isolate ? $this->connection->isolate(function (Connection $replicate) use ($chunk) { - $chunk($this->clone()->setConnection($replicate)); - }) : $chunk($this); + // 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, 'chunk', $this->getElapsedTime($start)); + $this->logQuery($this, self::TYPE_CHUNK, $this->getElapsedTime($start)); return true; } /** * Runs the chunk operation with the given filter. - * - * @param string $filter - * @param int $perPage - * @param bool $isCritical - * @return \Generator */ - protected function runChunk($filter, $perPage, $isCritical) + protected function runChunk(string $filter, int $perPage, bool $isCritical): Generator { - return $this->connection->run(function (LdapInterface $ldap) use ($filter, $perPage, $isCritical) { - return (new LazyPaginator($this, $filter, $perPage, $isCritical))->execute($ldap); - }); + 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. - * - * @param int $page - * @param int $perPage - * @param string $orderBy - * @param string $orderByDir - * @return Slice */ - public function slice($page = 1, $perPage = 100, $orderBy = 'cn', $orderByDir = 'asc') + public function slice(int $page = 1, int $perPage = 100, string $orderBy = 'cn', string $orderByDir = 'asc'): Slice { $results = $this->forPage($page, $perPage, $orderBy, $orderByDir); @@ -589,14 +496,8 @@ class Builder /** * Get the results of a query for a given page. - * - * @param int $page - * @param int $perPage - * @param string $orderBy - * @param string $orderByDir - * @return Collection|array */ - public function forPage($page = 1, $perPage = 100, $orderBy = 'cn', $orderByDir = 'asc') + public function forPage(int $page = 1, int $perPage = 100, string $orderBy = 'cn', string $orderByDir = 'asc'): Collection|array { if (! $this->hasOrderBy()) { $this->orderBy($orderBy, $orderByDir); @@ -614,11 +515,8 @@ class Builder /** * Processes and converts the given LDAP results into models. - * - * @param array $results - * @return array */ - protected function process(array $results) + protected function process(array $results): mixed { unset($results['count']); @@ -631,11 +529,8 @@ class Builder /** * Flattens LDAP paged results into a single array. - * - * @param array $pages - * @return array */ - protected function flattenPages(array $pages) + protected function flattenPages(array $pages): array { $records = []; @@ -650,15 +545,11 @@ class Builder /** * Get the cached response or execute and cache the callback value. - * - * @param string $query - * @param Closure $callback - * @return mixed */ - protected function getCachedResponse($query, Closure $callback) + protected function getCachedResponse(string $query, Closure $callback): mixed { if ($this->cache && $this->caching) { - $key = $this->getCacheKey($query); + $key = $this->cacheKey ?? $this->getCacheKey($query); if ($this->flushCache) { $this->cache->delete($key); @@ -667,16 +558,20 @@ class Builder return $this->cache->remember($key, $this->cacheUntil, $callback); } - return $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. - * - * @param string $filter - * @return resource */ - public function run($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 @@ -690,7 +585,7 @@ class Builder } return $ldap->{$this->type}( - $this->dn ?? $this->baseDn, + (string) ($this->dn ?? $this->baseDn), $filter, $this->getSelects(), $onlyAttributes = false, @@ -701,11 +596,8 @@ class Builder /** * Parses the given LDAP resource by retrieving its entries. - * - * @param resource $resource - * @return array */ - public function parse($resource) + public function parse(mixed $resource): array { if (! $resource) { return []; @@ -714,17 +606,10 @@ class Builder return $this->connection->run(function (LdapInterface $ldap) use ($resource) { $this->controlsResponse = $this->controls; - $errorCode = 0; - $dn = $errorMessage = $refs = null; - // Process the server controls response. $ldap->parseResult( - $resource, - $errorCode, - $dn, - $errorMessage, - $refs, - $this->controlsResponse + result: $resource, + controls: $this->controlsResponse ); $entries = $ldap->getEntries($resource); @@ -739,16 +624,13 @@ class Builder } /** - * Returns the cache key. - * - * @param string $query - * @return string + * Get the cache key. */ - protected function getCacheKey($query) + protected function getCacheKey(string $query): string { - $host = $this->connection->run(function (LdapInterface $ldap) { - return $ldap->getHost(); - }); + $host = $this->connection->run( + fn (LdapInterface $ldap) => $ldap->getHost() + ); $key = $host .$this->type @@ -762,12 +644,9 @@ class Builder } /** - * Returns the first entry in a search result. - * - * @param array|string $columns - * @return Model|null + * Get the first entry in a search result. */ - public function first($columns = ['*']) + public function first(array|string $columns = ['*']): Model|array|null { return Arr::first( $this->limit(1)->get($columns) @@ -775,16 +654,13 @@ class Builder } /** - * Returns the first entry in a search result. + * Get the first entry in a search result. * * If no entry is found, an exception is thrown. * - * @param array|string $columns - * @return Model|array - * * @throws ObjectNotFoundException */ - public function firstOrFail($columns = ['*']) + public function firstOrFail(array|string $columns = ['*']): Model|array { if (! $record = $this->first($columns)) { $this->throwNotFoundException($this->getUnescapedQuery(), $this->dn); @@ -794,12 +670,9 @@ class Builder } /** - * Return the first entry in a result, or execute the callback. - * - * @param Closure $callback - * @return Model|mixed + * Get the first entry in a result, or execute the callback. */ - public function firstOr(Closure $callback) + public function firstOr(Closure $callback): mixed { return $this->first() ?: $callback(); } @@ -807,13 +680,10 @@ class Builder /** * Execute the query and get the first result if it's the sole matching record. * - * @param array|string $columns - * @return Model|array - * * @throws ObjectsNotFoundException * @throws MultipleObjectsFoundException */ - public function sole($columns = ['*']) + public function sole(array|string $columns = ['*']): Model|array { $result = $this->limit(2)->get($columns); @@ -830,31 +700,24 @@ class Builder /** * Determine if any results exist for the current query. - * - * @return bool */ - public function exists() + public function exists(): bool { return ! is_null($this->first()); } /** * Determine if no results exist for the current query. - * - * @return bool */ - public function doesntExist() + public function doesntExist(): bool { return ! $this->exists(); } /** * Execute the given callback if no rows exist for the current query. - * - * @param Closure $callback - * @return bool|mixed */ - public function existsOr(Closure $callback) + public function existsOr(Closure $callback): mixed { return $this->exists() ? true : $callback(); } @@ -862,12 +725,9 @@ class Builder /** * Throws a not found exception. * - * @param string $query - * @param string $dn - * * @throws ObjectNotFoundException */ - protected function throwNotFoundException($query, $dn) + protected function throwNotFoundException(string $query, ?string $dn = null): void { throw ObjectNotFoundException::forQuery($query, $dn); } @@ -875,17 +735,14 @@ class Builder /** * Finds a record by the specified attribute and value. * - * @param string $attribute - * @param string $value - * @param array|string $columns * @return Model|static|null */ - public function findBy($attribute, $value, $columns = ['*']) + public function findBy(string $attribute, string $value, array|string $columns = ['*']): Model|array|null { try { return $this->findByOrFail($attribute, $value, $columns); } catch (ObjectNotFoundException $e) { - return; + return null; } } @@ -894,26 +751,17 @@ class Builder * * If no record is found an exception is thrown. * - * @param string $attribute - * @param string $value - * @param array|string $columns - * @return Model - * * @throws ObjectNotFoundException */ - public function findByOrFail($attribute, $value, $columns = ['*']) + 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. - * - * @param string|array $dns - * @param array $columns - * @return array|Collection */ - public function findMany($dns, $columns = ['*']) + public function findMany(array|string $dns, array|string $columns = ['*']): Collection|array { if (empty($dns)) { return $this->process([]); @@ -932,13 +780,8 @@ class Builder /** * Finds many records by the specified attribute. - * - * @param string $attribute - * @param array $values - * @param array $columns - * @return Collection */ - public function findManyBy($attribute, array $values = [], $columns = ['*']) + public function findManyBy(string $attribute, array $values = [], array|string $columns = ['*']): Collection|array { $query = $this->select($columns); @@ -951,12 +794,8 @@ class Builder /** * Finds a record by its distinguished name. - * - * @param string|array $dn - * @param array|string $columns - * @return Model|static|array|Collection|null */ - public function find($dn, $columns = ['*']) + public function find(array|string $dn, array|string $columns = ['*']): Collection|Model|array|null { if (is_array($dn)) { return $this->findMany($dn, $columns); @@ -965,7 +804,7 @@ class Builder try { return $this->findOrFail($dn, $columns); } catch (ObjectNotFoundException $e) { - return; + return null; } } @@ -974,13 +813,9 @@ class Builder * * Fails upon no records returned. * - * @param string $dn - * @param array|string $columns - * @return Model|static - * * @throws ObjectNotFoundException */ - public function findOrFail($dn, $columns = ['*']) + public function findOrFail(string $dn, array|string $columns = ['*']): Model|array { return $this->setDn($dn) ->read() @@ -990,11 +825,8 @@ class Builder /** * Adds the inserted fields to query on the current LDAP connection. - * - * @param array|string $columns - * @return $this */ - public function select($columns = ['*']) + public function select(array|string $columns = ['*']): static { $columns = is_array($columns) ? $columns : func_get_args(); @@ -1007,11 +839,8 @@ class Builder /** * Add a new select column to the query. - * - * @param array|mixed $column - * @return $this */ - public function addSelect($column) + public function addSelect(array|string $column): static { $column = is_array($column) ? $column : func_get_args(); @@ -1022,46 +851,38 @@ class Builder /** * Add an order by control to the query. - * - * @param string $attribute - * @param string $direction - * @return $this */ - public function orderBy($attribute, $direction = 'asc') + public function orderBy(string $attribute, string $direction = 'asc', array $options = []): static { return $this->addControl(LDAP_CONTROL_SORTREQUEST, true, [ - ['attr' => $attribute, 'reverse' => $direction === 'desc'], + [ + ...$options, + 'attr' => $attribute, + 'reverse' => $direction === 'desc', + ], ]); } /** * Add an order by descending control to the query. - * - * @param string $attribute - * @return $this */ - public function orderByDesc($attribute) + public function orderByDesc(string $attribute, array $options = []): static { - return $this->orderBy($attribute, 'desc'); + return $this->orderBy($attribute, 'desc', $options); } /** * Determine if the query has a sotr request control header. - * - * @return bool */ - public function hasOrderBy() + public function hasOrderBy(): bool { return $this->hasControl(LDAP_CONTROL_SORTREQUEST); } /** * Adds a raw filter to the current query. - * - * @param array|string $filters - * @return $this */ - public function rawFilter($filters = []) + public function rawFilter(array|string $filters = []): static { $filters = is_array($filters) ? $filters : func_get_args(); @@ -1074,11 +895,8 @@ class Builder /** * Adds a nested 'and' filter to the current query. - * - * @param Closure $closure - * @return $this */ - public function andFilter(Closure $closure) + public function andFilter(Closure $closure): static { $query = $this->newNestedInstance($closure); @@ -1089,11 +907,8 @@ class Builder /** * Adds a nested 'or' filter to the current query. - * - * @param Closure $closure - * @return $this */ - public function orFilter(Closure $closure) + public function orFilter(Closure $closure): static { $query = $this->newNestedInstance($closure); @@ -1104,11 +919,8 @@ class Builder /** * Adds a nested 'not' filter to the current query. - * - * @param Closure $closure - * @return $this */ - public function notFilter(Closure $closure) + public function notFilter(Closure $closure): static { $query = $this->newNestedInstance($closure); @@ -1120,30 +932,23 @@ class Builder /** * Adds a where clause to the current query. * - * @param string|array $field - * @param string $operator - * @param string $value - * @param string $boolean - * @param bool $raw - * @return $this - * * @throws InvalidArgumentException */ - public function where($field, $operator = null, $value = null, $boolean = 'and', $raw = false) + 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 seperate where clause. + // 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. - if (func_num_args() === 2 && ! in_array($operator, ['*', '!*'])) { - [$value, $operator] = [$operator, '=']; - } + [$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]"); @@ -1152,7 +957,7 @@ class Builder // We'll escape the value if raw isn't requested. $value = $this->prepareWhereValue($field, $value, $raw); - $field = $this->escape($field)->both()->get(); + $field = $this->escape($field)->forDnAndFilter()->get(); $this->addFilter($boolean, compact('field', 'operator', 'value')); @@ -1160,125 +965,114 @@ class Builder } /** - * Prepare the value for being queried. - * - * @param string $field - * @param string $value - * @param bool $raw - * @return string + * Prepare the value and operator for a where clause. */ - protected function prepareWhereValue($field, $value, $raw = false) + public function prepareValueAndOperator(mixed $value, mixed $operator, bool $useDefault = false): array { - return $raw ? $value : $this->escape($value); + 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. - * - * @param string|array $field - * @param string $operator - * @param string $value - * @return $this */ - public function whereRaw($field, $operator = null, $value = null) + 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. - * - * @param string $field - * @param string $value - * @return $this */ - public function whereEquals($field, $value) + public function whereEquals(string $field, string $value): static { return $this->where($field, '=', $value); } /** * Adds a 'where not equals' clause to the current query. - * - * @param string $field - * @param string $value - * @return $this */ - public function whereNotEquals($field, $value) + public function whereNotEquals(string $field, string $value): static { return $this->where($field, '!', $value); } /** * Adds a 'where approximately equals' clause to the current query. - * - * @param string $field - * @param string $value - * @return $this */ - public function whereApproximatelyEquals($field, $value) + public function whereApproximatelyEquals(string $field, string $value): static { return $this->where($field, '~=', $value); } /** * Adds a 'where has' clause to the current query. - * - * @param string $field - * @return $this */ - public function whereHas($field) + public function whereHas(string $field): static { return $this->where($field, '*'); } /** * Adds a 'where not has' clause to the current query. - * - * @param string $field - * @return $this */ - public function whereNotHas($field) + public function whereNotHas(string $field): static { return $this->where($field, '!*'); } /** * Adds a 'where contains' clause to the current query. - * - * @param string $field - * @param string $value - * @return $this */ - public function whereContains($field, $value) + public function whereContains(string $field, string $value): static { return $this->where($field, 'contains', $value); } /** * Adds a 'where contains' clause to the current query. - * - * @param string $field - * @param string $value - * @return $this */ - public function whereNotContains($field, $value) + 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. - * - * @param string $field - * @param array $values - * @return $this */ - public function whereIn($field, array $values) + public function whereIn(string $field, array $values): static { - return $this->orFilter(function (self $query) use ($field, $values) { + 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); } @@ -1287,12 +1081,8 @@ class Builder /** * Adds a 'between' clause to the current query. - * - * @param string $field - * @param array $values - * @return $this */ - public function whereBetween($field, array $values) + public function whereBetween(string $field, array $values): static { return $this->where([ [$field, '>=', $values[0]], @@ -1302,81 +1092,56 @@ class Builder /** * Adds a 'where starts with' clause to the current query. - * - * @param string $field - * @param string $value - * @return $this */ - public function whereStartsWith($field, $value) + 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. - * - * @param string $field - * @param string $value - * @return $this */ - public function whereNotStartsWith($field, $value) + 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. - * - * @param string $field - * @param string $value - * @return $this */ - public function whereEndsWith($field, $value) + 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. - * - * @param string $field - * @param string $value - * @return $this */ - public function whereNotEndsWith($field, $value) + public function whereNotEndsWith(string $field, string $value): static { return $this->where($field, 'not_ends_with', $value); } /** * Only include deleted models in the results. - * - * @return $this */ - public function whereDeleted() + public function whereDeleted(): static { return $this->withDeleted()->whereEquals('isDeleted', 'TRUE'); } /** * Set the LDAP control option to include deleted LDAP models. - * - * @return $this */ - public function withDeleted() + public function withDeleted(): static { return $this->addControl(LdapInterface::OID_SERVER_SHOW_DELETED, $isCritical = true); } /** * Add a server control to the query. - * - * @param string $oid - * @param bool $isCritical - * @param mixed $value - * @return $this */ - public function addControl($oid, $isCritical = false, $value = null) + public function addControl(string $oid, bool $isCritical = false, mixed $value = null): static { $this->controls[$oid] = compact('oid', 'isCritical', 'value'); @@ -1385,25 +1150,21 @@ class Builder /** * Determine if the server control exists on the query. - * - * @param string $oid - * @return bool */ - public function hasControl($oid) + public function hasControl(string $oid): bool { return array_key_exists($oid, $this->controls); } /** * Adds an 'or where' clause to the current query. - * - * @param array|string $field - * @param string|null $operator - * @param string|null $value - * @return $this */ - public function orWhere($field, $operator = null, $value = null) + 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'); } @@ -1411,143 +1172,96 @@ class Builder * 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 $this */ - public function orWhereRaw($field, $operator = null, $value = null) + 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. - * - * @param string $field - * @return $this */ - public function orWhereHas($field) + public function orWhereHas(string $field): static { return $this->orWhere($field, '*'); } /** * Adds a 'where not has' clause to the current query. - * - * @param string $field - * @return $this */ - public function orWhereNotHas($field) + public function orWhereNotHas(string $field): static { return $this->orWhere($field, '!*'); } /** * Adds an 'or where equals' clause to the current query. - * - * @param string $field - * @param string $value - * @return $this */ - public function orWhereEquals($field, $value) + public function orWhereEquals(string $field, string $value): static { return $this->orWhere($field, '=', $value); } /** * Adds an 'or where not equals' clause to the current query. - * - * @param string $field - * @param string $value - * @return $this */ - public function orWhereNotEquals($field, $value) + public function orWhereNotEquals(string $field, string $value): static { return $this->orWhere($field, '!', $value); } /** * Adds a 'or where approximately equals' clause to the current query. - * - * @param string $field - * @param string $value - * @return $this */ - public function orWhereApproximatelyEquals($field, $value) + public function orWhereApproximatelyEquals(string $field, string $value): static { return $this->orWhere($field, '~=', $value); } /** * Adds an 'or where contains' clause to the current query. - * - * @param string $field - * @param string $value - * @return $this */ - public function orWhereContains($field, $value) + 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. - * - * @param string $field - * @param string $value - * @return $this */ - public function orWhereNotContains($field, $value) + 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. - * - * @param string $field - * @param string $value - * @return $this */ - public function orWhereStartsWith($field, $value) + 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. - * - * @param string $field - * @param string $value - * @return $this */ - public function orWhereNotStartsWith($field, $value) + 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. - * - * @param string $field - * @param string $value - * @return $this */ - public function orWhereEndsWith($field, $value) + 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. - * - * @param string $field - * @param string $value - * @return $this */ - public function orWhereNotEndsWith($field, $value) + public function orWhereNotEndsWith(string $field, string $value): static { return $this->orWhere($field, 'not_ends_with', $value); } @@ -1555,13 +1269,9 @@ class Builder /** * Adds a filter binding onto the current query. * - * @param string $type The type of filter to add. - * @param array $bindings The bindings of the filter. - * @return $this - * * @throws InvalidArgumentException */ - public function addFilter($type, array $bindings) + public function addFilter(string $type, array $bindings): static { if (! array_key_exists($type, $this->filters)) { throw new InvalidArgumentException("Filter type: [$type] is invalid."); @@ -1583,11 +1293,8 @@ class Builder /** * Extract any missing required binding keys. - * - * @param array $bindings - * @return array */ - protected function missingBindingKeys($bindings) + protected function missingBindingKeys(array $bindings): array { $required = array_flip(['field', 'operator', 'value']); @@ -1598,20 +1305,16 @@ class Builder /** * Get all the filters on the query. - * - * @return array */ - public function getFilters() + public function getFilters(): array { return $this->filters; } /** * Clear the query filters. - * - * @return $this */ - public function clearFilters() + public function clearFilters(): static { foreach (array_keys($this->filters) as $type) { $this->filters[$type] = []; @@ -1622,20 +1325,16 @@ class Builder /** * Determine if the query has attributes selected. - * - * @return bool */ - public function hasSelects() + public function hasSelects(): bool { - return count($this->columns) > 0; + return count($this->columns ?? []) > 0; } /** * Get the attributes to select on the search. - * - * @return array */ - public function getSelects() + public function getSelects(): array { $selects = $this->columns ?? ['*']; @@ -1659,49 +1358,48 @@ class Builder * Set the query to search on the base distinguished name. * * This will result in one record being returned. - * - * @return $this */ - public function read() + public function read(): static { - $this->type = 'read'; + $this->type = self::TYPE_READ; return $this; } /** * Set the query to search one level on the base distinguished name. - * - * @return $this */ - public function listing() + public function list(): static { - $this->type = 'listing'; + $this->type = self::TYPE_LIST; return $this; } /** - * Set the query to search the entire directory on the base distinguished name. - * - * @return $this + * Alias for the "search" method. */ - public function recursive() + public function recursive(): static { - $this->type = 'search'; + 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. - * - * @param bool $nested - * @return $this */ - public function nested($nested = true) + public function nested(bool $nested = true): static { - $this->nested = (bool) $nested; + $this->nested = $nested; return $this; } @@ -1710,14 +1408,11 @@ class Builder * 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) + 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; @@ -1726,20 +1421,16 @@ class Builder /** * Determine if the query is nested. - * - * @return bool */ - public function isNested() + public function isNested(): bool { return $this->nested === true; } /** * Determine whether the query is paginated. - * - * @return bool */ - public function isPaginated() + public function isPaginated(): bool { return $this->paginated; } @@ -1747,14 +1438,22 @@ class Builder /** * Insert an entry into the directory. * - * @param string $dn - * @param array $attributes - * @return bool + * @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 insert($dn, array $attributes) + 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).'); } @@ -1765,102 +1464,85 @@ class Builder ); } - return $this->connection->run(function (LdapInterface $ldap) use ($dn, $attributes) { - return $ldap->add($dn, $attributes); - }); + return $this->connection->run( + fn (LdapInterface $ldap) => $ldap->add($dn, $attributes) + ) ? $dn : false; } /** - * Create attributes on the entry in the directory. - * - * @param string $dn - * @param array $attributes - * @return bool + * Add attributes to an entry in the directory. */ - public function insertAttributes($dn, array $attributes) + public function add(string $dn, array $attributes): bool { - return $this->connection->run(function (LdapInterface $ldap) use ($dn, $attributes) { - return $ldap->modAdd($dn, $attributes); - }); + return $this->connection->run( + fn (LdapInterface $ldap) => $ldap->modAdd($dn, $attributes) + ); } /** * Update the entry with the given modifications. - * - * @param string $dn - * @param array $modifications - * @return bool */ - public function update($dn, array $modifications) + public function update(string $dn, array $modifications): bool { - return $this->connection->run(function (LdapInterface $ldap) use ($dn, $modifications) { - return $ldap->modifyBatch($dn, $modifications); - }); + return $this->connection->run( + fn (LdapInterface $ldap) => $ldap->modifyBatch($dn, $modifications) + ); } /** - * Update an entries attribute in the directory. - * - * @param string $dn - * @param array $attributes - * @return bool + * Replace an entry's attributes in the directory. */ - public function updateAttributes($dn, array $attributes) + public function replace(string $dn, array $attributes): bool { - return $this->connection->run(function (LdapInterface $ldap) use ($dn, $attributes) { - return $ldap->modReplace($dn, $attributes); - }); + return $this->connection->run( + fn (LdapInterface $ldap) => $ldap->modReplace($dn, $attributes) + ); } /** * Delete an entry from the directory. - * - * @param string $dn - * @return bool */ - public function delete($dn) + public function delete(string $dn): bool { - return $this->connection->run(function (LdapInterface $ldap) use ($dn) { - return $ldap->delete($dn); - }); + return $this->connection->run( + fn (LdapInterface $ldap) => $ldap->delete($dn) + ); } /** - * Delete attributes on the entry in the directory. - * - * @param string $dn - * @param array $attributes - * @return bool + * Remove attributes on the entry in the directory. */ - public function deleteAttributes($dn, array $attributes) + public function remove(string $dn, array $attributes): bool { - return $this->connection->run(function (LdapInterface $ldap) use ($dn, $attributes) { - return $ldap->modDelete($dn, $attributes); - }); + return $this->connection->run( + fn (LdapInterface $ldap) => $ldap->modDelete($dn, $attributes) + ); } /** * Rename an entry in the directory. - * - * @param string $dn - * @param string $rdn - * @param string $newParentDn - * @param bool $deleteOldRdn - * @return bool */ - public function rename($dn, $rdn, $newParentDn, $deleteOldRdn = true) + public function rename(string $dn, string $rdn, string $newParentDn, bool $deleteOldRdn = true): bool { - return $this->connection->run(function (LdapInterface $ldap) use ($dn, $rdn, $newParentDn, $deleteOldRdn) { - return $ldap->rename($dn, $rdn, $newParentDn, $deleteOldRdn); - }); + 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. - * - * @return static */ - public function clone() + public function clone(): static { return clone $this; } @@ -1868,18 +1550,14 @@ class Builder /** * Handle dynamic method calls on the query builder. * - * @param string $method - * @param array $parameters - * @return mixed - * * @throws BadMethodCallException */ - public function __call($method, $parameters) + 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 (substr($method, 0, 5) === 'where') { + if (str_starts_with($method, 'where')) { return $this->dynamicWhere($method, $parameters); } @@ -1893,11 +1571,9 @@ class Builder /** * Handles dynamic "where" clauses to the query. * - * @param string $method - * @param array $parameters * @return $this */ - public function dynamicWhere($method, $parameters) + public function dynamicWhere(string $method, array $parameters): static { $finder = substr($method, 5); @@ -1933,13 +1609,8 @@ class Builder /** * Adds an array of wheres to the current query. - * - * @param array $wheres - * @param string $boolean - * @param bool $raw - * @return $this */ - protected function addArrayOfWheres($wheres, $boolean, $raw) + protected function addArrayOfWheres(array $wheres, string $boolean, bool $raw): static { foreach ($wheres as $key => $value) { if (is_numeric($key) && is_array($value)) { @@ -1964,14 +1635,8 @@ class Builder /** * 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) + 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. @@ -1984,55 +1649,34 @@ class Builder /** * Logs the given executed query information by firing its query event. - * - * @param Builder $query - * @param string $type - * @param null|float $time - * @return void */ - protected function logQuery($query, $type, $time = null) + protected function logQuery(Builder $query, string $type, ?float $time = null): void { $args = [$query, $time]; - switch ($type) { - case 'listing': - $event = new Events\Listing(...$args); - break; - case 'read': - $event = new Events\Read(...$args); - break; - case 'chunk': - $event = new Events\Chunk(...$args); - break; - case 'paginate': - $event = new Events\Paginate(...$args); - break; - default: - $event = new Events\Search(...$args); - break; - } - - $this->fireQueryEvent($event); + $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. - * - * @param QueryExecuted $event - * @return void */ - protected function fireQueryEvent(QueryExecuted $event) + protected function fireQueryEvent(QueryExecuted $event): void { - Container::getInstance()->getEventDispatcher()->fire($event); + Container::getInstance()->getDispatcher()->fire($event); } /** * Get the elapsed time since a given starting point. - * - * @param int $start - * @return float */ - protected function getElapsedTime($start) + 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 index d6057bfb3..184455ced 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Cache.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Cache.php @@ -13,15 +13,11 @@ class Cache /** * The cache driver. - * - * @var CacheInterface */ - protected $store; + protected CacheInterface $store; /** * Constructor. - * - * @param CacheInterface $store */ public function __construct(CacheInterface $store) { @@ -30,47 +26,26 @@ class Cache /** * Get an item from the cache. - * - * @param string $key - * @return mixed */ - public function get($key) + public function get(string $key): mixed { return $this->store->get($key); } /** * Store an item in the cache. - * - * @param string $key - * @param mixed $value - * @param DateTimeInterface|DateInterval|int|null $ttl - * @return bool */ - public function put($key, $value, $ttl = null) + public function put(string $key, mixed $value, DateTimeInterface|DateInterval|int|null $ttl = null): bool { - $seconds = $this->secondsUntil($ttl); - - if ($seconds <= 0) { - return $this->delete($key); - } - - return $this->store->set($key, $value, $seconds); + return $this->store->set($key, $value, $this->secondsUntil($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 - * @return mixed */ - public function remember($key, $ttl, Closure $callback) + public function remember(string $key, DateTimeInterface|DateInterval|int|null $ttl, Closure $callback): mixed { - $value = $this->get($key); - - if (! is_null($value)) { + if (! is_null($value = $this->get($key))) { return $value; } @@ -81,21 +56,16 @@ class Cache /** * Delete an item from the cache. - * - * @param string $key - * @return bool */ - public function delete($key) + public function delete(string $key): bool { return $this->store->delete($key); } /** * Get the underlying cache store. - * - * @return CacheInterface */ - public function 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 index 036affa22..9cda10cdb 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Collection.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Collection.php @@ -2,25 +2,23 @@ namespace LdapRecord\Query; +use Illuminate\Support\Collection as BaseCollection; use LdapRecord\Models\Model; -use Tightenco\Collect\Support\Collection as BaseCollection; class Collection extends BaseCollection { /** - * @inheritdoc + * {@inheritdoc} */ - protected function valueRetriever($value) + protected function valueRetriever(mixed $value): callable { if ($this->useAsCallable($value)) { /** @var callable $value */ return $value; } - return function ($item) use ($value) { - return $item instanceof Model + 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/QueryExecuted.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/QueryExecuted.php index 8ed8f835d..95cadd6ae 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/QueryExecuted.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/QueryExecuted.php @@ -8,46 +8,35 @@ class QueryExecuted { /** * The LDAP filter that was used for the query. - * - * @var Builder */ - protected $query; + protected Builder $query; /** * The number of milliseconds it took to execute the query. - * - * @var ?float */ - protected $time; + protected ?float $time; /** * Constructor. - * - * @param Builder $query - * @param null|float $time */ - public function __construct(Builder $query, $time = null) + public function __construct(Builder $query, ?float $time = null) { $this->query = $query; $this->time = $time; } /** - * Returns the LDAP filter that was used for the query. - * - * @return Builder + * Get the LDAP filter that was used for the query. */ - public function getQuery() + public function getQuery(): Builder { return $this->query; } /** - * Returns the number of milliseconds it took to execute the query. - * - * @return float|null + * Get the number of milliseconds it took to execute the query. */ - public function getTime() + public function getTime(): ?float { return $this->time; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/ConditionNode.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/ConditionNode.php index 6203360c5..4dfed722b 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/ConditionNode.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Filter/ConditionNode.php @@ -8,38 +8,28 @@ class ConditionNode extends Node { /** * The condition's attribute. - * - * @var string */ - protected $attribute; + protected string $attribute; /** * The condition's operator. - * - * @var string */ - protected $operator; + protected string $operator; /** * The condition's value. - * - * @var string */ - protected $value; + protected string $value; /** * The available condition operators. - * - * @var array */ - protected $operators = ['>=', '<=', '~=', '=']; + protected array $operators = ['>=', '<=', '~=', '=']; /** * Constructor. - * - * @param string $filter */ - public function __construct($filter) + public function __construct(string $filter) { $this->raw = $filter; @@ -48,51 +38,38 @@ class ConditionNode extends Node /** * Get the condition's attribute. - * - * @return string */ - public function getAttribute() + public function getAttribute(): string { return $this->attribute; } /** * Get the condition's operator. - * - * @return string */ - public function getOperator() + public function getOperator(): string { return $this->operator; } /** * Get the condition's value. - * - * @return string */ - public function getValue() + public function getValue(): string { return $this->value; } /** * Extract the condition components from the filter. - * - * @param string $filter - * @return array */ - protected function extractComponents($filter) + protected function extractComponents(string $filter): array { $components = Str::whenContains( $filter, $this->operators, - function ($operator, $filter) { - return explode($this->operator = $operator, $filter, 2); - }, - function ($filter) { - throw new ParserException("Invalid query condition. No operator found in [$filter]"); - }, + 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 index 1a3596f3e..1af71369e 100644 --- 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 @@ -6,24 +6,20 @@ class GroupNode extends Node { /** * The group's operator. - * - * @var string */ - protected $operator; + protected string $operator; /** * The group's sub-nodes. * * @var Node[] */ - protected $nodes = []; + protected array $nodes = []; /** * Constructor. - * - * @param string $filter */ - public function __construct($filter) + public function __construct(string $filter) { $this->raw = $filter; @@ -34,10 +30,8 @@ class GroupNode extends Node /** * Get the group's operator. - * - * @return string */ - public function getOperator() + public function getOperator(): string { return $this->operator; } @@ -47,7 +41,7 @@ class GroupNode extends Node * * @return Node[] */ - public function getNodes() + 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 index faf88d41f..3e9c039f1 100644 --- 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 @@ -6,24 +6,18 @@ abstract class Node { /** * The raw value of the node. - * - * @var string */ - protected $raw; + protected string $raw; /** * Create a new filter node. - * - * @param string $filter */ - abstract public function __construct($filter); + abstract public function __construct(string $filter); /** * Get the raw value of the node. - * - * @return string */ - public function getRaw() + public function getRaw(): string { return $this->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 index 7717ac522..1fea1810d 100644 --- 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 @@ -10,12 +10,11 @@ class Parser /** * Parse an LDAP filter into nodes. * - * @param string $string * @return (ConditionNode|GroupNode)[] * * @throws ParserException */ - public static function parse($string) + public static function parse(string $string): array { [$open, $close] = static::countParenthesis($string); @@ -34,11 +33,8 @@ class Parser /** * Perform a match for all filters in the string. - * - * @param string $string - * @return array */ - protected static function match($string) + protected static function match(string $string): array { preg_match_all("/\((((?>[^()]+)|(?R))*)\)/", trim($string), $matches); @@ -49,22 +45,18 @@ class Parser * Assemble the parsed nodes into a single filter. * * @param Node|Node[] $nodes - * @return string */ - public static function assemble($nodes = []) + public static function assemble(Node|array $nodes = []): string { - return array_reduce(Arr::wrap($nodes), function ($carry, Node $node) { - return $carry .= static::compileNode($node); - }); + return array_reduce(Arr::wrap($nodes), fn ($carry, Node $node) => ( + $carry .= static::compileNode($node) + )); } /** * Assemble the node into its string based format. - * - * @param GroupNode|ConditionNode $node - * @return string */ - protected static function compileNode(Node $node) + protected static function compileNode(Node $node): string { switch (true) { case $node instanceof GroupNode: @@ -84,7 +76,7 @@ class Parser * * @throws ParserException */ - protected static function buildNodes(array $filters = []) + protected static function buildNodes(array $filters = []): array { return array_map(function ($filter) { if (static::isWrapped($filter)) { @@ -103,33 +95,24 @@ class Parser /** * Count the open and close parenthesis of the sting. - * - * @param string $string - * @return array */ - protected static function countParenthesis($string) + protected static function countParenthesis(string $string): array { return [Str::substrCount($string, '('), Str::substrCount($string, ')')]; } /** * Wrap the value in parentheses. - * - * @param string $value - * @return string */ - protected static function wrap($value) + protected static function wrap(string $value): string { return "($value)"; } /** * Recursively unwrwap the value from its parentheses. - * - * @param string $value - * @return string */ - protected static function unwrap($value) + protected static function unwrap(string $value): string { $nodes = static::parse($value); @@ -140,22 +123,16 @@ class Parser /** * Determine if the filter is wrapped. - * - * @param string $filter - * @return bool */ - protected static function isWrapped($filter) + protected static function isWrapped(string $filter): bool { return Str::startsWith($filter, '(') && Str::endsWith($filter, ')'); } /** * Determine if the filter is a group. - * - * @param string $filter - * @return bool */ - protected static function isGroup($filter) + 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 index 68a4ca171..3c3d68e4f 100644 --- 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 @@ -4,6 +4,4 @@ namespace LdapRecord\Query\Filter; use LdapRecord\LdapRecordException; -class ParserException extends LdapRecordException -{ -} +class ParserException extends LdapRecordException {} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Grammar.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Grammar.php index 84c4b8d0c..0506c983e 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Grammar.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Grammar.php @@ -8,10 +8,8 @@ class Grammar { /** * The query operators and their method names. - * - * @var array */ - public $operators = [ + public array $operators = [ '*' => 'has', '!*' => 'notHas', '=' => 'equals', @@ -30,17 +28,13 @@ class Grammar /** * The query wrapper. - * - * @var string|null */ - protected $wrapper; + protected ?string $wrapper = null; /** * Get all the available operators. - * - * @return array */ - public function getOperators() + public function getOperators(): array { return array_keys($this->operators); } @@ -49,24 +43,16 @@ class Grammar * Wraps a query string in brackets. * * Produces: (query) - * - * @param string $query - * @param string $prefix - * @param string $suffix - * @return string */ - public function wrap($query, $prefix = '(', $suffix = ')') + public function wrap(string $query, ?string $prefix = '(', ?string $suffix = ')'): string { return $prefix.$query.$suffix; } /** * Compiles the Builder instance into an LDAP query string. - * - * @param Builder $query - * @return string */ - public function compile(Builder $query) + public function compile(Builder $query): string { if ($this->queryMustBeWrapped($query)) { $this->wrapper = 'and'; @@ -76,50 +62,37 @@ class Grammar .$this->compileWheres($query) .$this->compileOrWheres($query); - switch ($this->wrapper) { - case 'and': - return $this->compileAnd($filter); - case 'or': - return $this->compileOr($filter); - default: - return $filter; - } + 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. - * - * @param Builder $query - * @return bool */ - protected function queryMustBeWrapped(Builder $query) + protected function queryMustBeWrapped(Builder $query): bool { return ! $query->isNested() && $this->hasMultipleFilters($query); } /** - * Assembles all of the "raw" filters on the query. - * - * @param Builder $builder - * @return string + * Assembles all the "raw" filters on the query. */ - protected function compileRaws(Builder $builder) + protected function compileRaws(Builder $builder): string { return $this->concatenate($builder->filters['raw']); } /** * Assembles all where clauses in the current wheres property. - * - * @param Builder $builder - * @param string $type - * @return string */ - protected function compileWheres(Builder $builder, $type = 'and') + protected function compileWheres(Builder $builder, string $type = 'and'): string { $filter = ''; - foreach ($builder->filters[$type] as $where) { + foreach ($builder->filters[$type] ?? [] as $where) { $filter .= $this->compileWhere($where); } @@ -128,11 +101,8 @@ class Grammar /** * Assembles all or where clauses in the current orWheres property. - * - * @param Builder $query - * @return string */ - protected function compileOrWheres(Builder $query) + protected function compileOrWheres(Builder $query): string { $filter = $this->compileWheres($query, 'or'); @@ -154,24 +124,18 @@ class Grammar /** * Determine if the query can be wrapped in a single or statement. - * - * @param Builder $query - * @return bool */ - protected function queryCanBeWrappedInSingleOrStatement(Builder $query) + protected function queryCanBeWrappedInSingleOrStatement(Builder $query): bool { - return $this->has($query, 'or', '>=', 1) && - $this->has($query, 'and', '<=', 1) && - $this->has($query, 'raw', '=', 0); + return $this->has($query, 'or', '>=', 1) + && $this->has($query, 'and', '<=', 1) + && $this->has($query, 'raw', '=', 0); } /** * Concatenates filters into a single string. - * - * @param array $bindings - * @return string */ - public function concatenate(array $bindings = []) + public function concatenate(array $bindings = []): string { // Filter out empty query segments. return implode( @@ -181,69 +145,48 @@ class Grammar /** * Determine if the binding value is not empty. - * - * @param string $value - * @return bool */ - protected function bindingValueIsNotEmpty($value) + protected function bindingValueIsNotEmpty(string $value): bool { return ! empty($value); } /** * Determine if the query is using multiple filters. - * - * @param Builder $query - * @return bool */ - protected function hasMultipleFilters(Builder $query) + protected function hasMultipleFilters(Builder $query): bool { return $this->has($query, ['and', 'or', 'raw'], '>', 1); } /** * Determine if the query contains the given filter statement type. - * - * @param Builder $query - * @param string|array $type - * @param string $operator - * @param int $count - * @return bool */ - protected function has(Builder $query, $type, $operator = '>=', $count = 1) + 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]); + $filters += count($query->filters[$type] ?? []); } - switch ($operator) { - case '>': - return $filters > $count; - case '>=': - return $filters >= $count; - case '<': - return $filters < $count; - case '<=': - return $filters <= $count; - default: - return $filters == $count; - } + return match ($operator) { + '>' => $filters > $count, + '>=' => $filters >= $count, + '<' => $filters < $count, + '<=' => $filters <= $count, + default => $filters == $count, + }; } /** * Returns a query string for equals. * * Produces: (field=value) - * - * @param string $field - * @param string $value - * @return string */ - public function compileEquals($field, $value) + public function compileEquals(string $field, string $value): string { return $this->wrap($field.'='.$value); } @@ -252,12 +195,8 @@ class Grammar * Returns a query string for does not equal. * * Produces: (!(field=value)) - * - * @param string $field - * @param string $value - * @return string */ - public function compileDoesNotEqual($field, $value) + public function compileDoesNotEqual(string $field, string $value): string { return $this->compileNot( $this->compileEquals($field, $value) @@ -268,12 +207,8 @@ class Grammar * Alias for does not equal operator (!=) operator. * * Produces: (!(field=value)) - * - * @param string $field - * @param string $value - * @return string */ - public function compileDoesNotEqualAlias($field, $value) + public function compileDoesNotEqualAlias(string $field, string $value): string { return $this->compileDoesNotEqual($field, $value); } @@ -282,12 +217,8 @@ class Grammar * 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) + public function compileGreaterThanOrEquals(string $field, string $value): string { return $this->wrap("$field>=$value"); } @@ -296,12 +227,8 @@ class Grammar * 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) + public function compileLessThanOrEquals(string $field, string $value): string { return $this->wrap("$field<=$value"); } @@ -310,12 +237,8 @@ class Grammar * Returns a query string for approximately equals. * * Produces: (field~=value) - * - * @param string $field - * @param string $value - * @return string */ - public function compileApproximatelyEquals($field, $value) + public function compileApproximatelyEquals(string $field, string $value): string { return $this->wrap("$field~=$value"); } @@ -324,12 +247,8 @@ class Grammar * Returns a query string for starts with. * * Produces: (field=value*) - * - * @param string $field - * @param string $value - * @return string */ - public function compileStartsWith($field, $value) + public function compileStartsWith(string $field, string $value): string { return $this->wrap("$field=$value*"); } @@ -338,12 +257,8 @@ class Grammar * 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) + public function compileNotStartsWith(string $field, string $value): string { return $this->compileNot( $this->compileStartsWith($field, $value) @@ -354,12 +269,8 @@ class Grammar * Returns a query string for ends with. * * Produces: (field=*value) - * - * @param string $field - * @param string $value - * @return string */ - public function compileEndsWith($field, $value) + public function compileEndsWith(string $field, string $value): string { return $this->wrap("$field=*$value"); } @@ -368,12 +279,8 @@ class Grammar * 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) + public function compileNotEndsWith(string $field, string $value): string { return $this->compileNot($this->compileEndsWith($field, $value)); } @@ -382,12 +289,8 @@ class Grammar * Returns a query string for contains. * * Produces: (field=*value*) - * - * @param string $field - * @param string $value - * @return string */ - public function compileContains($field, $value) + public function compileContains(string $field, string $value): string { return $this->wrap("$field=*$value*"); } @@ -396,12 +299,8 @@ class Grammar * Returns a query string for does not contain. * * Produces: (!(field=*value*)) - * - * @param string $field - * @param string $value - * @return string */ - public function compileNotContains($field, $value) + public function compileNotContains(string $field, string $value): string { return $this->compileNot( $this->compileContains($field, $value) @@ -412,11 +311,8 @@ class Grammar * Returns a query string for a where has. * * Produces: (field=*) - * - * @param string $field - * @return string */ - public function compileHas($field) + public function compileHas(string $field): string { return $this->wrap("$field=*"); } @@ -425,11 +321,8 @@ class Grammar * Returns a query string for a where does not have. * * Produces: (!(field=*)) - * - * @param string $field - * @return string */ - public function compileNotHas($field) + public function compileNotHas(string $field): string { return $this->compileNot( $this->compileHas($field) @@ -440,11 +333,8 @@ class Grammar * Wraps the inserted query inside an AND operator. * * Produces: (&query) - * - * @param string $query - * @return string */ - public function compileAnd($query) + public function compileAnd(string $query): string { return $query ? $this->wrap($query, '(&') : ''; } @@ -453,22 +343,16 @@ class Grammar * Wraps the inserted query inside an OR operator. * * Produces: (|query) - * - * @param string $query - * @return string */ - public function compileOr($query) + public function compileOr(string $query): string { return $query ? $this->wrap($query, '(|') : ''; } /** * Wraps the inserted query inside an NOT operator. - * - * @param string $query - * @return string */ - public function compileNot($query) + public function compileNot(string $query): string { return $query ? $this->wrap($query, '(!') : ''; } @@ -476,12 +360,9 @@ class Grammar /** * Assembles a single where query. * - * @param array $where - * @return string - * * @throws UnexpectedValueException */ - protected function compileWhere(array $where) + protected function compileWhere(array $where): string { $method = $this->makeCompileMethod($where['operator']); @@ -491,12 +372,9 @@ class Grammar /** * Make the compile method name for the operator. * - * @param string $operator - * @return string - * * @throws UnexpectedValueException */ - protected function makeCompileMethod($operator) + protected function makeCompileMethod(string $operator): string { if (! $this->operatorExists($operator)) { throw new UnexpectedValueException("Invalid LDAP filter operator ['$operator']"); @@ -507,11 +385,8 @@ class Grammar /** * Determine if the operator exists. - * - * @param string $operator - * @return bool */ - protected function operatorExists($operator) + 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 index e8024c033..111c2c201 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/InteractsWithTime.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/InteractsWithTime.php @@ -15,11 +15,8 @@ trait InteractsWithTime { /** * Get the number of seconds until the given DateTime. - * - * @param DateTimeInterface|DateInterval|int $delay - * @return int */ - protected function secondsUntil($delay) + protected function secondsUntil(DateTimeInterface|DateInterval|int|null $delay = null): int { $delay = $this->parseDateInterval($delay); @@ -30,11 +27,8 @@ trait InteractsWithTime /** * Get the "available at" UNIX timestamp. - * - * @param DateTimeInterface|DateInterval|int $delay - * @return int */ - protected function availableAt($delay = 0) + protected function availableAt(DateTimeInterface|DateInterval|int $delay = 0): int { $delay = $this->parseDateInterval($delay); @@ -45,11 +39,8 @@ trait InteractsWithTime /** * If the given value is an interval, convert it to a DateTime instance. - * - * @param DateTimeInterface|DateInterval|int $delay - * @return DateTimeInterface|int */ - protected function parseDateInterval($delay) + protected function parseDateInterval(DateTimeInterface|DateInterval|int|null $delay = null): DateTimeInterface|int|null { if ($delay instanceof DateInterval) { $delay = Carbon::now()->add($delay); @@ -60,10 +51,8 @@ trait InteractsWithTime /** * Get the current system time as a UNIX timestamp. - * - * @return int */ - protected function currentTime() + 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 index 3b0e96f79..7fb8ab682 100644 --- 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 @@ -5,23 +5,20 @@ namespace LdapRecord\Query\Model; use Closure; use LdapRecord\LdapInterface; use LdapRecord\Models\Attributes\AccountControl; +use LdapRecord\Models\Model; use LdapRecord\Models\ModelNotFoundException; class ActiveDirectoryBuilder extends Builder { /** * Finds a record by its Object SID. - * - * @param string $sid - * @param array|string $columns - * @return \LdapRecord\Models\ActiveDirectory\Entry|static|null */ - public function findBySid($sid, $columns = []) + public function findBySid(string $sid, array|string $columns = ['*']): ?Model { try { return $this->findBySidOrFail($sid, $columns); - } catch (ModelNotFoundException $e) { - return; + } catch (ModelNotFoundException) { + return null; } } @@ -30,190 +27,157 @@ class ActiveDirectoryBuilder extends Builder * * Fails upon no records returned. * - * @param string $sid - * @param array|string $columns - * @return \LdapRecord\Models\ActiveDirectory\Entry|static - * * @throws ModelNotFoundException */ - public function findBySidOrFail($sid, $columns = []) + public function findBySidOrFail(string $sid, array $columns = ['*']): Model { return $this->findByOrFail('objectsid', $sid, $columns); } /** * Adds a enabled filter to the current query. - * - * @return $this */ - public function whereEnabled() + public function whereEnabled(): static { - return $this->notFilter(function ($query) { - return $query->whereDisabled(); - }); + return $this->notFilter( + fn (self $query) => $query->whereDisabled() + ); } /** * Adds a disabled filter to the current query. - * - * @return $this */ - public function whereDisabled() + public function whereDisabled(): static { return $this->rawFilter( - (new AccountControl())->accountIsDisabled()->filter() + (new AccountControl)->setAccountIsDisabled()->filter() ); } /** * Adds a 'where member' filter to the current query. - * - * @param string $dn - * @param bool $nested - * @return $this */ - public function whereMember($dn, $nested = false) + public function whereMember(string $dn, bool $nested = false): static { - return $this->nestedMatchQuery(function ($attribute) use ($dn) { - return $this->whereEquals($attribute, $dn); - }, 'member', $nested); + return $this->nestedMatchQuery( + fn (string $attribute) => $this->whereEquals($attribute, $this->substituteBaseDn($dn)), + 'member', + $nested + ); } /** * Adds an 'or where member' filter to the current query. - * - * @param string $dn - * @param bool $nested - * @return $this */ - public function orWhereMember($dn, $nested = false) + public function orWhereMember(string $dn, bool $nested = false): static { - return $this->nestedMatchQuery(function ($attribute) use ($dn) { - return $this->orWhereEquals($attribute, $dn); - }, 'member', $nested); + return $this->nestedMatchQuery( + fn (string $attribute) => $this->orWhereEquals($attribute, $this->substituteBaseDn($dn)), + 'member', + $nested + ); } /** * Adds a 'where member of' filter to the current query. - * - * @param string $dn - * @param bool $nested - * @return $this */ - public function whereMemberOf($dn, $nested = false) + public function whereMemberOf(string $dn, bool $nested = false): static { - return $this->nestedMatchQuery(function ($attribute) use ($dn) { - return $this->whereEquals($attribute, $dn); - }, 'memberof', $nested); + 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. - * - * @param string $dn - * @param bool $nested - * @return $this */ - public function whereNotMemberof($dn, $nested = false) + public function whereNotMemberof(string $dn, bool $nested = false): static { - return $this->nestedMatchQuery(function ($attribute) use ($dn) { - return $this->whereNotEquals($attribute, $dn); - }, 'memberof', $nested); + 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. - * - * @param string $dn - * @param bool $nested - * @return $this */ - public function orWhereMemberOf($dn, $nested = false) + public function orWhereMemberOf(string $dn, bool $nested = false): static { - return $this->nestedMatchQuery(function ($attribute) use ($dn) { - return $this->orWhereEquals($attribute, $dn); - }, 'memberof', $nested); + 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. - * - * @param string $dn - * @param bool $nested - * @return $this */ - public function orWhereNotMemberof($dn, $nested = false) + public function orWhereNotMemberof(string $dn, bool $nested = false): static { - return $this->nestedMatchQuery(function ($attribute) use ($dn) { - return $this->orWhereNotEquals($attribute, $dn); - }, 'memberof', $nested); + return $this->nestedMatchQuery( + fn (string $attribute) => $this->orWhereNotEquals($attribute, $this->substituteBaseDn($dn)), + 'memberof', + $nested + ); } /** * Adds a 'where manager' filter to the current query. - * - * @param string $dn - * @param bool $nested - * @return $this */ - public function whereManager($dn, $nested = false) + public function whereManager(string $dn, bool $nested = false): static { - return $this->nestedMatchQuery(function ($attribute) use ($dn) { - return $this->whereEquals($attribute, $dn); - }, 'manager', $nested); + return $this->nestedMatchQuery( + fn (string $attribute) => $this->whereEquals($attribute, $this->substituteBaseDn($dn)), + 'manager', + $nested + ); } /** * Adds a 'where not manager' filter to the current query. - * - * @param string $dn - * @param bool $nested - * @return $this */ - public function whereNotManager($dn, $nested = false) + public function whereNotManager(string $dn, bool $nested = false): static { - return $this->nestedMatchQuery(function ($attribute) use ($dn) { - return $this->whereNotEquals($attribute, $dn); - }, 'manager', $nested); + return $this->nestedMatchQuery( + fn (string $attribute) => $this->whereNotEquals($attribute, $this->substituteBaseDn($dn)), + 'manager', + $nested + ); } /** * Adds an 'or where manager' filter to the current query. - * - * @param string $dn - * @param bool $nested - * @return $this */ - public function orWhereManager($dn, $nested = false) + public function orWhereManager(string $dn, bool $nested = false): static { - return $this->nestedMatchQuery(function ($attribute) use ($dn) { - return $this->orWhereEquals($attribute, $dn); - }, 'manager', $nested); + 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. - * - * @param string $dn - * @param bool $nested - * @return $this */ - public function orWhereNotManager($dn, $nested = false) + public function orWhereNotManager(string $dn, bool $nested = false): static { - return $this->nestedMatchQuery(function ($attribute) use ($dn) { - return $this->orWhereNotEquals($attribute, $dn); - }, 'manager', $nested); + return $this->nestedMatchQuery( + fn (string $attribute) => $this->orWhereNotEquals($attribute, $this->substituteBaseDn($dn)), + 'manager', + $nested + ); } /** * Execute the callback with a nested match attribute. - * - * @param Closure $callback - * @param string $attribute - * @param bool $nested - * @return $this */ - protected function nestedMatchQuery(Closure $callback, $attribute, $nested = false) + protected function nestedMatchQuery(Closure $callback, string $attribute, bool $nested = false): static { return $callback( $nested ? $this->makeNestedMatchAttribute($attribute) : $attribute @@ -222,11 +186,8 @@ class ActiveDirectoryBuilder extends Builder /** * Make a "nested match" filter attribute for querying descendants. - * - * @param string $attribute - * @return string */ - protected function makeNestedMatchAttribute($attribute) + 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 index a6886ea96..741646e7e 100644 --- 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 @@ -4,51 +4,41 @@ namespace LdapRecord\Query\Model; use Closure; use DateTime; +use LdapRecord\Models\Attributes\Guid; +use LdapRecord\Models\Collection; use LdapRecord\Models\Model; use LdapRecord\Models\ModelNotFoundException; use LdapRecord\Models\Scope; use LdapRecord\Models\Types\ActiveDirectory; use LdapRecord\Query\Builder as BaseBuilder; -use LdapRecord\Utilities; +use UnexpectedValueException; class Builder extends BaseBuilder { /** * The model being queried. - * - * @var Model */ - protected $model; + protected Model $model; /** * The global scopes to be applied. - * - * @var array */ - protected $scopes = []; + protected array $scopes = []; /** * The removed global scopes. - * - * @var array */ - protected $removedScopes = []; + protected array $removedScopes = []; /** * The applied global scopes. - * - * @var array */ - protected $appliedScopes = []; + protected array $appliedScopes = []; /** * Dynamically handle calls into the query instance. - * - * @param string $method - * @param array $parameters - * @return mixed */ - public function __call($method, $parameters) + public function __call(string $method, array $parameters): static { if (method_exists($this->model, $scope = 'scope'.ucfirst($method))) { return $this->callScope([$this->model, $scope], $parameters); @@ -59,12 +49,8 @@ class Builder extends BaseBuilder /** * Apply the given scope on the current builder instance. - * - * @param callable $scope - * @param array $parameters - * @return mixed */ - protected function callScope(callable $scope, $parameters = []) + protected function callScope(callable $scope, array $parameters = []): static { array_unshift($parameters, $this); @@ -73,10 +59,8 @@ class Builder extends BaseBuilder /** * Get the attributes to select on the search. - * - * @return array */ - public function getSelects() + public function getSelects(): array { // Here we will ensure the models GUID attribute is always // selected. In some LDAP directories, the attribute is @@ -88,11 +72,8 @@ class Builder extends BaseBuilder /** * Set the model instance for the model being queried. - * - * @param Model $model - * @return $this */ - public function setModel(Model $model) + public function setModel(Model $model): static { $this->model = $model; @@ -100,48 +81,97 @@ class Builder extends BaseBuilder } /** - * Returns the model being queried for. - * - * @return Model + * Get the model being queried for. */ - public function getModel() + public function getModel(): Model { return $this->model; } /** * Get a new model query builder instance. - * - * @param string|null $baseDn - * @return static */ - public function newInstance($baseDn = null) + public function newInstance(?string $baseDn = null): BaseBuilder { return parent::newInstance($baseDn)->model($this->model); } /** - * Finds a model by its distinguished name. - * - * @param array|string $dn - * @param array|string|string[] $columns - * @return Model|\LdapRecord\Query\Collection|static|null + * {@inheritDoc} */ - public function find($dn, $columns = ['*']) + public function first(array|string $columns = ['*']): ?Model { - return $this->afterScopes(function () use ($dn, $columns) { - return parent::find($dn, $columns); - }); + 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. - * - * @param string|array $value - * @param array|string $columns - * @return Model|\LdapRecord\Query\Collection|static|null */ - public function findByAnr($value, $columns = ['*']) + public function findByAnr(array|string $value, array|string $columns = ['*']): Model|Collection|null { if (is_array($value)) { return $this->findManyByAnr($value, $columns); @@ -159,10 +189,8 @@ class Builder extends BaseBuilder /** * Determine if the current model is compatible with ANR filters. - * - * @return bool */ - protected function modelIsCompatibleWithAnr() + protected function modelIsCompatibleWithAnr(): bool { return $this->model instanceof ActiveDirectory; } @@ -172,13 +200,9 @@ class Builder extends BaseBuilder * * If a record is not found, an exception is thrown. * - * @param string $value - * @param array|string $columns - * @return Model - * * @throws ModelNotFoundException */ - public function findByAnrOrFail($value, $columns = ['*']) + public function findByAnrOrFail(string $value, array|string $columns = ['*']): Model { if (! $entry = $this->findByAnr($value, $columns)) { $this->throwNotFoundException($this->getUnescapedQuery(), $this->dn); @@ -190,24 +214,17 @@ class Builder extends BaseBuilder /** * Throws a not found exception. * - * @param string $query - * @param string $dn - * * @throws ModelNotFoundException */ - protected function throwNotFoundException($query, $dn) + protected function throwNotFoundException(string $query, ?string $dn = null): void { throw ModelNotFoundException::forQuery($query, $dn); } /** * Finds multiple records using ambiguous name resolution. - * - * @param array $values - * @param array $columns - * @return \LdapRecord\Query\Collection */ - public function findManyByAnr(array $values = [], $columns = ['*']) + public function findManyByAnr(array $values = [], array|string $columns = ['*']): Collection { $this->select($columns); @@ -224,13 +241,10 @@ class Builder extends BaseBuilder /** * Creates an ANR equivalent query for LDAP distributions that do not support ANR. - * - * @param string $value - * @return $this */ - protected function prepareAnrEquivalentQuery($value) + protected function prepareAnrEquivalentQuery(string $value): static { - return $this->orFilter(function (self $query) use ($value) { + return $this->orFilter(function (BaseBuilder $query) use ($value) { foreach ($this->model->getAnrAttributes() as $attribute) { $query->whereEquals($attribute, $value); } @@ -239,35 +253,25 @@ class Builder extends BaseBuilder /** * Finds a record by its string GUID. - * - * @param string $guid - * @param array|string $columns - * @return Model|static|null */ - public function findByGuid($guid, $columns = ['*']) + public function findByGuid(string $guid, array|string $columns = ['*']): ?Model { try { return $this->findByGuidOrFail($guid, $columns); } catch (ModelNotFoundException $e) { - return; + return null; } } /** - * Finds a record by its string GUID. - * - * Fails upon no records returned. - * - * @param string $guid - * @param array|string $columns - * @return Model|static + * Finds a record by its string GUID or throw an exception. * * @throws ModelNotFoundException */ - public function findByGuidOrFail($guid, $columns = ['*']) + public function findByGuidOrFail(string $guid, array|string $columns = ['*']): Model { if ($this->model instanceof ActiveDirectory) { - $guid = Utilities::stringGuidToHex($guid); + $guid = (new Guid($guid))->getEncodedHex(); } return $this->whereRaw([ @@ -276,22 +280,17 @@ class Builder extends BaseBuilder } /** - * @inheritdoc + * {@inheritdoc} */ - public function getQuery() + public function getQuery(): string { - return $this->afterScopes(function () { - return parent::getQuery(); - }); + return $this->afterScopes(fn () => parent::getQuery()); } /** * Apply the query scopes and execute the callback. - * - * @param Closure $callback - * @return mixed */ - protected function afterScopes(Closure $callback) + protected function afterScopes(Closure $callback): mixed { $this->applyScopes(); @@ -300,10 +299,8 @@ class Builder extends BaseBuilder /** * Apply the global query scopes. - * - * @return $this */ - public function applyScopes() + public function applyScopes(): static { if (! $this->scopes) { return $this; @@ -326,12 +323,8 @@ class Builder extends BaseBuilder /** * Register a new global scope. - * - * @param string $identifier - * @param Scope|\Closure $scope - * @return $this */ - public function withGlobalScope($identifier, $scope) + public function withGlobalScope(string $identifier, Scope|Closure $scope): static { $this->scopes[$identifier] = $scope; @@ -340,11 +333,8 @@ class Builder extends BaseBuilder /** * Remove a registered global scope. - * - * @param Scope|string $scope - * @return $this */ - public function withoutGlobalScope($scope) + public function withoutGlobalScope(Scope|string $scope): static { if (! is_string($scope)) { $scope = get_class($scope); @@ -359,11 +349,8 @@ class Builder extends BaseBuilder /** * Remove all or passed registered global scopes. - * - * @param array|null $scopes - * @return $this */ - public function withoutGlobalScopes(array $scopes = null) + public function withoutGlobalScopes(?array $scopes = null): static { if (! is_array($scopes)) { $scopes = array_keys($this->scopes); @@ -378,51 +365,44 @@ class Builder extends BaseBuilder /** * Get an array of global scopes that were removed from the query. - * - * @return array */ - public function removedScopes() + public function removedScopes(): array { return $this->removedScopes; } /** * Get an array of the global scopes that were applied to the query. - * - * @return array */ - public function appliedScopes() + public function appliedScopes(): array { return $this->appliedScopes; } /** * Processes and converts the given LDAP results into models. - * - * @param array $results - * @return \LdapRecord\Query\Collection */ - protected function process(array $results) + protected function process(array $results): Collection { return $this->model->hydrate(parent::process($results)); } /** - * @inheritdoc + * {@inheritdoc} */ - protected function prepareWhereValue($field, $value, $raw = false) + 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( + 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/v2/model-mutators/#date-mutators' + .' Refer to https://ldaprecord.com/docs/core/v3/model-mutators/#date-mutators' ); } - $value = $this->model->fromDateTime($this->model->getDates()[$field], $value); + $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 index 5e3d43fef..cac5ac30b 100644 --- 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 @@ -6,20 +6,16 @@ class FreeIpaBuilder extends Builder { /** * Adds a enabled filter to the current query. - * - * @return $this */ - public function whereEnabled() + public function whereEnabled(): static { return $this->rawFilter('(!(pwdAccountLockedTime=*))'); } /** * Adds a disabled filter to the current query. - * - * @return $this */ - public function whereDisabled() + 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 index dd41344b7..e0a8d5ebc 100644 --- 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 @@ -6,20 +6,16 @@ class OpenLdapBuilder extends Builder { /** * Adds a enabled filter to the current query. - * - * @return $this */ - public function whereEnabled() + public function whereEnabled(): static { return $this->rawFilter('(!(pwdAccountLockedTime=*))'); } /** * Adds a disabled filter to the current query. - * - * @return $this */ - public function whereDisabled() + 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 index 0ece752cf..79ed1e3ef 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/MultipleObjectsFoundException.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/MultipleObjectsFoundException.php @@ -4,6 +4,4 @@ namespace LdapRecord\Query; use LdapRecord\LdapRecordException; -class MultipleObjectsFoundException extends LdapRecordException -{ -} +class MultipleObjectsFoundException extends LdapRecordException {} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ObjectNotFoundException.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ObjectNotFoundException.php index 99c2e85e3..8fb22a218 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ObjectNotFoundException.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ObjectNotFoundException.php @@ -8,38 +8,26 @@ class ObjectNotFoundException extends LdapRecordException { /** * The query filter that was used. - * - * @var string */ - protected $query; + protected string $query; /** * The base DN of the query that was used. - * - * @var string */ - protected $baseDn; + protected ?string $baseDn; /** * Create a new exception for the executed filter. - * - * @param string $query - * @param ?string $baseDn - * @return static */ - public static function forQuery($query, $baseDn = null) + public static function forQuery(string $query, ?string $baseDn = null): static { - return (new static())->setQuery($query, $baseDn); + return (new static)->setQuery($query, $baseDn); } /** * Set the query that was used. - * - * @param string $query - * @param string|null $baseDn - * @return $this */ - public function setQuery($query, $baseDn = null) + public function setQuery(string $query, ?string $baseDn = null): static { $this->query = $query; $this->baseDn = $baseDn; 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 index a9846005c..85edb7f00 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ObjectsNotFoundException.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ObjectsNotFoundException.php @@ -4,6 +4,4 @@ namespace LdapRecord\Query; use LdapRecord\LdapRecordException; -class ObjectsNotFoundException extends LdapRecordException -{ -} +class ObjectsNotFoundException extends LdapRecordException {} diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/AbstractPaginator.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/AbstractPaginator.php index 11e2dc547..c55677314 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/AbstractPaginator.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/AbstractPaginator.php @@ -9,38 +9,28 @@ abstract class AbstractPaginator { /** * The query builder instance. - * - * @var Builder */ - protected $query; + protected Builder $query; /** * The filter to execute. - * - * @var string */ - protected $filter; + protected string $filter; /** * The amount of objects to fetch per page. - * - * @var int */ - protected $perPage; + protected int $perPage; /** * Whether the operation is critical. - * - * @var bool */ - protected $isCritical; + protected bool $isCritical; /** * Constructor. - * - * @param Builder $query */ - public function __construct(Builder $query, $filter, $perPage, $isCritical) + public function __construct(Builder $query, string $filter, int $perPage, bool $isCritical = false) { $this->query = $query; $this->filter = $filter; @@ -50,11 +40,8 @@ abstract class AbstractPaginator /** * Execute the pagination request. - * - * @param LdapInterface $ldap - * @return array */ - public function execute(LdapInterface $ldap) + public function execute(LdapInterface $ldap): mixed { $pages = []; @@ -78,53 +65,37 @@ abstract class AbstractPaginator } /** - * Whether the paginater should continue iterating. - * - * @return bool + * Whether the paginator should continue iterating. */ - protected function shouldContinue() + protected function shouldContinue(): bool { - $cookie = (string) $this->fetchCookie(); + $cookie = $this->fetchCookie(); return $cookie !== ''; } /** * Fetch the pagination cookie. - * - * @return string */ - abstract protected function fetchCookie(); + abstract protected function fetchCookie(): ?string; /** * Prepare the server controls before executing the pagination request. - * - * @return void */ - abstract protected function prepareServerControls(); + abstract protected function prepareServerControls(): void; /** * Apply the server controls. - * - * @param LdapInterface $ldap - * @return void */ - abstract protected function applyServerControls(LdapInterface $ldap); + abstract protected function applyServerControls(LdapInterface $ldap): void; /** * Reset the server controls. - * - * @param LdapInterface $ldap - * @return void */ - abstract protected function resetServerControls(LdapInterface $ldap); + abstract protected function resetServerControls(LdapInterface $ldap): void; /** * Update the server controls. - * - * @param LdapInterface $ldap - * @param resource $resource - * @return void */ - abstract protected function updateServerControls(LdapInterface $ldap, $resource); + abstract protected function updateServerControls(LdapInterface $ldap, mixed $resource): void; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/DeprecatedPaginator.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/DeprecatedPaginator.php deleted file mode 100644 index b4a7f8db9..000000000 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/DeprecatedPaginator.php +++ /dev/null @@ -1,58 +0,0 @@ -cookie; - } - - /** - * @inheritdoc - */ - protected function prepareServerControls() - { - $this->cookie = ''; - } - - /** - * @inheritdoc - */ - protected function applyServerControls(LdapInterface $ldap) - { - $ldap->controlPagedResult($this->perPage, $this->isCritical, $this->cookie); - } - - /** - * @inheritdoc - */ - protected function updateServerControls(LdapInterface $ldap, $resource) - { - $ldap->controlPagedResultResponse($resource, $this->cookie); - } - - /** - * @inheritdoc - */ - protected function resetServerControls(LdapInterface $ldap) - { - $ldap->controlPagedResult(); - } -} 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 index 8b12d526c..d770259ed 100644 --- 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 @@ -2,17 +2,15 @@ namespace LdapRecord\Query\Pagination; +use Generator; use LdapRecord\LdapInterface; class LazyPaginator extends Paginator { /** * Execute the pagination request. - * - * @param LdapInterface $ldap - * @return \Generator */ - public function execute(LdapInterface $ldap) + public function execute(LdapInterface $ldap): Generator { $this->prepareServerControls(); 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 index c0a31afd9..50671a77a 100644 --- 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 @@ -7,17 +7,17 @@ use LdapRecord\LdapInterface; class Paginator extends AbstractPaginator { /** - * @inheritdoc + * {@inheritdoc} */ - protected function fetchCookie() + protected function fetchCookie(): ?string { return $this->query->controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'] ?? null; } /** - * @inheritdoc + * {@inheritdoc} */ - protected function prepareServerControls() + protected function prepareServerControls(): void { $this->query->addControl(LDAP_CONTROL_PAGEDRESULTS, $this->isCritical, [ 'size' => $this->perPage, 'cookie' => '', @@ -25,31 +25,27 @@ class Paginator extends AbstractPaginator } /** - * @inheritdoc + * {@inheritdoc} */ - protected function applyServerControls(LdapInterface $ldap) + protected function applyServerControls(LdapInterface $ldap): void { $ldap->setOption(LDAP_OPT_SERVER_CONTROLS, $this->query->controls); } /** - * @inheritdoc + * {@inheritdoc} */ - protected function updateServerControls(LdapInterface $ldap, $resource) + protected function updateServerControls(LdapInterface $ldap, mixed $resource): void { - $errorCode = 0; - $dn = $errorMessage = $refs = null; $controls = $this->query->controls; - $ldap->parseResult( - $resource, - $errorCode, - $dn, - $errorMessage, - $refs, - $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'] = [ @@ -59,9 +55,9 @@ class Paginator extends AbstractPaginator } /** - * @inheritdoc + * {@inheritdoc} */ - protected function resetServerControls(LdapInterface $ldap) + 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 index 12faa5bd1..2921724ca 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Slice.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Slice.php @@ -10,49 +10,34 @@ use JsonSerializable; class Slice implements ArrayAccess, IteratorAggregate, JsonSerializable { /** - * All of the items being paginated. - * - * @var \LdapRecord\Query\Collection|array + * All the items in the slice. */ - protected $items; + protected Collection|array $items; /** * The number of items to be shown per page. - * - * @var int */ - protected $perPage; + protected int $perPage; /** * The total number of items before slicing. - * - * @var int */ - protected $total; + protected int $total; /** * The last available page. - * - * @var int */ - protected $lastPage; + protected int $lastPage; /** * The current page being "viewed". - * - * @var int */ - protected $currentPage; + protected int $currentPage; /** * Constructor. - * - * @param \LdapRecord\Query\Collection|array $items - * @param int $total - * @param int $perPage - * @param int|null $currentPage */ - public function __construct($items, $total, $perPage, $currentPage = null) + public function __construct(Collection|array $items, int $total, int $perPage, ?int $currentPage = null) { $this->items = $items; $this->total = $total; @@ -63,202 +48,159 @@ class Slice implements ArrayAccess, IteratorAggregate, JsonSerializable /** * Get the slice of items being paginated. - * - * @return \LdapRecord\Query\Collection|array */ - public function items() + public function items(): Collection|array { return $this->items; } /** * Get the total number of items being paginated. - * - * @return int */ - public function total() + public function total(): int { return $this->total; } /** * Get the number of items shown per page. - * - * @return int */ - public function perPage() + public function perPage(): int { return $this->perPage; } /** * Determine if there are more items in the data source. - * - * @return bool */ - public function hasMorePages() + public function hasMorePages(): bool { return $this->currentPage() < $this->lastPage(); } /** * Determine if there are enough items to split into multiple pages. - * - * @return bool */ - public function hasPages() + public function hasPages(): bool { return $this->currentPage() != 1 || $this->hasMorePages(); } /** * Determine if the paginator is on the first page. - * - * @return bool */ - public function onFirstPage() + public function onFirstPage(): bool { return $this->currentPage() <= 1; } /** * Determine if the paginator is on the last page. - * - * @return bool */ - public function onLastPage() + public function onLastPage(): bool { return ! $this->hasMorePages(); } /** * Get the current page. - * - * @return int */ - public function currentPage() + public function currentPage(): int { return $this->currentPage; } /** * Get the last page. - * - * @return int */ - public function lastPage() + public function lastPage(): int { return $this->lastPage; } /** * Get an iterator for the items. - * - * @return \ArrayIterator */ #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): ArrayIterator { return new ArrayIterator($this->items); } /** * Determine if the list of items is empty. - * - * @return bool */ - public function isEmpty() + public function isEmpty(): bool { return empty($this->items); } /** * Determine if the list of items is not empty. - * - * @return bool */ - public function isNotEmpty() + public function isNotEmpty(): bool { return ! $this->isEmpty(); } /** * Get the number of items for the current page. - * - * @return int */ #[\ReturnTypeWillChange] - public function count() + public function count(): int { return count($this->items); } /** * Determine if the given item exists. - * - * @param mixed $key - * @return bool */ #[\ReturnTypeWillChange] - public function offsetExists($key) + public function offsetExists(mixed $offset): bool { - return array_key_exists($key, $this->items); + return array_key_exists($offset, $this->items); } /** * Get the item at the given offset. - * - * @param mixed $key - * @return mixed */ #[\ReturnTypeWillChange] - public function offsetGet($key) + public function offsetGet(mixed $offset): mixed { - return $this->items[$key] ?? null; + return $this->items[$offset] ?? null; } /** * Set the item at the given offset. - * - * @param mixed $key - * @param mixed $value - * @return void */ #[\ReturnTypeWillChange] - public function offsetSet($key, $value) + public function offsetSet(mixed $offset, mixed $value): void { - $this->items[$key] = $value; + $this->items[$offset] = $value; } /** * Unset the item at the given key. - * - * @param mixed $key - * @return void */ #[\ReturnTypeWillChange] - public function offsetUnset($key) + public function offsetUnset(mixed $offset): void { - unset($this->items[$key]); + unset($this->items[$offset]); } /** * Convert the object into something JSON serializable. - * - * @return array */ #[\ReturnTypeWillChange] - public function jsonSerialize() + public function jsonSerialize(): array { return $this->toArray(); } /** * Get the arrayable items. - * - * @return array */ - public function getArrayableItems() + public function getArrayableItems(): array { return $this->items instanceof Collection ? $this->items->all() @@ -267,10 +209,8 @@ class Slice implements ArrayAccess, IteratorAggregate, JsonSerializable /** * Get the instance as an array. - * - * @return array */ - public function toArray() + public function toArray(): array { return [ 'current_page' => $this->currentPage(), 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 index 7475859ae..6c3217a50 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Support/Arr.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Support/Arr.php @@ -8,23 +8,16 @@ class Arr { /** * Determine whether the given value is array accessible. - * - * @param mixed $value - * @return bool */ - public static function accessible($value) + public static function accessible(mixed $value): bool { return is_array($value) || $value instanceof ArrayAccess; } /** * 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) + public static function exists(ArrayAccess|array $array, string|int $key): bool { if ($array instanceof ArrayAccess) { return $array->offsetExists($key); @@ -35,11 +28,8 @@ class Arr /** * 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) + public static function wrap(mixed $value): array { if (is_null($value)) { return []; @@ -49,14 +39,9 @@ class Arr } /** - * Return the first element in an array passing a given truth test. - * - * @param iterable $array - * @param callable|null $callback - * @param mixed $default - * @return mixed + * Get the first element in an array passing a given truth test. */ - public static function first($array, callable $callback = null, $default = null) + public static function first(iterable $array, ?callable $callback = null, mixed $default = null): mixed { if (is_null($callback)) { if (empty($array)) { @@ -78,14 +63,11 @@ class Arr } /** - * Return the last element in an array passing a given truth test. + * Get 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) + 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); @@ -96,13 +78,8 @@ class Arr /** * 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) + public static function get(ArrayAccess|array $array, string|int|null $key, mixed $default = null): mixed { if (! static::accessible($array)) { return Helpers::value($default); 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 index ab217d6ff..5b4bd46fb 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Support/Helpers.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Support/Helpers.php @@ -7,13 +7,9 @@ use Closure; class Helpers { /** - * Return the default value of the given value. - * - * @param mixed $value - * @param mixed $args - * @return mixed + * Get the default value of the given value. */ - public static function value($value, ...$args) + public static function value(mixed $value, mixed ...$args): mixed { return $value instanceof Closure ? $value(...$args) : $value; } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Support/Str.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Support/Str.php index 820834e21..ced2a5fd1 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Support/Str.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Support/Str.php @@ -5,19 +5,49 @@ namespace LdapRecord\Support; class Str { /** - * Return the remainder of a string after the last occurrence of a given value. - * - * @param string $subject - * @param string $search - * @return string + * Determine if a given string matches a given pattern. */ - public static function afterLast($subject, $search) + public static function is(string|iterable $pattern, string $value): bool + { + if (! is_iterable($pattern)) { + $pattern = [$pattern]; + } + + foreach ($pattern as $pattern) { + $pattern = (string) $pattern; + + // If the given value 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 === $value) { + 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', $value) === 1) { + return true; + } + } + + return false; + } + + /** + * Get the remainder of a string after the last occurrence of a given value. + */ + public static function afterLast(string $subject, string $search): string { if ($search === '') { return $subject; } - $position = strrpos($subject, (string) $search); + $position = strrpos($subject, $search); if ($position === false) { return $subject; @@ -28,12 +58,8 @@ class Str /** * Determine if a given string starts with a given substring. - * - * @param string $haystack - * @param string|string[] $needles - * @return bool */ - public static function startsWith($haystack, $needles) + public static function startsWith(string $haystack, array|string $needles): bool { foreach ((array) $needles as $needle) { if ((string) $needle !== '' && str_starts_with($haystack, $needle)) { @@ -46,12 +72,8 @@ class Str /** * Determine if a given string ends with a given substring. - * - * @param string $haystack - * @param string|string[] $needles - * @return bool */ - public static function endsWith($haystack, $needles) + public static function endsWith(string $haystack, array|string $needles): bool { foreach ((array) $needles as $needle) { if ( @@ -67,14 +89,8 @@ class Str /** * Execute a callback when a needle is found in the haystack. - * - * @param string $haystack - * @param string|string[] $needles - * @param \Closure|mixed $callback - * @param \Closure|mixed $default - * @return mixed */ - public static function whenContains($haystack, $needles, $callback, $default = null) + public static function whenContains(string $haystack, array|string $needles, mixed $callback, mixed $default = null): mixed { foreach ((array) $needles as $needle) { if (static::contains($haystack, $needle)) { @@ -87,13 +103,8 @@ class Str /** * Determine if a given string contains a given substring. - * - * @param string $haystack - * @param string|string[] $needles - * @param bool $ignoreCase - * @return bool */ - public static function contains($haystack, $needles, $ignoreCase = false) + public static function contains(string $haystack, array|string $needles, bool $ignoreCase = false): bool { if ($ignoreCase) { $haystack = mb_strtolower($haystack); @@ -110,15 +121,9 @@ class Str } /** - * Returns the number of substring occurrences. - * - * @param string $haystack - * @param string $needle - * @param int $offset - * @param int|null $length - * @return int + * Get the number of substring occurrences. */ - public static function substrCount($haystack, $needle, $offset = 0, $length = null) + public static function substrCount(string $haystack, string $needle, int $offset = 0, ?int $length = null): int { if (! is_null($length)) { return substr_count($haystack, $needle, $offset, $length); diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/AuthGuardFake.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/AuthGuardFake.php index 4a691506d..36574c0d5 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/AuthGuardFake.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/AuthGuardFake.php @@ -8,11 +8,6 @@ class AuthGuardFake extends Guard { /** * Always allow binding as configured user. - * - * @return bool */ - public function bindAsConfiguredUser() - { - return true; - } + public function bindAsConfiguredUser(): void {} } diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/ConnectionFake.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/ConnectionFake.php index 09530f5c7..2c76a06d5 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/ConnectionFake.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/ConnectionFake.php @@ -4,6 +4,7 @@ namespace LdapRecord\Testing; use LdapRecord\Auth\Guard; use LdapRecord\Connection; +use LdapRecord\LdapInterface; use LdapRecord\Models\Model; class ConnectionFake extends Connection @@ -13,25 +14,19 @@ class ConnectionFake extends Connection * * @var LdapFake */ - protected $ldap; + protected LdapInterface $ldap; /** * Whether the fake is connected. - * - * @var bool */ - protected $connected = false; + protected bool $connected = false; /** * Make a new fake LDAP connection instance. - * - * @param array $config - * @param string $ldap - * @return static */ - public static function make(array $config = [], $ldap = LdapFake::class) + public static function make(array $config = [], string $ldap = LdapFake::class): static { - $connection = new static($config, new $ldap()); + $connection = new static($config, new $ldap); $connection->configure(); @@ -40,13 +35,10 @@ class ConnectionFake extends Connection /** * Set the user to authenticate as. - * - * @param Model|string $user - * @return $this */ - public function actingAs($user) + public function actingAs(Model|string $user): static { - $this->ldap->shouldAuthenticateWith( + $this->ldap->shouldAllowBindWith( $user instanceof Model ? $user->getDn() : $user ); @@ -55,10 +47,8 @@ class ConnectionFake extends Connection /** * Set the connection to bypass bind attempts as the configured user. - * - * @return $this */ - public function shouldBeConnected() + public function shouldBeConnected(): static { $this->connected = true; @@ -71,10 +61,8 @@ class ConnectionFake extends Connection /** * Set the connection to attempt binding as the configured user. - * - * @return $this */ - public function shouldNotBeConnected() + public function shouldNotBeConnected(): static { $this->connected = false; @@ -86,10 +74,20 @@ class ConnectionFake extends Connection } /** - * @inheritdoc + * {@inheritdoc} */ - public function isConnected() + 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 index 72be02af3..90cbf7ae6 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/DirectoryFake.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/DirectoryFake.php @@ -7,44 +7,50 @@ use LdapRecord\Container; class DirectoryFake { /** - * Setup the fake connection. + * The LDAP connections that were replaced with fakes. * - * @param string|null $name - * @return ConnectionFake + * @var \LdapRecord\Connection[] + */ + protected static array $replaced = []; + + /** + * Replace a connection a fake. * * @throws \LdapRecord\ContainerException */ - public static function setup($name = null) + public static function setup(?string $name = null): ConnectionFake { - $connection = Container::getConnection($name); + $name = $name ?? Container::getDefaultConnectionName(); + + $connection = static::$replaced[$name] = Container::getConnection($name); $fake = static::makeConnectionFake( $connection->getConfiguration()->all() ); - // Replace the connection with a fake. Container::addConnection($fake, $name); return $fake; } /** - * Reset the container. - * - * @return void + * Replace all faked connections with their original. */ - public static function tearDown() + public static function tearDown(): void { - Container::reset(); + foreach (static::$replaced as $name => $connection) { + Container::getConnection($name)->tearDown(); + } + + Container::flush(); + + static::$replaced = []; } /** * Make a connection fake. - * - * @param array $config - * @return ConnectionFake */ - public static function makeConnectionFake(array $config = []) + 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 index 693459149..2e010d7d7 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/LdapExpectation.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/LdapExpectation.php @@ -3,7 +3,9 @@ namespace LdapRecord\Testing; use Closure; +use Exception; use LdapRecord\LdapRecordException; +use LdapRecord\LdapResultResponse; use PHPUnit\Framework\Constraint\Callback; use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\Constraint\IsEqual; @@ -13,91 +15,76 @@ class LdapExpectation { /** * The value to return from the expectation. - * - * @var mixed */ - protected $value; + protected mixed $value = null; /** * The exception to throw from the expectation. - * - * @var null|LdapRecordException|\Exception */ - protected $exception; + protected ?Exception $exception = null; /** - * The amount of times the expectation should be called. - * - * @var int + * The tracked number of times the expectation should be called. */ - protected $count = 1; + protected int $count = 1; + + /** + * The original number of times the expectation should be called. + */ + protected int $originalCount = 1; + + /** + * The actual number of times the expectation was called. + */ + protected int $called = 0; /** * The method that the expectation belongs to. - * - * @var string */ - protected $method; + protected ?string $method = null; /** * The methods argument's. - * - * @var array */ - protected $args = []; + protected array $args = []; /** * Whether the same expectation should be returned indefinitely. - * - * @var bool */ - protected $indefinitely = true; + protected bool $indefinitely = true; /** - * Whether the expectation should return errors. - * - * @var bool + * Whether the expectation should return an error. */ - protected $errors = false; + protected bool $errors = false; /** - * The error number to return. - * - * @var int + * The error code to return. */ - protected $errorCode = 1; + protected int $errorCode = 1; /** - * The last error string to return. - * - * @var string + * The error message to return. */ - protected $errorMessage = ''; + protected string $errorMessage = 'Unknown error'; /** * The diagnostic message string to return. - * - * @var string */ - protected $errorDiagnosticMessage = ''; + protected ?string $errorDiagnosticMessage = null; /** * Constructor. - * - * @param string $method */ - public function __construct($method) + public function __construct(string $method) { $this->method = $method; } /** * Set the arguments that the operation should receive. - * - * @param mixed $args - * @return $this */ - public function with($args) + public function with(mixed $args): static { $this->args = array_map(function ($arg) { if ($arg instanceof Closure) { @@ -116,11 +103,8 @@ class LdapExpectation /** * Set the expected value to return. - * - * @param mixed $value - * @return $this */ - public function andReturn($value) + public function andReturn(mixed $value): static { $this->value = $value; @@ -128,31 +112,62 @@ class LdapExpectation } /** - * The error message to return from the expectation. - * - * @param int $code - * @param string $error - * @param string $diagnosticMessage - * @return $this + * Set the expected value to return true. */ - public function andReturnError($code = 1, $error = '', $diagnosticMessage = '') + 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 = $code; - $this->errorMessage = $error; + $this->errorCode = $errorCode; + $this->errorMessage = $errorMessage; $this->errorDiagnosticMessage = $diagnosticMessage; return $this; } /** - * Set the expected exception to throw. - * - * @param string|\Exception|LdapRecordException $exception - * @return $this + * Return an error LDAP result response. */ - public function andThrow($exception) + 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); @@ -165,45 +180,36 @@ class LdapExpectation /** * Set the expectation to be only called once. - * - * @return $this */ - public function once() + public function once(): static { - return $this->times(1); + return $this->times(); } /** * Set the expectation to be only called twice. - * - * @return $this */ - public function twice() + public function twice(): static { return $this->times(2); } /** * Set the expectation to be called the given number of times. - * - * @param int $count - * @return $this */ - public function times($count = 1) + public function times(int $count = 1): static { $this->indefinitely = false; - $this->count = $count; + $this->originalCount = $this->count = $count; return $this; } /** * Get the method the expectation belongs to. - * - * @return string */ - public function getMethod() + public function getMethod(): string { if (is_null($this->method)) { throw new UnexpectedValueException('An expectation must have a method.'); @@ -214,89 +220,105 @@ class LdapExpectation /** * Get the expected call count. - * - * @return int */ - public function getExpectedCount() + 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() + public function getExpectedArgs(): array { return $this->args; } /** * Get the expected exception. - * - * @return null|\Exception|LdapRecordException */ - public function getExpectedException() + public function getExpectedException(): ?Exception { return $this->exception; } /** * Get the expected value. - * - * @return mixed */ - public function getExpectedValue() + public function getExpectedValue(): mixed { return $this->value; } /** - * Determine whether the expectation is returning an error. - * - * @return bool + * Determine whether the expectation can be called indefinitely. */ - public function isReturningError() + public function isIndefinite(): bool + { + return $this->indefinitely; + } + + /** + * Determine whether the expectation is returning an error. + */ + public function isReturningError(): bool { return $this->errors; } /** - * @return int + * Get the expected error code. */ - public function getExpectedErrorCode() + public function getExpectedErrorCode(): int { return $this->errorCode; } /** - * @return string + * Get the expected error message. */ - public function getExpectedErrorMessage() + public function getExpectedErrorMessage(): ?string { return $this->errorMessage; } /** - * @return string + * Get the expected diagnostic message. */ - public function getExpectedErrorDiagnosticMessage() + public function getExpectedErrorDiagnosticMessage(): ?string { return $this->errorDiagnosticMessage; } /** - * Decrement the call count of the expectation. - * - * @return $this + * Decrement the expected count of the expectation. */ - public function decrementCallCount() + 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 000000000..74a3ebe33 --- /dev/null +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/LdapExpectationException.php @@ -0,0 +1,7 @@ + */ - protected $expectations = []; + protected array $expectations = []; /** * The default fake error number. - * - * @var int */ - protected $errNo = 1; + protected int $errNo = 1; /** * The default fake last error string. - * - * @var string */ - protected $lastError = ''; + protected string $lastError = 'Unknown error'; /** * The default fake diagnostic message string. - * - * @var string */ - protected $diagnosticMessage = ''; + protected ?string $diagnosticMessage = null; /** * Create a new expected operation. - * - * @param string $method - * @return LdapExpectation */ - public static function operation($method) + public static function operation(string $method): LdapExpectation { return new LdapExpectation($method); } /** - * Set the user that will pass binding. - * - * @param string $dn - * @return $this + * Set the fake LDAP host. */ - public function shouldAuthenticateWith($dn) + public function setHost(string $host): void { - return $this->expect( - static::operation('bind')->with($dn, PHPUnit::anything())->andReturn(true) - ); + $this->host = $host; } /** * Add an LDAP method expectation. - * - * @param LdapExpectation|array $expectations - * @return $this */ - public function expect($expectations = []) + public function expect(LdapExpectation|array $expectations = []): static { $expectations = Arr::wrap($expectations); @@ -101,11 +85,8 @@ class LdapFake implements LdapInterface /** * Determine if the method has any expectations. - * - * @param string $method - * @return bool */ - public function hasExpectations($method) + public function hasExpectations(string $method): bool { return count($this->getExpectations($method)) > 0; } @@ -113,33 +94,55 @@ class LdapFake implements LdapInterface /** * Get expectations by method. * - * @param string $method - * @return LdapExpectation[]|mixed + * @return LdapExpectation[] */ - public function getExpectations($method) + public function getExpectations(string $method): array { return $this->expectations[$method] ?? []; } /** * Remove an expectation by method and key. - * - * @param string $method - * @param int $key - * @return void */ - public function removeExpectation($method, $key) + public function removeExpectation(string $method, int $key): void { unset($this->expectations[$method][$key]); } /** - * Set the error number of a failed bind attempt. - * - * @param int $number - * @return $this + * Set the fake to allow any bind attempt. */ - public function shouldReturnErrorNumber($number = 1) + 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; @@ -148,11 +151,8 @@ class LdapFake implements LdapInterface /** * Set the last error of a failed bind attempt. - * - * @param string $message - * @return $this */ - public function shouldReturnError($message = '') + public function shouldReturnError(string $message): static { $this->lastError = $message; @@ -161,11 +161,8 @@ class LdapFake implements LdapInterface /** * Set the diagnostic message of a failed bind attempt. - * - * @param string $message - * @return $this */ - public function shouldReturnDiagnosticMessage($message = '') + public function shouldReturnDiagnosticMessage(?string $message): static { $this->diagnosticMessage = $message; @@ -174,38 +171,32 @@ class LdapFake implements LdapInterface /** * Return a fake error number. - * - * @return int */ - public function errNo() + public function errNo(): int { return $this->errNo; } /** * Return a fake error. - * - * @return string */ - public function getLastError() + public function getLastError(): string { return $this->lastError; } /** - * @inheritdoc + * Return a fake diagnostic message. */ - public function getDiagnosticMessage() + public function getDiagnosticMessage(): ?string { return $this->diagnosticMessage; } /** * Return a fake detailed error. - * - * @return DetailedError */ - public function getDetailedError() + public function getDetailedError(): DetailedError { return new DetailedError( $this->errNo(), @@ -215,258 +206,309 @@ class LdapFake implements LdapInterface } /** - * @inheritdoc + * {@inheritdoc} */ - public function getEntries($searchResults) + public function getEntries(mixed $result): array { - return $searchResults; + return $result; } /** - * @inheritdoc + * {@inheritDoc} */ - public function isUsingSSL() + public function getValuesLen(mixed $entry, string $attribute): array|false { - return $this->hasExpectations('isUsingSSL') - ? $this->resolveExpectation('isUsingSSL') + 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 + * {@inheritdoc} */ - public function isUsingTLS() + public function isUsingTLS(): bool { - return $this->hasExpectations('isUsingTLS') - ? $this->resolveExpectation('isUsingTLS') + return $this->hasExpectations(__FUNCTION__) + ? $this->resolveExpectation(__FUNCTION__) : $this->useTLS; } /** - * @inheritdoc + * {@inheritdoc} */ - public function isBound() + public function isBound(): bool { - return $this->hasExpectations('isBound') - ? $this->resolveExpectation('isBound') + return $this->hasExpectations(__FUNCTION__) + ? $this->resolveExpectation(__FUNCTION__) : $this->bound; } /** - * @inheritdoc + * {@inheritdoc} */ - public function getHost() + public function setOption(int $option, mixed $value): bool { - return $this->hasExpectations('getHost') - ? $this->resolveExpectation('getHost') - : $this->host; - } - - /** - * @inheritdoc - */ - public function setOption($option, $value) - { - return $this->hasExpectations('setOption') - ? $this->resolveExpectation('setOption', func_get_args()) + return $this->hasExpectations(__FUNCTION__) + ? $this->resolveExpectation(__FUNCTION__, func_get_args()) : true; } /** - * @inheritdoc + * {@inheritdoc} */ - public function getOption($option, &$value = null) + public function getOption(int $option, mixed &$value = null): mixed { - return $this->resolveExpectation('getOption', func_get_args()); + return $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function startTLS() + public function startTLS(): bool { - return $this->resolveExpectation('startTLS', func_get_args()); + return $this->secure = $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function connect($hosts = [], $port = 389) + 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('connect') - ? $this->resolveExpectation('connect', func_get_args()) + return $this->connection = $this->hasExpectations(__FUNCTION__) + ? $this->resolveExpectation(__FUNCTION__, func_get_args()) : true; } /** - * @inheritdoc + * {@inheritdoc} */ - public function close() + public function close(): bool { - $this->connection = null; $this->bound = false; - $this->host = null; + $this->secure = false; - return $this->hasExpectations('close') - ? $this->resolveExpectation('close') + $this->host = null; + $this->protocol = null; + $this->connection = null; + + return $this->hasExpectations(__FUNCTION__) + ? $this->resolveExpectation(__FUNCTION__) : true; } /** - * @inheritdoc + * {@inheritdoc} */ - public function bind($username, $password) + public function bind(?string $dn = null, ?string $password = null, ?array $controls = null): LdapResultResponse { - return $this->bound = $this->resolveExpectation('bind', func_get_args()); + $result = $this->resolveExpectation(__FUNCTION__, func_get_args()); + + $this->handleBindResponse($result); + + return $result; } /** - * @inheritdoc + * {@inheritDoc} */ - public function search($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = LDAP_DEREF_NEVER, $serverControls = []) + public function saslBind(?string $dn = null, ?string $password = null, ?array $options = null): bool { - return $this->resolveExpectation('search', func_get_args()); + return $this->bound = $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function listing($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = LDAP_DEREF_NEVER, $serverControls = []) + 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('listing', func_get_args()); + return $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function read($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = LDAP_DEREF_NEVER, $serverControls = []) + 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('read', func_get_args()); + return $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function parseResult($result, &$errorCode, &$dn, &$errorMessage, &$referrals, &$serverControls = []) + 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('parseResult', func_get_args()); + return $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function add($dn, array $entry) + public function parseResult(mixed $result, int &$errorCode = 0, ?string &$dn = null, ?string &$errorMessage = null, ?array &$referrals = null, ?array &$controls = null): LdapResultResponse|false { - return $this->resolveExpectation('add', func_get_args()); + return $this->hasExpectations(__FUNCTION__) + ? $this->resolveExpectation(__FUNCTION__, func_get_args()) + : new LdapResultResponse; } /** - * @inheritdoc + * {@inheritdoc} */ - public function delete($dn) + public function add(string $dn, array $entry): bool { - return $this->resolveExpectation('delete', func_get_args()); + return $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function rename($dn, $newRdn, $newParent, $deleteOldRdn = false) + public function delete(string $dn): bool { - return $this->resolveExpectation('rename', func_get_args()); + return $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function modify($dn, array $entry) + public function rename(string $dn, string $newRdn, string $newParent, bool $deleteOldRdn = false): bool { - return $this->resolveExpectation('modify', func_get_args()); + return $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function modifyBatch($dn, array $values) + public function modify(string $dn, array $entry): bool { - return $this->resolveExpectation('modifyBatch', func_get_args()); + return $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function modAdd($dn, array $entry) + public function modifyBatch(string $dn, array $values): bool { - return $this->resolveExpectation('modAdd', func_get_args()); + return $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function modReplace($dn, array $entry) + public function modAdd(string $dn, array $entry): bool { - return $this->resolveExpectation('modReplace', func_get_args()); + return $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function modDelete($dn, array $entry) + public function modReplace(string $dn, array $entry): bool { - return $this->resolveExpectation('modDelete', func_get_args()); + return $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function controlPagedResult($pageSize = 1000, $isCritical = false, $cookie = '') + public function modDelete(string $dn, array $entry): bool { - return $this->resolveExpectation('controlPagedResult', func_get_args()); + return $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function controlPagedResultResponse($result, &$cookie) + public function freeResult(mixed $result): bool { - return $this->resolveExpectation('controlPagedResultResponse', func_get_args()); + return $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** - * @inheritdoc + * {@inheritdoc} */ - public function freeResult($result) + public function err2Str(int $number): string { - return $this->resolveExpectation('freeResult', func_get_args()); - } - - /** - * @inheritdoc - */ - public function err2Str($number) - { - return $this->resolveExpectation('err2Str', func_get_args()); + return $this->resolveExpectation(__FUNCTION__, func_get_args()); } /** * Resolve the methods expectations. * - * @param string $method - * @param array $args - * @return mixed - * - * @throws Exception + * @throws LdapExpectationException */ - protected function resolveExpectation($method, array $args = []) + protected function resolveExpectation(string $method, array $args = []): mixed { foreach ($this->getExpectations($method) as $key => $expectation) { $this->assertMethodArgumentsMatch($method, $expectation->getExpectedArgs(), $args); - $expectation->decrementCallCount(); + $expectation->decrementExpectedCount(); if ($expectation->getExpectedCount() === 0) { $this->removeExpectation($method, $key); @@ -483,31 +525,43 @@ class LdapFake implements LdapInterface return $expectation->getExpectedValue(); } - throw new Exception("LDAP method [$method] was unexpected."); + throw new LdapExpectationException("LDAP method [$method] was unexpected."); } /** * Apply the expectation error to the fake. - * - * @param LdapExpectation $expectation - * @return void */ - protected function applyExpectationError(LdapExpectation $expectation) + 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 string $method * @param Constraint[] $expectedArgs - * @param array $methodArgs - * @return void */ - protected function assertMethodArgumentsMatch($method, array $expectedArgs = [], array $methodArgs = []) + protected function assertMethodArgumentsMatch(string $method, array $expectedArgs = [], array $methodArgs = []): void { foreach ($expectedArgs as $key => $constraint) { $argNumber = $key + 1; @@ -515,7 +569,7 @@ class LdapFake implements LdapInterface PHPUnit::assertArrayHasKey( $key, $methodArgs, - "LDAP method [$method] argument #{$argNumber} does not exist." + "LDAP method [$method] argument #$argNumber does not exist." ); $constraint->evaluate( diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Utilities.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Utilities.php deleted file mode 100644 index c4f5190d8..000000000 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Utilities.php +++ /dev/null @@ -1,187 +0,0 @@ - $value) { - $dn[$rdn] = static::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 - * - * @see https://github.com/ChadSikorra - * @see 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 (is_null($binGuid) || trim($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); - - return sprintf('%s-%s-%s-%s-%s', $hex1, $hex2, $hex3, $hex4, $hex5); - } - - /** - * 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; - } - - /** - * Round a Windows timestamp down to seconds and remove - * the seconds between 1601-01-01 and 1970-01-01. - * - * @param int $windowsTime - * @return int - */ - public static function convertWindowsTimeToUnixTime($windowsTime) - { - return (int) (round($windowsTime / 10000000) - 11644473600); - } - - /** - * Convert a Unix timestamp to Windows timestamp. - * - * @param int $unixTime - * @return int - */ - 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", (string) $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}$/', (string) $guid); - } -} diff --git a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/Arr.php b/data/web/inc/lib/vendor/illuminate/collections/Arr.php similarity index 88% rename from data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/Arr.php rename to data/web/inc/lib/vendor/illuminate/collections/Arr.php index f3a4b042e..d83cf5f73 100644 --- a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/Arr.php +++ b/data/web/inc/lib/vendor/illuminate/collections/Arr.php @@ -1,10 +1,10 @@ $value) { @@ -214,7 +216,7 @@ class Arr * @param mixed $default * @return mixed */ - public static function last($array, callable $callback = null, $default = null) + public static function last($array, ?callable $callback = null, $default = null) { if (is_null($callback)) { return empty($array) ? value($default) : end($array); @@ -223,6 +225,22 @@ class Arr 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. * @@ -410,9 +428,7 @@ class Arr */ public static function isAssoc(array $array) { - $keys = array_keys($array); - - return array_keys($keys) !== $keys; + return ! array_is_list($array); } /** @@ -425,7 +441,7 @@ class Arr */ public static function isList($array) { - return ! self::isAssoc($array); + return array_is_list($array); } /** @@ -476,9 +492,7 @@ class Arr */ public static function prependKeysWith($array, $prependWith) { - return Collection::make($array)->mapWithKeys(function ($item, $key) use ($prependWith) { - return [$prependWith.$key => $item]; - })->all(); + return static::mapWithKeys($array, fn ($item, $key) => [$prependWith.$key => $item]); } /** @@ -493,6 +507,32 @@ class Arr 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. * @@ -565,6 +605,35 @@ class Arr 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. * @@ -759,7 +828,7 @@ class Arr } } - if (static::isAssoc($array)) { + if (! array_is_list($array)) { $descending ? krsort($array, $options) : ksort($array, $options); @@ -772,6 +841,18 @@ class Arr 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. * diff --git a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/Collection.php b/data/web/inc/lib/vendor/illuminate/collections/Collection.php similarity index 86% rename from data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/Collection.php rename to data/web/inc/lib/vendor/illuminate/collections/Collection.php index e705ee0d0..2f0c768f1 100644 --- a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/Collection.php +++ b/data/web/inc/lib/vendor/illuminate/collections/Collection.php @@ -1,26 +1,28 @@ - * @implements \Tightenco\Collect\Support\Enumerable + * @implements \Illuminate\Support\Enumerable */ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerable { /** - * @use \Tightenco\Collect\Support\Traits\EnumeratesValues + * @use \Illuminate\Support\Traits\EnumeratesValues */ use EnumeratesValues, Macroable; @@ -34,7 +36,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Create a new collection. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable|null $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable|null $items * @return void */ public function __construct($items = []) @@ -67,7 +69,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Get a lazy collection for the items in this collection. * - * @return \Tightenco\Collect\Support\LazyCollection + * @return \Illuminate\Support\LazyCollection */ public function lazy() { @@ -220,7 +222,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * @template TCrossJoinKey * @template TCrossJoinValue * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable ...$lists + * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$lists * @return static> */ public function crossJoin(...$lists) @@ -233,7 +235,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Get the items in the collection that are not present in the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function diff($items) @@ -244,7 +246,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Get the items in the collection that are not present in the given items, using the callback. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TValue, TValue): int $callback * @return static */ @@ -256,7 +258,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Get the items in the collection whose keys and values are not present in the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function diffAssoc($items) @@ -267,7 +269,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Get the items in the collection whose keys and values are not present in the given items, using the callback. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TKey, TKey): int $callback * @return static */ @@ -279,7 +281,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Get the items in the collection whose keys are not present in the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function diffKeys($items) @@ -290,7 +292,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Get the items in the collection whose keys are not present in the given items, using the callback. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TKey, TKey): int $callback * @return static */ @@ -356,11 +358,15 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Get all items except for those with the specified keys. * - * @param \Tightenco\Collect\Support\Enumerable|array $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)) { @@ -373,10 +379,10 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Run a filter over each of the items. * - * @param (callable(TValue, TKey): bool)|null $callback + * @param (callable(TValue, TKey): bool)|null $callback * @return static */ - public function filter(callable $callback = null) + public function filter(?callable $callback = null) { if ($callback) { return new static(Arr::where($this->items, $callback)); @@ -394,7 +400,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * @param TFirstDefault|(\Closure(): TFirstDefault) $default * @return TValue|TFirstDefault */ - public function first(callable $callback = null, $default = null) + public function first(?callable $callback = null, $default = null) { return Arr::first($this->items, $callback, $default); } @@ -423,12 +429,13 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Remove an item from the collection by key. * - * @param TKey|array $keys + * \Illuminate\Contracts\Support\Arrayable|iterable|TKey $keys + * * @return $this */ public function forget($keys) { - foreach ((array) $keys as $key) { + foreach ($this->getArrayableItems($keys) as $key) { $this->offsetUnset($key); } @@ -456,9 +463,11 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Get an item from the collection by key or add it to collection if it does not exist. * + * @template TGetOrPutValue + * * @param mixed $key - * @param mixed $value - * @return mixed + * @param TGetOrPutValue|(\Closure(): TGetOrPutValue) $value + * @return TValue|TGetOrPutValue */ public function getOrPut($key, $value) { @@ -500,6 +509,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl foreach ($groupKeys as $groupKey) { $groupKey = match (true) { is_bool($groupKey) => (int) $groupKey, + $groupKey instanceof \BackedEnum => $groupKey->value, $groupKey instanceof \Stringable => (string) $groupKey, default => $groupKey, }; @@ -603,7 +613,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl $first = $this->first(); - if (is_array($first) || (is_object($first) && ! $first instanceof \Illuminate\Support\Stringable)) { + if (is_array($first) || (is_object($first) && ! $first instanceof Stringable)) { return implode($glue ?? '', $this->pluck($value)->all()); } @@ -613,7 +623,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Intersect the collection with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function intersect($items) @@ -624,7 +634,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Intersect the collection with the given items, using the callback. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TValue, TValue): int $callback * @return static */ @@ -636,7 +646,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Intersect the collection with the given items with additional index check. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function intersectAssoc($items) @@ -647,7 +657,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Intersect the collection with the given items with additional index check, using the callback. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TValue, TValue): int $callback * @return static */ @@ -659,7 +669,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Intersect the collection with the given items by key. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function intersectByKeys($items) @@ -738,7 +748,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * @param TLastDefault|(\Closure(): TLastDefault) $default * @return TValue|TLastDefault */ - public function last(callable $callback = null, $default = null) + public function last(?callable $callback = null, $default = null) { return Arr::last($this->items, $callback, $default); } @@ -813,23 +823,13 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl */ public function mapWithKeys(callable $callback) { - $result = []; - - foreach ($this->items as $key => $value) { - $assoc = $callback($value, $key); - - foreach ($assoc as $mapKey => $mapValue) { - $result[$mapKey] = $mapValue; - } - } - - return new static($result); + return new static(Arr::mapWithKeys($this->items, $callback)); } /** * Merge the collection with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function merge($items) @@ -842,7 +842,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * * @template TMergeRecursiveValue * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function mergeRecursive($items) @@ -855,7 +855,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * * @template TCombineValue * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $values + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values * @return static */ public function combine($values) @@ -866,7 +866,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Union the collection with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function union($items) @@ -901,7 +901,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Get the items with the specified keys. * - * @param \Tightenco\Collect\Support\Enumerable|array|string|null $keys + * @param \Illuminate\Support\Enumerable|array|string|null $keys * @return static */ public function only($keys) @@ -919,6 +919,27 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl 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. * @@ -978,8 +999,11 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Push all of the given items onto the collection. * - * @param iterable $source - * @return static + * @template TConcatKey of array-key + * @template TConcatValue + * + * @param iterable $source + * @return static */ public function concat($source) { @@ -1024,27 +1048,28 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * 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) + 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))); + return new static(Arr::random($this->items, $number($this), $preserveKeys)); } - return new static(Arr::random($this->items, $number)); + return new static(Arr::random($this->items, $number, $preserveKeys)); } /** * Replace the collection items with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function replace($items) @@ -1055,7 +1080,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Recursively replace the collection items with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function replaceRecursive($items) @@ -1078,7 +1103,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * * @param TValue|(callable(TValue,TKey): bool) $value * @param bool $strict - * @return TKey|bool + * @return TKey|false */ public function search($value, $strict = false) { @@ -1100,17 +1125,27 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * * @param int $count * @return static|TValue|null + * + * @throws \InvalidArgumentException */ public function shift($count = 1) { - if ($count === 1) { - return array_shift($this->items); + 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(); @@ -1144,9 +1179,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl { $chunks = floor(($this->count() - $size) / $step) + 1; - return static::times($chunks, function ($number) use ($size, $step) { - return $this->slice(($number - 1) * $step, $size); - }); + return static::times($chunks, fn ($number) => $this->slice(($number - 1) * $step, $size)); } /** @@ -1250,8 +1283,8 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * @param mixed $value * @return TValue * - * @throws \Tightenco\Collect\Support\ItemNotFoundException - * @throws \Tightenco\Collect\Support\MultipleItemsFoundException + * @throws \Illuminate\Support\ItemNotFoundException + * @throws \Illuminate\Support\MultipleItemsFoundException */ public function sole($key = null, $operator = null, $value = null) { @@ -1282,7 +1315,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * @param mixed $value * @return TValue * - * @throws \Tightenco\Collect\Support\ItemNotFoundException + * @throws \Illuminate\Support\ItemNotFoundException */ public function firstOrFail($key = null, $operator = null, $value = null) { @@ -1378,7 +1411,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl public function sortBy($callback, $options = SORT_REGULAR, $descending = false) { if (is_array($callback) && ! is_callable($callback)) { - return $this->sortByMany($callback); + return $this->sortByMany($callback, $options); } $results = []; @@ -1409,13 +1442,14 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * Sort the collection using multiple comparisons. * * @param array $comparisons + * @param int $options * @return static */ - protected function sortByMany(array $comparisons = []) + protected function sortByMany(array $comparisons = [], int $options = SORT_REGULAR) { $items = $this->items; - uasort($items, function ($a, $b) use ($comparisons) { + uasort($items, function ($a, $b) use ($comparisons, $options) { foreach ($comparisons as $comparison) { $comparison = Arr::wrap($comparison); @@ -1433,7 +1467,21 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl $values = array_reverse($values); } - $result = $values[0] <=> $values[1]; + 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) { @@ -1456,6 +1504,16 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl */ 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); } @@ -1568,6 +1626,16 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl 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. * @@ -1622,7 +1690,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * * @template TZipValue * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable ...$items + * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$items * @return static> */ public function zip($items) @@ -1695,7 +1763,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Get a base Support collection instance from this collection. * - * @return \Tightenco\Collect\Support\Collection + * @return \Illuminate\Support\Collection */ public function toBase() { diff --git a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/Enumerable.php b/data/web/inc/lib/vendor/illuminate/collections/Enumerable.php similarity index 88% rename from data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/Enumerable.php rename to data/web/inc/lib/vendor/illuminate/collections/Enumerable.php index 1f0db03ae..806a6923f 100644 --- a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/Enumerable.php +++ b/data/web/inc/lib/vendor/illuminate/collections/Enumerable.php @@ -1,20 +1,21 @@ + * @template-covariant TValue + * + * @extends \Illuminate\Contracts\Support\Arrayable * @extends \IteratorAggregate */ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable @@ -25,7 +26,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @template TMakeKey of array-key * @template TMakeValue * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable|null $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable|null $items * @return static */ public static function make($items = []); @@ -37,7 +38,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @param callable|null $callback * @return static */ - public static function times($number, callable $callback = null); + public static function times($number, ?callable $callback = null); /** * Create a collection with the given range. @@ -167,7 +168,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @template TCrossJoinKey * @template TCrossJoinValue * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable ...$lists + * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$lists * @return static> */ public function crossJoin(...$lists); @@ -190,7 +191,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Get the items that are not present in the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function diff($items); @@ -198,7 +199,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Get the items that are not present in the given items, using the callback. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TValue, TValue): int $callback * @return static */ @@ -207,7 +208,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Get the items whose keys and values are not present in the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function diffAssoc($items); @@ -215,7 +216,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Get the items whose keys and values are not present in the given items, using the callback. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TKey, TKey): int $callback * @return static */ @@ -224,7 +225,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Get the items whose keys are not present in the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function diffKeys($items); @@ -232,7 +233,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Get the items whose keys are not present in the given items, using the callback. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TKey, TKey): int $callback * @return static */ @@ -284,7 +285,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Get all items except for those with the specified keys. * - * @param \Tightenco\Collect\Support\Enumerable|array $keys + * @param \Illuminate\Support\Enumerable|array $keys * @return static */ public function except($keys); @@ -295,7 +296,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @param (callable(TValue): bool)|null $callback * @return static */ - public function filter(callable $callback = null); + public function filter(?callable $callback = null); /** * Apply the callback if the given "value" is (or resolves to) truthy. @@ -307,7 +308,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @param (callable($this): TWhenReturnType)|null $default * @return $this|TWhenReturnType */ - public function when($value, callable $callback = null, callable $default = null); + public function when($value, ?callable $callback = null, ?callable $default = null); /** * Apply the callback if the collection is empty. @@ -318,7 +319,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @param (callable($this): TWhenEmptyReturnType)|null $default * @return $this|TWhenEmptyReturnType */ - public function whenEmpty(callable $callback, callable $default = null); + public function whenEmpty(callable $callback, ?callable $default = null); /** * Apply the callback if the collection is not empty. @@ -329,7 +330,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @param (callable($this): TWhenNotEmptyReturnType)|null $default * @return $this|TWhenNotEmptyReturnType */ - public function whenNotEmpty(callable $callback, callable $default = null); + public function whenNotEmpty(callable $callback, ?callable $default = null); /** * Apply the callback if the given "value" is (or resolves to) truthy. @@ -341,7 +342,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @param (callable($this): TUnlessReturnType)|null $default * @return $this|TUnlessReturnType */ - public function unless($value, callable $callback, callable $default = null); + public function unless($value, callable $callback, ?callable $default = null); /** * Apply the callback unless the collection is empty. @@ -352,7 +353,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @param (callable($this): TUnlessEmptyReturnType)|null $default * @return $this|TUnlessEmptyReturnType */ - public function unlessEmpty(callable $callback, callable $default = null); + public function unlessEmpty(callable $callback, ?callable $default = null); /** * Apply the callback unless the collection is not empty. @@ -363,7 +364,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @param (callable($this): TUnlessNotEmptyReturnType)|null $default * @return $this|TUnlessNotEmptyReturnType */ - public function unlessNotEmpty(callable $callback, callable $default = null); + public function unlessNotEmpty(callable $callback, ?callable $default = null); /** * Filter items by the given key value pair. @@ -404,7 +405,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * Filter items by the given key value pair. * * @param string $key - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $values + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values * @param bool $strict * @return static */ @@ -414,7 +415,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * Filter items by the given key value pair using strict comparison. * * @param string $key - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $values + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values * @return static */ public function whereInStrict($key, $values); @@ -423,7 +424,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * Filter items such that the value of the given key is between the given values. * * @param string $key - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $values + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values * @return static */ public function whereBetween($key, $values); @@ -432,7 +433,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * Filter items such that the value of the given key is not between the given values. * * @param string $key - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $values + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values * @return static */ public function whereNotBetween($key, $values); @@ -441,7 +442,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * Filter items by the given key value pair. * * @param string $key - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $values + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values * @param bool $strict * @return static */ @@ -451,7 +452,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * Filter items by the given key value pair using strict comparison. * * @param string $key - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $values + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values * @return static */ public function whereNotInStrict($key, $values); @@ -475,7 +476,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @param TFirstDefault|(\Closure(): TFirstDefault) $default * @return TValue|TFirstDefault */ - public function first(callable $callback = null, $default = null); + public function first(?callable $callback = null, $default = null); /** * Get the first item by the given key value pair. @@ -549,7 +550,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Concatenate values of a given key as a string. * - * @param string $value + * @param callable|string $value * @param string|null $glue * @return string */ @@ -558,7 +559,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Intersect the collection with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function intersect($items); @@ -566,7 +567,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Intersect the collection with the given items by key. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function intersectByKeys($items); @@ -617,7 +618,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @param TLastDefault|(\Closure(): TLastDefault) $default * @return TValue|TLastDefault */ - public function last(callable $callback = null, $default = null); + public function last(?callable $callback = null, $default = null); /** * Run a map over each of the items. @@ -682,7 +683,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @template TFlatMapKey of array-key * @template TFlatMapValue * - * @param callable(TValue, TKey): (\Tightenco\Collect\Support\Collection|array) $callback + * @param callable(TValue, TKey): (\Illuminate\Support\Collection|array) $callback * @return static */ public function flatMap(callable $callback); @@ -700,7 +701,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Merge the collection with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function merge($items); @@ -710,7 +711,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * * @template TMergeRecursiveValue * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function mergeRecursive($items); @@ -720,7 +721,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * * @template TCombineValue * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $values + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values * @return static */ public function combine($values); @@ -728,7 +729,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Union the collection with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function union($items); @@ -761,7 +762,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Get the items with the specified keys. * - * @param \Tightenco\Collect\Support\Enumerable|array|string $keys + * @param \Illuminate\Support\Enumerable|array|string $keys * @return static */ public function only($keys); @@ -788,8 +789,11 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Push all of the given items onto the collection. * - * @param iterable $source - * @return static + * @template TConcatKey of array-key + * @template TConcatValue + * + * @param iterable $source + * @return static */ public function concat($source); @@ -829,7 +833,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Replace the collection items with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function replace($items); @@ -837,7 +841,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Recursively replace the collection items with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function replaceRecursive($items); @@ -924,8 +928,8 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @param mixed $value * @return TValue * - * @throws \Tightenco\Collect\Support\ItemNotFoundException - * @throws \Tightenco\Collect\Support\MultipleItemsFoundException + * @throws \Illuminate\Support\ItemNotFoundException + * @throws \Illuminate\Support\MultipleItemsFoundException */ public function sole($key = null, $operator = null, $value = null); @@ -937,7 +941,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * @param mixed $value * @return TValue * - * @throws \Tightenco\Collect\Support\ItemNotFoundException + * @throws \Illuminate\Support\ItemNotFoundException */ public function firstOrFail($key = null, $operator = null, $value = null); @@ -1078,8 +1082,10 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Pass the collection into a new class. * - * @param class-string $class - * @return mixed + * @template TPipeIntoValue + * + * @param class-string $class + * @return TPipeIntoValue */ public function pipeInto($class); @@ -1180,7 +1186,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * * @template TZipValue * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable ...$items + * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$items * @return static> */ public function zip($items); @@ -1188,7 +1194,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Collect the values into a collection. * - * @return \Tightenco\Collect\Support\Collection + * @return \Illuminate\Support\Collection */ public function collect(); diff --git a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/HigherOrderCollectionProxy.php b/data/web/inc/lib/vendor/illuminate/collections/HigherOrderCollectionProxy.php similarity index 86% rename from data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/HigherOrderCollectionProxy.php rename to data/web/inc/lib/vendor/illuminate/collections/HigherOrderCollectionProxy.php index 01ac43f96..106356c3a 100644 --- a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/HigherOrderCollectionProxy.php +++ b/data/web/inc/lib/vendor/illuminate/collections/HigherOrderCollectionProxy.php @@ -1,16 +1,16 @@ + * @template-covariant TValue + * + * @implements \Illuminate\Support\Enumerable */ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable { /** - * @use \Tightenco\Collect\Support\Traits\EnumeratesValues + * @use \Illuminate\Support\Traits\EnumeratesValues */ use EnumeratesValues, Macroable; @@ -37,7 +38,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Create a new lazy collection instance. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable|(Closure(): \Generator)|self|array|null $source + * @param \Illuminate\Contracts\Support\Arrayable|iterable|(Closure(): \Generator)|self|array|null $source * @return void */ public function __construct($source = null) @@ -61,7 +62,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * @template TMakeKey of array-key * @template TMakeValue * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable|(Closure(): \Generator)|self|array|null $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable|(Closure(): \Generator)|self|array|null $items * @return static */ public static function make($items = []) @@ -281,7 +282,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * @template TCrossJoinKey * @template TCrossJoinValue * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable ...$arrays + * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$arrays * @return static> */ public function crossJoin(...$arrays) @@ -321,7 +322,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Get the items that are not present in the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function diff($items) @@ -332,7 +333,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Get the items that are not present in the given items, using the callback. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TValue, TValue): int $callback * @return static */ @@ -344,7 +345,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Get the items whose keys and values are not present in the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function diffAssoc($items) @@ -355,7 +356,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Get the items whose keys and values are not present in the given items, using the callback. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TKey, TKey): int $callback * @return static */ @@ -367,7 +368,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Get the items whose keys are not present in the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function diffKeys($items) @@ -378,7 +379,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Get the items whose keys are not present in the given items, using the callback. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TKey, TKey): int $callback * @return static */ @@ -413,7 +414,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Get all items except for those with the specified keys. * - * @param \Tightenco\Collect\Support\Enumerable|array $keys + * @param \Illuminate\Support\Enumerable|array $keys * @return static */ public function except($keys) @@ -427,7 +428,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * @param (callable(TValue, TKey): bool)|null $callback * @return static */ - public function filter(callable $callback = null) + public function filter(?callable $callback = null) { if (is_null($callback)) { $callback = fn ($value) => (bool) $value; @@ -451,7 +452,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * @param TFirstDefault|(\Closure(): TFirstDefault) $default * @return TValue|TFirstDefault */ - public function first(callable $callback = null, $default = null) + public function first(?callable $callback = null, $default = null) { $iterator = $this->getIterator(); @@ -622,7 +623,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Intersect the collection with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function intersect($items) @@ -633,7 +634,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Intersect the collection with the given items, using the callback. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TValue, TValue): int $callback * @return static */ @@ -645,7 +646,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Intersect the collection with the given items with additional index check. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function intersectAssoc($items) @@ -656,7 +657,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Intersect the collection with the given items with additional index check, using the callback. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TValue, TValue): int $callback * @return static */ @@ -668,7 +669,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Intersect the collection with the given items by key. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function intersectByKeys($items) @@ -731,7 +732,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * @param TLastDefault|(\Closure(): TLastDefault) $default * @return TValue|TLastDefault */ - public function last(callable $callback = null, $default = null) + public function last(?callable $callback = null, $default = null) { $needle = $placeholder = new stdClass; @@ -830,7 +831,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Merge the collection with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function merge($items) @@ -843,7 +844,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * * @template TMergeRecursiveValue * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function mergeRecursive($items) @@ -887,7 +888,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Union the collection with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function union($items) @@ -920,7 +921,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Get the items with the specified keys. * - * @param \Tightenco\Collect\Support\Enumerable|array|string $keys + * @param \Illuminate\Support\Enumerable|array|string $keys * @return static */ public function only($keys) @@ -952,11 +953,49 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable }); } + /** + * 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. * - * @param iterable $source - * @return static + * @template TConcatKey of array-key + * @template TConcatValue + * + * @param iterable $source + * @return static */ public function concat($source) { @@ -984,7 +1023,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Replace the collection items with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function replace($items) @@ -1011,7 +1050,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Recursively replace the collection items with the given items. * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function replaceRecursive($items) @@ -1034,7 +1073,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * * @param TValue|(callable(TValue,TKey): bool) $value * @param bool $strict - * @return TKey|bool + * @return TKey|false */ public function search($value, $strict = false) { @@ -1202,8 +1241,8 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * @param mixed $value * @return TValue * - * @throws \Tightenco\Collect\Support\ItemNotFoundException - * @throws \Tightenco\Collect\Support\MultipleItemsFoundException + * @throws \Illuminate\Support\ItemNotFoundException + * @throws \Illuminate\Support\MultipleItemsFoundException */ public function sole($key = null, $operator = null, $value = null) { @@ -1227,7 +1266,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * @param mixed $value * @return TValue * - * @throws \Tightenco\Collect\Support\ItemNotFoundException + * @throws \Illuminate\Support\ItemNotFoundException */ public function firstOrFail($key = null, $operator = null, $value = null) { @@ -1420,7 +1459,21 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable public function take($limit) { if ($limit < 0) { - return $this->passthru('take', func_get_args()); + 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) { @@ -1518,6 +1571,16 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable }); } + /** + * 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. * @@ -1574,7 +1637,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * * @template TZipValue * - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable ...$items + * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$items * @return static> */ public function zip($items) @@ -1715,6 +1778,8 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable */ protected function now() { - return time(); + 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 000000000..d90d835b4 --- /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/tightenco/collect/src/Collect/Support/Traits/EnumeratesValues.php b/data/web/inc/lib/vendor/illuminate/collections/Traits/EnumeratesValues.php similarity index 85% rename from data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/Traits/EnumeratesValues.php rename to data/web/inc/lib/vendor/illuminate/collections/Traits/EnumeratesValues.php index 25cde2216..9718f5bc5 100644 --- a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/Traits/EnumeratesValues.php +++ b/data/web/inc/lib/vendor/illuminate/collections/Traits/EnumeratesValues.php @@ -1,25 +1,28 @@ |iterable|null $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable|null $items * @return static */ public static function make($items = []) @@ -159,7 +164,7 @@ trait EnumeratesValues * @param (callable(int): TTimesValue)|null $callback * @return static */ - public static function times($number, callable $callback = null) + public static function times($number, ?callable $callback = null) { if ($number < 1) { return new static; @@ -296,9 +301,11 @@ trait EnumeratesValues /** * Get a single key's value from the first matching item in the collection. * + * @template TValueDefault + * * @param string $key - * @param mixed $default - * @return mixed + * @param TValueDefault|(\Closure(): TValueDefault) $default + * @return TValue|TValueDefault */ public function value($key, $default = null) { @@ -309,6 +316,35 @@ trait EnumeratesValues 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. * @@ -324,7 +360,7 @@ trait EnumeratesValues * * @template TMapSpreadValue * - * @param callable(mixed): TMapSpreadValue $callback + * @param callable(mixed...): TMapSpreadValue $callback * @return static */ public function mapSpread(callable $callback) @@ -360,7 +396,7 @@ trait EnumeratesValues * @template TFlatMapKey of array-key * @template TFlatMapValue * - * @param callable(TValue, TKey): (\Tightenco\Collect\Support\Collection|array) $callback + * @param callable(TValue, TKey): (\Illuminate\Support\Collection|array) $callback * @return static */ public function flatMap(callable $callback) @@ -455,6 +491,25 @@ trait EnumeratesValues 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. * @@ -479,7 +534,7 @@ trait EnumeratesValues * @param (callable($this): TWhenEmptyReturnType)|null $default * @return $this|TWhenEmptyReturnType */ - public function whenEmpty(callable $callback, callable $default = null) + public function whenEmpty(callable $callback, ?callable $default = null) { return $this->when($this->isEmpty(), $callback, $default); } @@ -493,7 +548,7 @@ trait EnumeratesValues * @param (callable($this): TWhenNotEmptyReturnType)|null $default * @return $this|TWhenNotEmptyReturnType */ - public function whenNotEmpty(callable $callback, callable $default = null) + public function whenNotEmpty(callable $callback, ?callable $default = null) { return $this->when($this->isNotEmpty(), $callback, $default); } @@ -507,7 +562,7 @@ trait EnumeratesValues * @param (callable($this): TUnlessEmptyReturnType)|null $default * @return $this|TUnlessEmptyReturnType */ - public function unlessEmpty(callable $callback, callable $default = null) + public function unlessEmpty(callable $callback, ?callable $default = null) { return $this->whenNotEmpty($callback, $default); } @@ -521,7 +576,7 @@ trait EnumeratesValues * @param (callable($this): TUnlessNotEmptyReturnType)|null $default * @return $this|TUnlessNotEmptyReturnType */ - public function unlessNotEmpty(callable $callback, callable $default = null) + public function unlessNotEmpty(callable $callback, ?callable $default = null) { return $this->whenEmpty($callback, $default); } @@ -577,7 +632,7 @@ trait EnumeratesValues * Filter items by the given key value pair. * * @param string $key - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $values + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values * @param bool $strict * @return static */ @@ -592,7 +647,7 @@ trait EnumeratesValues * Filter items by the given key value pair using strict comparison. * * @param string $key - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $values + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values * @return static */ public function whereInStrict($key, $values) @@ -604,7 +659,7 @@ trait EnumeratesValues * Filter items such that the value of the given key is between the given values. * * @param string $key - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $values + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values * @return static */ public function whereBetween($key, $values) @@ -616,7 +671,7 @@ trait EnumeratesValues * Filter items such that the value of the given key is not between the given values. * * @param string $key - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $values + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values * @return static */ public function whereNotBetween($key, $values) @@ -630,7 +685,7 @@ trait EnumeratesValues * Filter items by the given key value pair. * * @param string $key - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $values + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values * @param bool $strict * @return static */ @@ -645,7 +700,7 @@ trait EnumeratesValues * Filter items by the given key value pair using strict comparison. * * @param string $key - * @param \Tightenco\Collect\Contracts\Support\Arrayable|iterable $values + * @param \Illuminate\Contracts\Support\Arrayable|iterable $values * @return static */ public function whereNotInStrict($key, $values) @@ -694,8 +749,10 @@ trait EnumeratesValues /** * Pass the collection into a new class. * - * @param class-string $class - * @return mixed + * @template TPipeIntoValue + * + * @param class-string $class + * @return TPipeIntoValue */ public function pipeInto($class) { @@ -764,6 +821,21 @@ trait EnumeratesValues 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. * @@ -830,7 +902,7 @@ trait EnumeratesValues /** * Collect the values into a collection. * - * @return \Tightenco\Collect\Support\Collection + * @return \Illuminate\Support\Collection */ public function collect() { @@ -952,21 +1024,18 @@ trait EnumeratesValues { if (is_array($items)) { return $items; - } elseif ($items instanceof Enumerable) { - return $items->all(); - } elseif ($items instanceof Arrayable) { - return $items->toArray(); - } elseif ($items instanceof Traversable) { - return iterator_to_array($items); - } elseif ($items instanceof Jsonable) { - return json_decode($items->toJson(), true); - } elseif ($items instanceof JsonSerializable) { - return (array) $items->jsonSerialize(); - } elseif ($items instanceof UnitEnum) { - return [$items]; } - return (array) $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, + }; } /** 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 000000000..5e11a788e --- /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 000000000..6ccd9b3ed --- /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 000000000..235edceb1 --- /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/tightenco/collect/src/Collect/Conditionable/HigherOrderWhenProxy.php b/data/web/inc/lib/vendor/illuminate/conditionable/HigherOrderWhenProxy.php similarity index 98% rename from data/web/inc/lib/vendor/tightenco/collect/src/Collect/Conditionable/HigherOrderWhenProxy.php rename to data/web/inc/lib/vendor/illuminate/conditionable/HigherOrderWhenProxy.php index eaf24812b..579114cf1 100644 --- a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Conditionable/HigherOrderWhenProxy.php +++ b/data/web/inc/lib/vendor/illuminate/conditionable/HigherOrderWhenProxy.php @@ -1,6 +1,6 @@ name)) { - $method->setAccessible(true); static::macro($method->name, $method->invoke($mixin)); } } 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 000000000..231d2333a --- /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/symfony/var-dumper/CHANGELOG.md b/data/web/inc/lib/vendor/symfony/var-dumper/CHANGELOG.md deleted file mode 100644 index 7481cff1a..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/CHANGELOG.md +++ /dev/null @@ -1,91 +0,0 @@ -CHANGELOG -========= - -6.4 ---- - - * Dump uninitialized properties - -6.3 ---- - - * Add caster for `WeakMap` - * Add support of named arguments to `dd()` and `dump()` to display the argument name - * Add support for `Relay\Relay` - * Add display of invisible characters - -6.2 ---- - - * Add support for `FFI\CData` and `FFI\CType` - * Deprecate calling `VarDumper::setHandler()` without arguments - -5.4 ---- - - * Add ability to style integer and double values independently - * Add casters for Symfony's UUIDs and ULIDs - * Add support for `Fiber` - -5.2.0 ------ - - * added support for PHPUnit `--colors` option - * added `VAR_DUMPER_FORMAT=server` env var value support - * prevent replacing the handler when the `VAR_DUMPER_FORMAT` env var is set - -5.1.0 ------ - - * added `RdKafka` support - -4.4.0 ------ - - * added `VarDumperTestTrait::setUpVarDumper()` and `VarDumperTestTrait::tearDownVarDumper()` - to configure casters & flags to use in tests - * added `ImagineCaster` and infrastructure to dump images - * added the stamps of a message after it is dispatched in `TraceableMessageBus` and `MessengerDataCollector` collected data - * added `UuidCaster` - * made all casters final - * added support for the `NO_COLOR` env var (https://no-color.org/) - -4.3.0 ------ - - * added `DsCaster` to support dumping the contents of data structures from the Ds extension - -4.2.0 ------ - - * support selecting the format to use by setting the environment variable `VAR_DUMPER_FORMAT` to `html` or `cli` - -4.1.0 ------ - - * added a `ServerDumper` to send serialized Data clones to a server - * added a `ServerDumpCommand` and `DumpServer` to run a server collecting - and displaying dumps on a single place with multiple formats support - * added `CliDescriptor` and `HtmlDescriptor` descriptors for `server:dump` CLI and HTML formats support - -4.0.0 ------ - - * support for passing `\ReflectionClass` instances to the `Caster::castObject()` - method has been dropped, pass class names as strings instead - * the `Data::getRawData()` method has been removed - * the `VarDumperTestTrait::assertDumpEquals()` method expects a 3rd `$filter = 0` - argument and moves `$message = ''` argument at 4th position. - * the `VarDumperTestTrait::assertDumpMatchesFormat()` method expects a 3rd `$filter = 0` - argument and moves `$message = ''` argument at 4th position. - -3.4.0 ------ - - * added `AbstractCloner::setMinDepth()` function to ensure minimum tree depth - * deprecated `MongoCaster` - -2.7.0 ------ - - * deprecated `Cloner\Data::getLimitedClone()`. Use `withMaxDepth`, `withMaxItemsPerDepth` or `withRefHandles` instead. diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/AmqpCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/AmqpCaster.php deleted file mode 100644 index 22026f46a..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/AmqpCaster.php +++ /dev/null @@ -1,227 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts Amqp related classes to array representation. - * - * @author Grégoire Pineau - * - * @final - */ -class AmqpCaster -{ - private const FLAGS = [ - \AMQP_DURABLE => 'AMQP_DURABLE', - \AMQP_PASSIVE => 'AMQP_PASSIVE', - \AMQP_EXCLUSIVE => 'AMQP_EXCLUSIVE', - \AMQP_AUTODELETE => 'AMQP_AUTODELETE', - \AMQP_INTERNAL => 'AMQP_INTERNAL', - \AMQP_NOLOCAL => 'AMQP_NOLOCAL', - \AMQP_AUTOACK => 'AMQP_AUTOACK', - \AMQP_IFEMPTY => 'AMQP_IFEMPTY', - \AMQP_IFUNUSED => 'AMQP_IFUNUSED', - \AMQP_MANDATORY => 'AMQP_MANDATORY', - \AMQP_IMMEDIATE => 'AMQP_IMMEDIATE', - \AMQP_MULTIPLE => 'AMQP_MULTIPLE', - \AMQP_NOWAIT => 'AMQP_NOWAIT', - \AMQP_REQUEUE => 'AMQP_REQUEUE', - ]; - - private const EXCHANGE_TYPES = [ - \AMQP_EX_TYPE_DIRECT => 'AMQP_EX_TYPE_DIRECT', - \AMQP_EX_TYPE_FANOUT => 'AMQP_EX_TYPE_FANOUT', - \AMQP_EX_TYPE_TOPIC => 'AMQP_EX_TYPE_TOPIC', - \AMQP_EX_TYPE_HEADERS => 'AMQP_EX_TYPE_HEADERS', - ]; - - /** - * @return array - */ - public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - $a += [ - $prefix.'is_connected' => $c->isConnected(), - ]; - - // Recent version of the extension already expose private properties - if (isset($a["\x00AMQPConnection\x00login"])) { - return $a; - } - - // BC layer in the amqp lib - if (method_exists($c, 'getReadTimeout')) { - $timeout = $c->getReadTimeout(); - } else { - $timeout = $c->getTimeout(); - } - - $a += [ - $prefix.'is_connected' => $c->isConnected(), - $prefix.'login' => $c->getLogin(), - $prefix.'password' => $c->getPassword(), - $prefix.'host' => $c->getHost(), - $prefix.'vhost' => $c->getVhost(), - $prefix.'port' => $c->getPort(), - $prefix.'read_timeout' => $timeout, - ]; - - return $a; - } - - /** - * @return array - */ - public static function castChannel(\AMQPChannel $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - $a += [ - $prefix.'is_connected' => $c->isConnected(), - $prefix.'channel_id' => $c->getChannelId(), - ]; - - // Recent version of the extension already expose private properties - if (isset($a["\x00AMQPChannel\x00connection"])) { - return $a; - } - - $a += [ - $prefix.'connection' => $c->getConnection(), - $prefix.'prefetch_size' => $c->getPrefetchSize(), - $prefix.'prefetch_count' => $c->getPrefetchCount(), - ]; - - return $a; - } - - /** - * @return array - */ - public static function castQueue(\AMQPQueue $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - $a += [ - $prefix.'flags' => self::extractFlags($c->getFlags()), - ]; - - // Recent version of the extension already expose private properties - if (isset($a["\x00AMQPQueue\x00name"])) { - return $a; - } - - $a += [ - $prefix.'connection' => $c->getConnection(), - $prefix.'channel' => $c->getChannel(), - $prefix.'name' => $c->getName(), - $prefix.'arguments' => $c->getArguments(), - ]; - - return $a; - } - - /** - * @return array - */ - public static function castExchange(\AMQPExchange $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - $a += [ - $prefix.'flags' => self::extractFlags($c->getFlags()), - ]; - - $type = isset(self::EXCHANGE_TYPES[$c->getType()]) ? new ConstStub(self::EXCHANGE_TYPES[$c->getType()], $c->getType()) : $c->getType(); - - // Recent version of the extension already expose private properties - if (isset($a["\x00AMQPExchange\x00name"])) { - $a["\x00AMQPExchange\x00type"] = $type; - - return $a; - } - - $a += [ - $prefix.'connection' => $c->getConnection(), - $prefix.'channel' => $c->getChannel(), - $prefix.'name' => $c->getName(), - $prefix.'type' => $type, - $prefix.'arguments' => $c->getArguments(), - ]; - - return $a; - } - - /** - * @return array - */ - public static function castEnvelope(\AMQPEnvelope $c, array $a, Stub $stub, bool $isNested, int $filter = 0) - { - $prefix = Caster::PREFIX_VIRTUAL; - - $deliveryMode = new ConstStub($c->getDeliveryMode().(2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode()); - - // Recent version of the extension already expose private properties - if (isset($a["\x00AMQPEnvelope\x00body"])) { - $a["\0AMQPEnvelope\0delivery_mode"] = $deliveryMode; - - return $a; - } - - if (!($filter & Caster::EXCLUDE_VERBOSE)) { - $a += [$prefix.'body' => $c->getBody()]; - } - - $a += [ - $prefix.'delivery_tag' => $c->getDeliveryTag(), - $prefix.'is_redelivery' => $c->isRedelivery(), - $prefix.'exchange_name' => $c->getExchangeName(), - $prefix.'routing_key' => $c->getRoutingKey(), - $prefix.'content_type' => $c->getContentType(), - $prefix.'content_encoding' => $c->getContentEncoding(), - $prefix.'headers' => $c->getHeaders(), - $prefix.'delivery_mode' => $deliveryMode, - $prefix.'priority' => $c->getPriority(), - $prefix.'correlation_id' => $c->getCorrelationId(), - $prefix.'reply_to' => $c->getReplyTo(), - $prefix.'expiration' => $c->getExpiration(), - $prefix.'message_id' => $c->getMessageId(), - $prefix.'timestamp' => $c->getTimeStamp(), - $prefix.'type' => $c->getType(), - $prefix.'user_id' => $c->getUserId(), - $prefix.'app_id' => $c->getAppId(), - ]; - - return $a; - } - - private static function extractFlags(int $flags): ConstStub - { - $flagsArray = []; - - foreach (self::FLAGS as $value => $name) { - if ($flags & $value) { - $flagsArray[] = $name; - } - } - - if (!$flagsArray) { - $flagsArray = ['AMQP_NOPARAM']; - } - - return new ConstStub(implode('|', $flagsArray), $flags); - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ArgsStub.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ArgsStub.php deleted file mode 100644 index 9dc24c1b1..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ArgsStub.php +++ /dev/null @@ -1,80 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Represents a list of function arguments. - * - * @author Nicolas Grekas - */ -class ArgsStub extends EnumStub -{ - private static array $parameters = []; - - public function __construct(array $args, string $function, ?string $class) - { - [$variadic, $params] = self::getParameters($function, $class); - - $values = []; - foreach ($args as $k => $v) { - $values[$k] = !\is_scalar($v) && !$v instanceof Stub ? new CutStub($v) : $v; - } - if (null === $params) { - parent::__construct($values, false); - - return; - } - if (\count($values) < \count($params)) { - $params = \array_slice($params, 0, \count($values)); - } elseif (\count($values) > \count($params)) { - $values[] = new EnumStub(array_splice($values, \count($params)), false); - $params[] = $variadic; - } - if (['...'] === $params) { - $this->dumpKeys = false; - $this->value = $values[0]->value; - } else { - $this->value = array_combine($params, $values); - } - } - - private static function getParameters(string $function, ?string $class): array - { - if (isset(self::$parameters[$k = $class.'::'.$function])) { - return self::$parameters[$k]; - } - - try { - $r = null !== $class ? new \ReflectionMethod($class, $function) : new \ReflectionFunction($function); - } catch (\ReflectionException) { - return [null, null]; - } - - $variadic = '...'; - $params = []; - foreach ($r->getParameters() as $v) { - $k = '$'.$v->name; - if ($v->isPassedByReference()) { - $k = '&'.$k; - } - if ($v->isVariadic()) { - $variadic .= $k; - } else { - $params[] = $k; - } - } - - return self::$parameters[$k] = [$variadic, $params]; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/Caster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/Caster.php deleted file mode 100644 index d9577e7ae..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/Caster.php +++ /dev/null @@ -1,198 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Helper for filtering out properties in casters. - * - * @author Nicolas Grekas - * - * @final - */ -class Caster -{ - public const EXCLUDE_VERBOSE = 1; - public const EXCLUDE_VIRTUAL = 2; - public const EXCLUDE_DYNAMIC = 4; - public const EXCLUDE_PUBLIC = 8; - public const EXCLUDE_PROTECTED = 16; - public const EXCLUDE_PRIVATE = 32; - public const EXCLUDE_NULL = 64; - public const EXCLUDE_EMPTY = 128; - public const EXCLUDE_NOT_IMPORTANT = 256; - public const EXCLUDE_STRICT = 512; - public const EXCLUDE_UNINITIALIZED = 1024; - - public const PREFIX_VIRTUAL = "\0~\0"; - public const PREFIX_DYNAMIC = "\0+\0"; - public const PREFIX_PROTECTED = "\0*\0"; - // usage: sprintf(Caster::PATTERN_PRIVATE, $class, $property) - public const PATTERN_PRIVATE = "\0%s\0%s"; - - private static array $classProperties = []; - - /** - * Casts objects to arrays and adds the dynamic property prefix. - * - * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not - */ - public static function castObject(object $obj, string $class, bool $hasDebugInfo = false, ?string $debugClass = null): array - { - if ($hasDebugInfo) { - try { - $debugInfo = $obj->__debugInfo(); - } catch (\Throwable) { - // ignore failing __debugInfo() - $hasDebugInfo = false; - } - } - - $a = $obj instanceof \Closure ? [] : (array) $obj; - - if ($obj instanceof \__PHP_Incomplete_Class) { - return $a; - } - - $classProperties = self::$classProperties[$class] ??= self::getClassProperties(new \ReflectionClass($class)); - $a = array_replace($classProperties, $a); - - if ($a) { - $debugClass ??= get_debug_type($obj); - - $i = 0; - $prefixedKeys = []; - foreach ($a as $k => $v) { - if ("\0" !== ($k[0] ?? '')) { - if (!isset($classProperties[$k])) { - $prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k; - } - } elseif ($debugClass !== $class && 1 === strpos($k, $class)) { - $prefixedKeys[$i] = "\0".$debugClass.strrchr($k, "\0"); - } - ++$i; - } - if ($prefixedKeys) { - $keys = array_keys($a); - foreach ($prefixedKeys as $i => $k) { - $keys[$i] = $k; - } - $a = array_combine($keys, $a); - } - } - - if ($hasDebugInfo && \is_array($debugInfo)) { - foreach ($debugInfo as $k => $v) { - if (!isset($k[0]) || "\0" !== $k[0]) { - if (\array_key_exists(self::PREFIX_DYNAMIC.$k, $a)) { - continue; - } - $k = self::PREFIX_VIRTUAL.$k; - } - - unset($a[$k]); - $a[$k] = $v; - } - } - - return $a; - } - - /** - * Filters out the specified properties. - * - * By default, a single match in the $filter bit field filters properties out, following an "or" logic. - * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed. - * - * @param array $a The array containing the properties to filter - * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out - * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set - * @param int|null &$count Set to the number of removed properties - */ - public static function filter(array $a, int $filter, array $listedProperties = [], ?int &$count = 0): array - { - $count = 0; - - foreach ($a as $k => $v) { - $type = self::EXCLUDE_STRICT & $filter; - - if (null === $v) { - $type |= self::EXCLUDE_NULL & $filter; - $type |= self::EXCLUDE_EMPTY & $filter; - } elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || [] === $v) { - $type |= self::EXCLUDE_EMPTY & $filter; - } elseif ($v instanceof UninitializedStub) { - $type |= self::EXCLUDE_UNINITIALIZED & $filter; - } - if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !\in_array($k, $listedProperties, true)) { - $type |= self::EXCLUDE_NOT_IMPORTANT; - } - if ((self::EXCLUDE_VERBOSE & $filter) && \in_array($k, $listedProperties, true)) { - $type |= self::EXCLUDE_VERBOSE; - } - - if (!isset($k[1]) || "\0" !== $k[0]) { - $type |= self::EXCLUDE_PUBLIC & $filter; - } elseif ('~' === $k[1]) { - $type |= self::EXCLUDE_VIRTUAL & $filter; - } elseif ('+' === $k[1]) { - $type |= self::EXCLUDE_DYNAMIC & $filter; - } elseif ('*' === $k[1]) { - $type |= self::EXCLUDE_PROTECTED & $filter; - } else { - $type |= self::EXCLUDE_PRIVATE & $filter; - } - - if ((self::EXCLUDE_STRICT & $filter) ? $type === $filter : $type) { - unset($a[$k]); - ++$count; - } - } - - return $a; - } - - public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, bool $isNested): array - { - if (isset($a['__PHP_Incomplete_Class_Name'])) { - $stub->class .= '('.$a['__PHP_Incomplete_Class_Name'].')'; - unset($a['__PHP_Incomplete_Class_Name']); - } - - return $a; - } - - private static function getClassProperties(\ReflectionClass $class): array - { - $classProperties = []; - $className = $class->name; - - if ($parent = $class->getParentClass()) { - $classProperties += self::$classProperties[$parent->name] ??= self::getClassProperties($parent); - } - - foreach ($class->getProperties() as $p) { - if ($p->isStatic()) { - continue; - } - - $classProperties[match (true) { - $p->isPublic() => $p->name, - $p->isProtected() => self::PREFIX_PROTECTED.$p->name, - default => "\0".$className."\0".$p->name, - }] = new UninitializedStub($p); - } - - return $classProperties; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ClassStub.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ClassStub.php deleted file mode 100644 index 914728663..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ClassStub.php +++ /dev/null @@ -1,107 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Represents a PHP class identifier. - * - * @author Nicolas Grekas - */ -class ClassStub extends ConstStub -{ - /** - * @param string $identifier A PHP identifier, e.g. a class, method, interface, etc. name - * @param callable $callable The callable targeted by the identifier when it is ambiguous or not a real PHP identifier - */ - public function __construct(string $identifier, callable|array|string|null $callable = null) - { - $this->value = $identifier; - - try { - if (null !== $callable) { - if ($callable instanceof \Closure) { - $r = new \ReflectionFunction($callable); - } elseif (\is_object($callable)) { - $r = [$callable, '__invoke']; - } elseif (\is_array($callable)) { - $r = $callable; - } elseif (false !== $i = strpos($callable, '::')) { - $r = [substr($callable, 0, $i), substr($callable, 2 + $i)]; - } else { - $r = new \ReflectionFunction($callable); - } - } elseif (0 < $i = strpos($identifier, '::') ?: strpos($identifier, '->')) { - $r = [substr($identifier, 0, $i), substr($identifier, 2 + $i)]; - } else { - $r = new \ReflectionClass($identifier); - } - - if (\is_array($r)) { - try { - $r = new \ReflectionMethod($r[0], $r[1]); - } catch (\ReflectionException) { - $r = new \ReflectionClass($r[0]); - } - } - - if (str_contains($identifier, "@anonymous\0")) { - $this->value = $identifier = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $identifier); - } - - if (null !== $callable && $r instanceof \ReflectionFunctionAbstract) { - $s = ReflectionCaster::castFunctionAbstract($r, [], new Stub(), true, Caster::EXCLUDE_VERBOSE); - $s = ReflectionCaster::getSignature($s); - - if (str_ends_with($identifier, '()')) { - $this->value = substr_replace($identifier, $s, -2); - } else { - $this->value .= $s; - } - } - } catch (\ReflectionException) { - return; - } finally { - if (0 < $i = strrpos($this->value, '\\')) { - $this->attr['ellipsis'] = \strlen($this->value) - $i; - $this->attr['ellipsis-type'] = 'class'; - $this->attr['ellipsis-tail'] = 1; - } - } - - if ($f = $r->getFileName()) { - $this->attr['file'] = $f; - $this->attr['line'] = $r->getStartLine(); - } - } - - /** - * @return mixed - */ - public static function wrapCallable(mixed $callable) - { - if (\is_object($callable) || !\is_callable($callable)) { - return $callable; - } - - if (!\is_array($callable)) { - $callable = new static($callable, $callable); - } elseif (\is_string($callable[0])) { - $callable[0] = new static($callable[0], $callable); - } else { - $callable[1] = new static($callable[1], $callable); - } - - return $callable; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ConstStub.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ConstStub.php deleted file mode 100644 index 587c6c398..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ConstStub.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Represents a PHP constant and its value. - * - * @author Nicolas Grekas - */ -class ConstStub extends Stub -{ - public function __construct(string $name, string|int|float|null $value = null) - { - $this->class = $name; - $this->value = 1 < \func_num_args() ? $value : $name; - } - - public function __toString(): string - { - return (string) $this->value; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/CutArrayStub.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/CutArrayStub.php deleted file mode 100644 index 0e4fb363d..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/CutArrayStub.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -/** - * Represents a cut array. - * - * @author Nicolas Grekas - */ -class CutArrayStub extends CutStub -{ - public $preservedSubset; - - public function __construct(array $value, array $preservedKeys) - { - parent::__construct($value); - - $this->preservedSubset = array_intersect_key($value, array_flip($preservedKeys)); - $this->cut -= \count($this->preservedSubset); - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/CutStub.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/CutStub.php deleted file mode 100644 index 772399ef6..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/CutStub.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Represents the main properties of a PHP variable, pre-casted by a caster. - * - * @author Nicolas Grekas - */ -class CutStub extends Stub -{ - public function __construct(mixed $value) - { - $this->value = $value; - - switch (\gettype($value)) { - case 'object': - $this->type = self::TYPE_OBJECT; - $this->class = $value::class; - - if ($value instanceof \Closure) { - ReflectionCaster::castClosure($value, [], $this, true, Caster::EXCLUDE_VERBOSE); - } - - $this->cut = -1; - break; - - case 'array': - $this->type = self::TYPE_ARRAY; - $this->class = self::ARRAY_ASSOC; - $this->cut = $this->value = \count($value); - break; - - case 'resource': - case 'unknown type': - case 'resource (closed)': - $this->type = self::TYPE_RESOURCE; - $this->handle = (int) $value; - if ('Unknown' === $this->class = @get_resource_type($value)) { - $this->class = 'Closed'; - } - $this->cut = -1; - break; - - case 'string': - $this->type = self::TYPE_STRING; - $this->class = preg_match('//u', $value) ? self::STRING_UTF8 : self::STRING_BINARY; - $this->cut = self::STRING_BINARY === $this->class ? \strlen($value) : mb_strlen($value, 'UTF-8'); - $this->value = ''; - break; - } - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DOMCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DOMCaster.php deleted file mode 100644 index d2d3fc129..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DOMCaster.php +++ /dev/null @@ -1,312 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts DOM related classes to array representation. - * - * @author Nicolas Grekas - * - * @final - */ -class DOMCaster -{ - private const ERROR_CODES = [ - \DOM_PHP_ERR => 'DOM_PHP_ERR', - \DOM_INDEX_SIZE_ERR => 'DOM_INDEX_SIZE_ERR', - \DOMSTRING_SIZE_ERR => 'DOMSTRING_SIZE_ERR', - \DOM_HIERARCHY_REQUEST_ERR => 'DOM_HIERARCHY_REQUEST_ERR', - \DOM_WRONG_DOCUMENT_ERR => 'DOM_WRONG_DOCUMENT_ERR', - \DOM_INVALID_CHARACTER_ERR => 'DOM_INVALID_CHARACTER_ERR', - \DOM_NO_DATA_ALLOWED_ERR => 'DOM_NO_DATA_ALLOWED_ERR', - \DOM_NO_MODIFICATION_ALLOWED_ERR => 'DOM_NO_MODIFICATION_ALLOWED_ERR', - \DOM_NOT_FOUND_ERR => 'DOM_NOT_FOUND_ERR', - \DOM_NOT_SUPPORTED_ERR => 'DOM_NOT_SUPPORTED_ERR', - \DOM_INUSE_ATTRIBUTE_ERR => 'DOM_INUSE_ATTRIBUTE_ERR', - \DOM_INVALID_STATE_ERR => 'DOM_INVALID_STATE_ERR', - \DOM_SYNTAX_ERR => 'DOM_SYNTAX_ERR', - \DOM_INVALID_MODIFICATION_ERR => 'DOM_INVALID_MODIFICATION_ERR', - \DOM_NAMESPACE_ERR => 'DOM_NAMESPACE_ERR', - \DOM_INVALID_ACCESS_ERR => 'DOM_INVALID_ACCESS_ERR', - \DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR', - ]; - - private const NODE_TYPES = [ - \XML_ELEMENT_NODE => 'XML_ELEMENT_NODE', - \XML_ATTRIBUTE_NODE => 'XML_ATTRIBUTE_NODE', - \XML_TEXT_NODE => 'XML_TEXT_NODE', - \XML_CDATA_SECTION_NODE => 'XML_CDATA_SECTION_NODE', - \XML_ENTITY_REF_NODE => 'XML_ENTITY_REF_NODE', - \XML_ENTITY_NODE => 'XML_ENTITY_NODE', - \XML_PI_NODE => 'XML_PI_NODE', - \XML_COMMENT_NODE => 'XML_COMMENT_NODE', - \XML_DOCUMENT_NODE => 'XML_DOCUMENT_NODE', - \XML_DOCUMENT_TYPE_NODE => 'XML_DOCUMENT_TYPE_NODE', - \XML_DOCUMENT_FRAG_NODE => 'XML_DOCUMENT_FRAG_NODE', - \XML_NOTATION_NODE => 'XML_NOTATION_NODE', - \XML_HTML_DOCUMENT_NODE => 'XML_HTML_DOCUMENT_NODE', - \XML_DTD_NODE => 'XML_DTD_NODE', - \XML_ELEMENT_DECL_NODE => 'XML_ELEMENT_DECL_NODE', - \XML_ATTRIBUTE_DECL_NODE => 'XML_ATTRIBUTE_DECL_NODE', - \XML_ENTITY_DECL_NODE => 'XML_ENTITY_DECL_NODE', - \XML_NAMESPACE_DECL_NODE => 'XML_NAMESPACE_DECL_NODE', - ]; - - /** - * @return array - */ - public static function castException(\DOMException $e, array $a, Stub $stub, bool $isNested) - { - $k = Caster::PREFIX_PROTECTED.'code'; - if (isset($a[$k], self::ERROR_CODES[$a[$k]])) { - $a[$k] = new ConstStub(self::ERROR_CODES[$a[$k]], $a[$k]); - } - - return $a; - } - - /** - * @return array - */ - public static function castLength($dom, array $a, Stub $stub, bool $isNested) - { - $a += [ - 'length' => $dom->length, - ]; - - return $a; - } - - /** - * @return array - */ - public static function castImplementation(\DOMImplementation $dom, array $a, Stub $stub, bool $isNested) - { - $a += [ - Caster::PREFIX_VIRTUAL.'Core' => '1.0', - Caster::PREFIX_VIRTUAL.'XML' => '2.0', - ]; - - return $a; - } - - /** - * @return array - */ - public static function castNode(\DOMNode $dom, array $a, Stub $stub, bool $isNested) - { - $a += [ - 'nodeName' => $dom->nodeName, - 'nodeValue' => new CutStub($dom->nodeValue), - 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), - 'parentNode' => new CutStub($dom->parentNode), - 'childNodes' => $dom->childNodes, - 'firstChild' => new CutStub($dom->firstChild), - 'lastChild' => new CutStub($dom->lastChild), - 'previousSibling' => new CutStub($dom->previousSibling), - 'nextSibling' => new CutStub($dom->nextSibling), - 'attributes' => $dom->attributes, - 'ownerDocument' => new CutStub($dom->ownerDocument), - 'namespaceURI' => $dom->namespaceURI, - 'prefix' => $dom->prefix, - 'localName' => $dom->localName, - 'baseURI' => $dom->baseURI ? new LinkStub($dom->baseURI) : $dom->baseURI, - 'textContent' => new CutStub($dom->textContent), - ]; - - return $a; - } - - /** - * @return array - */ - public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, bool $isNested) - { - $a += [ - 'nodeName' => $dom->nodeName, - 'nodeValue' => new CutStub($dom->nodeValue), - 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), - 'prefix' => $dom->prefix, - 'localName' => $dom->localName, - 'namespaceURI' => $dom->namespaceURI, - 'ownerDocument' => new CutStub($dom->ownerDocument), - 'parentNode' => new CutStub($dom->parentNode), - ]; - - return $a; - } - - /** - * @return array - */ - public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, bool $isNested, int $filter = 0) - { - $a += [ - 'doctype' => $dom->doctype, - 'implementation' => $dom->implementation, - 'documentElement' => new CutStub($dom->documentElement), - 'actualEncoding' => $dom->actualEncoding, - 'encoding' => $dom->encoding, - 'xmlEncoding' => $dom->xmlEncoding, - 'standalone' => $dom->standalone, - 'xmlStandalone' => $dom->xmlStandalone, - 'version' => $dom->version, - 'xmlVersion' => $dom->xmlVersion, - 'strictErrorChecking' => $dom->strictErrorChecking, - 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI, - 'config' => $dom->config, - 'formatOutput' => $dom->formatOutput, - 'validateOnParse' => $dom->validateOnParse, - 'resolveExternals' => $dom->resolveExternals, - 'preserveWhiteSpace' => $dom->preserveWhiteSpace, - 'recover' => $dom->recover, - 'substituteEntities' => $dom->substituteEntities, - ]; - - if (!($filter & Caster::EXCLUDE_VERBOSE)) { - $formatOutput = $dom->formatOutput; - $dom->formatOutput = true; - $a += [Caster::PREFIX_VIRTUAL.'xml' => $dom->saveXML()]; - $dom->formatOutput = $formatOutput; - } - - return $a; - } - - /** - * @return array - */ - public static function castCharacterData(\DOMCharacterData $dom, array $a, Stub $stub, bool $isNested) - { - $a += [ - 'data' => $dom->data, - 'length' => $dom->length, - ]; - - return $a; - } - - /** - * @return array - */ - public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, bool $isNested) - { - $a += [ - 'name' => $dom->name, - 'specified' => $dom->specified, - 'value' => $dom->value, - 'ownerElement' => $dom->ownerElement, - 'schemaTypeInfo' => $dom->schemaTypeInfo, - ]; - - return $a; - } - - /** - * @return array - */ - public static function castElement(\DOMElement $dom, array $a, Stub $stub, bool $isNested) - { - $a += [ - 'tagName' => $dom->tagName, - 'schemaTypeInfo' => $dom->schemaTypeInfo, - ]; - - return $a; - } - - /** - * @return array - */ - public static function castText(\DOMText $dom, array $a, Stub $stub, bool $isNested) - { - $a += [ - 'wholeText' => $dom->wholeText, - ]; - - return $a; - } - - /** - * @return array - */ - public static function castDocumentType(\DOMDocumentType $dom, array $a, Stub $stub, bool $isNested) - { - $a += [ - 'name' => $dom->name, - 'entities' => $dom->entities, - 'notations' => $dom->notations, - 'publicId' => $dom->publicId, - 'systemId' => $dom->systemId, - 'internalSubset' => $dom->internalSubset, - ]; - - return $a; - } - - /** - * @return array - */ - public static function castNotation(\DOMNotation $dom, array $a, Stub $stub, bool $isNested) - { - $a += [ - 'publicId' => $dom->publicId, - 'systemId' => $dom->systemId, - ]; - - return $a; - } - - /** - * @return array - */ - public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, bool $isNested) - { - $a += [ - 'publicId' => $dom->publicId, - 'systemId' => $dom->systemId, - 'notationName' => $dom->notationName, - 'actualEncoding' => $dom->actualEncoding, - 'encoding' => $dom->encoding, - 'version' => $dom->version, - ]; - - return $a; - } - - /** - * @return array - */ - public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, Stub $stub, bool $isNested) - { - $a += [ - 'target' => $dom->target, - 'data' => $dom->data, - ]; - - return $a; - } - - /** - * @return array - */ - public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, bool $isNested) - { - $a += [ - 'document' => $dom->document, - ]; - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DateCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DateCaster.php deleted file mode 100644 index a0cbddb76..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DateCaster.php +++ /dev/null @@ -1,139 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts DateTimeInterface related classes to array representation. - * - * @author Dany Maillard - * - * @final - */ -class DateCaster -{ - private const PERIOD_LIMIT = 3; - - /** - * @return array - */ - public static function castDateTime(\DateTimeInterface $d, array $a, Stub $stub, bool $isNested, int $filter) - { - $prefix = Caster::PREFIX_VIRTUAL; - $location = $d->getTimezone() ? $d->getTimezone()->getLocation() : null; - $fromNow = (new \DateTimeImmutable())->diff($d); - - $title = $d->format('l, F j, Y') - ."\n".self::formatInterval($fromNow).' from now' - .($location ? ($d->format('I') ? "\nDST On" : "\nDST Off") : '') - ; - - unset( - $a[Caster::PREFIX_DYNAMIC.'date'], - $a[Caster::PREFIX_DYNAMIC.'timezone'], - $a[Caster::PREFIX_DYNAMIC.'timezone_type'] - ); - $a[$prefix.'date'] = new ConstStub(self::formatDateTime($d, $location ? ' e (P)' : ' P'), $title); - - $stub->class .= $d->format(' @U'); - - return $a; - } - - /** - * @return array - */ - public static function castInterval(\DateInterval $interval, array $a, Stub $stub, bool $isNested, int $filter) - { - $now = new \DateTimeImmutable('@0', new \DateTimeZone('UTC')); - $numberOfSeconds = $now->add($interval)->getTimestamp() - $now->getTimestamp(); - $title = number_format($numberOfSeconds, 0, '.', ' ').'s'; - - $i = [Caster::PREFIX_VIRTUAL.'interval' => new ConstStub(self::formatInterval($interval), $title)]; - - return $filter & Caster::EXCLUDE_VERBOSE ? $i : $i + $a; - } - - private static function formatInterval(\DateInterval $i): string - { - $format = '%R '; - - if (0 === $i->y && 0 === $i->m && ($i->h >= 24 || $i->i >= 60 || $i->s >= 60)) { - $d = new \DateTimeImmutable('@0', new \DateTimeZone('UTC')); - $i = $d->diff($d->add($i)); // recalculate carry over points - $format .= 0 < $i->days ? '%ad ' : ''; - } else { - $format .= ($i->y ? '%yy ' : '').($i->m ? '%mm ' : '').($i->d ? '%dd ' : ''); - } - - $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, substr($i->f, 2)) : ''; - $format = '%R ' === $format ? '0s' : $format; - - return $i->format(rtrim($format)); - } - - /** - * @return array - */ - public static function castTimeZone(\DateTimeZone $timeZone, array $a, Stub $stub, bool $isNested, int $filter) - { - $location = $timeZone->getLocation(); - $formatted = (new \DateTimeImmutable('now', $timeZone))->format($location ? 'e (P)' : 'P'); - $title = $location && \extension_loaded('intl') ? \Locale::getDisplayRegion('-'.$location['country_code']) : ''; - - $z = [Caster::PREFIX_VIRTUAL.'timezone' => new ConstStub($formatted, $title)]; - - return $filter & Caster::EXCLUDE_VERBOSE ? $z : $z + $a; - } - - /** - * @return array - */ - public static function castPeriod(\DatePeriod $p, array $a, Stub $stub, bool $isNested, int $filter) - { - $dates = []; - foreach (clone $p as $i => $d) { - if (self::PERIOD_LIMIT === $i) { - $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); - $dates[] = sprintf('%s more', ($end = $p->getEndDate()) - ? ceil(($end->format('U.u') - $d->format('U.u')) / ((int) $now->add($p->getDateInterval())->format('U.u') - (int) $now->format('U.u'))) - : $p->recurrences - $i - ); - break; - } - $dates[] = sprintf('%s) %s', $i + 1, self::formatDateTime($d)); - } - - $period = sprintf( - 'every %s, from %s%s %s', - self::formatInterval($p->getDateInterval()), - $p->include_start_date ? '[' : ']', - self::formatDateTime($p->getStartDate()), - ($end = $p->getEndDate()) ? 'to '.self::formatDateTime($end).(\PHP_VERSION_ID >= 80200 && $p->include_end_date ? ']' : '[') : 'recurring '.$p->recurrences.' time/s' - ); - - $p = [Caster::PREFIX_VIRTUAL.'period' => new ConstStub($period, implode("\n", $dates))]; - - return $filter & Caster::EXCLUDE_VERBOSE ? $p : $p + $a; - } - - private static function formatDateTime(\DateTimeInterface $d, string $extra = ''): string - { - return $d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).$extra); - } - - private static function formatSeconds(string $s, string $us): string - { - return sprintf('%02d.%s', $s, 0 === ($len = \strlen($t = rtrim($us, '0'))) ? '0' : ($len <= 3 ? str_pad($t, 3, '0') : $us)); - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DoctrineCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DoctrineCaster.php deleted file mode 100644 index 3120c3d91..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DoctrineCaster.php +++ /dev/null @@ -1,71 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Doctrine\Common\Proxy\Proxy as CommonProxy; -use Doctrine\ORM\PersistentCollection; -use Doctrine\ORM\Proxy\Proxy as OrmProxy; -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts Doctrine related classes to array representation. - * - * @author Nicolas Grekas - * - * @final - */ -class DoctrineCaster -{ - /** - * @return array - */ - public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, bool $isNested) - { - foreach (['__cloner__', '__initializer__'] as $k) { - if (\array_key_exists($k, $a)) { - unset($a[$k]); - ++$stub->cut; - } - } - - return $a; - } - - /** - * @return array - */ - public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, bool $isNested) - { - foreach (['_entityPersister', '_identifier'] as $k) { - if (\array_key_exists($k = "\0Doctrine\\ORM\\Proxy\\Proxy\0".$k, $a)) { - unset($a[$k]); - ++$stub->cut; - } - } - - return $a; - } - - /** - * @return array - */ - public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, bool $isNested) - { - foreach (['snapshot', 'association', 'typeClass'] as $k) { - if (\array_key_exists($k = "\0Doctrine\\ORM\\PersistentCollection\0".$k, $a)) { - $a[$k] = new CutStub($a[$k]); - } - } - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DsCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DsCaster.php deleted file mode 100644 index b34b67004..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DsCaster.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Ds\Collection; -use Ds\Map; -use Ds\Pair; -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts Ds extension classes to array representation. - * - * @author Jáchym Toušek - * - * @final - */ -class DsCaster -{ - public static function castCollection(Collection $c, array $a, Stub $stub, bool $isNested): array - { - $a[Caster::PREFIX_VIRTUAL.'count'] = $c->count(); - $a[Caster::PREFIX_VIRTUAL.'capacity'] = $c->capacity(); - - if (!$c instanceof Map) { - $a += $c->toArray(); - } - - return $a; - } - - public static function castMap(Map $c, array $a, Stub $stub, bool $isNested): array - { - foreach ($c as $k => $v) { - $a[] = new DsPairStub($k, $v); - } - - return $a; - } - - public static function castPair(Pair $c, array $a, Stub $stub, bool $isNested): array - { - foreach ($c->toArray() as $k => $v) { - $a[Caster::PREFIX_VIRTUAL.$k] = $v; - } - - return $a; - } - - public static function castPairStub(DsPairStub $c, array $a, Stub $stub, bool $isNested): array - { - if ($isNested) { - $stub->class = Pair::class; - $stub->value = null; - $stub->handle = 0; - - $a = $c->value; - } - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DsPairStub.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DsPairStub.php deleted file mode 100644 index afa2727b1..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/DsPairStub.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * @author Nicolas Grekas - */ -class DsPairStub extends Stub -{ - public function __construct(mixed $key, mixed $value) - { - $this->value = [ - Caster::PREFIX_VIRTUAL.'key' => $key, - Caster::PREFIX_VIRTUAL.'value' => $value, - ]; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/EnumStub.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/EnumStub.php deleted file mode 100644 index 7a4e98a21..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/EnumStub.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Represents an enumeration of values. - * - * @author Nicolas Grekas - */ -class EnumStub extends Stub -{ - public $dumpKeys = true; - - public function __construct(array $values, bool $dumpKeys = true) - { - $this->value = $values; - $this->dumpKeys = $dumpKeys; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ExceptionCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ExceptionCaster.php deleted file mode 100644 index 02efb1b02..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ExceptionCaster.php +++ /dev/null @@ -1,419 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\ErrorHandler\Exception\FlattenException; -use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; -use Symfony\Component\VarDumper\Cloner\Stub; -use Symfony\Component\VarDumper\Exception\ThrowingCasterException; - -/** - * Casts common Exception classes to array representation. - * - * @author Nicolas Grekas - * - * @final - */ -class ExceptionCaster -{ - public static int $srcContext = 1; - public static bool $traceArgs = true; - public static array $errorTypes = [ - \E_DEPRECATED => 'E_DEPRECATED', - \E_USER_DEPRECATED => 'E_USER_DEPRECATED', - \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', - \E_ERROR => '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', - ]; - - private static array $framesCache = []; - - /** - * @return array - */ - public static function castError(\Error $e, array $a, Stub $stub, bool $isNested, int $filter = 0) - { - return self::filterExceptionArray($stub->class, $a, "\0Error\0", $filter); - } - - /** - * @return array - */ - public static function castException(\Exception $e, array $a, Stub $stub, bool $isNested, int $filter = 0) - { - return self::filterExceptionArray($stub->class, $a, "\0Exception\0", $filter); - } - - /** - * @return array - */ - public static function castErrorException(\ErrorException $e, array $a, Stub $stub, bool $isNested) - { - if (isset($a[$s = Caster::PREFIX_PROTECTED.'severity'], self::$errorTypes[$a[$s]])) { - $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); - } - - return $a; - } - - /** - * @return array - */ - public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, bool $isNested) - { - $trace = Caster::PREFIX_VIRTUAL.'trace'; - $prefix = Caster::PREFIX_PROTECTED; - $xPrefix = "\0Exception\0"; - - if (isset($a[$xPrefix.'previous'], $a[$trace]) && $a[$xPrefix.'previous'] instanceof \Exception) { - $b = (array) $a[$xPrefix.'previous']; - $class = get_debug_type($a[$xPrefix.'previous']); - self::traceUnshift($b[$xPrefix.'trace'], $class, $b[$prefix.'file'], $b[$prefix.'line']); - $a[$trace] = new TraceStub($b[$xPrefix.'trace'], false, 0, -\count($a[$trace]->value)); - } - - unset($a[$xPrefix.'previous'], $a[$prefix.'code'], $a[$prefix.'file'], $a[$prefix.'line']); - - return $a; - } - - /** - * @return array - */ - public static function castSilencedErrorContext(SilencedErrorContext $e, array $a, Stub $stub, bool $isNested) - { - $sPrefix = "\0".SilencedErrorContext::class."\0"; - - if (!isset($a[$s = $sPrefix.'severity'])) { - return $a; - } - - if (isset(self::$errorTypes[$a[$s]])) { - $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); - } - - $trace = [[ - 'file' => $a[$sPrefix.'file'], - 'line' => $a[$sPrefix.'line'], - ]]; - - if (isset($a[$sPrefix.'trace'])) { - $trace = array_merge($trace, $a[$sPrefix.'trace']); - } - - unset($a[$sPrefix.'file'], $a[$sPrefix.'line'], $a[$sPrefix.'trace']); - $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs); - - return $a; - } - - /** - * @return array - */ - public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, bool $isNested) - { - if (!$isNested) { - return $a; - } - $stub->class = ''; - $stub->handle = 0; - $frames = $trace->value; - $prefix = Caster::PREFIX_VIRTUAL; - - $a = []; - $j = \count($frames); - if (0 > $i = $trace->sliceOffset) { - $i = max(0, $j + $i); - } - if (!isset($trace->value[$i])) { - return []; - } - $lastCall = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[0]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : ''; - $frames[] = ['function' => '']; - $collapse = false; - - for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) { - $f = $frames[$i]; - $call = isset($f['function']) ? (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'] : '???'; - - $frame = new FrameStub( - [ - 'object' => $f['object'] ?? null, - 'class' => $f['class'] ?? null, - 'type' => $f['type'] ?? null, - 'function' => $f['function'] ?? null, - ] + $frames[$i - 1], - false, - true - ); - $f = self::castFrameStub($frame, [], $frame, true); - if (isset($f[$prefix.'src'])) { - foreach ($f[$prefix.'src']->value as $label => $frame) { - if (str_starts_with($label, "\0~collapse=0")) { - if ($collapse) { - $label = substr_replace($label, '1', 11, 1); - } else { - $collapse = true; - } - } - $label = substr_replace($label, "title=Stack level $j.&", 2, 0); - } - $f = $frames[$i - 1]; - if ($trace->keepArgs && !empty($f['args']) && $frame instanceof EnumStub) { - $frame->value['arguments'] = new ArgsStub($f['args'], $f['function'] ?? null, $f['class'] ?? null); - } - } elseif ('???' !== $lastCall) { - $label = new ClassStub($lastCall); - if (isset($label->attr['ellipsis'])) { - $label->attr['ellipsis'] += 2; - $label = substr_replace($prefix, "ellipsis-type=class&ellipsis={$label->attr['ellipsis']}&ellipsis-tail=1&title=Stack level $j.", 2, 0).$label->value.'()'; - } else { - $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$label->value.'()'; - } - } else { - $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$lastCall; - } - $a[substr_replace($label, sprintf('separator=%s&', $frame instanceof EnumStub ? ' ' : ':'), 2, 0)] = $frame; - - $lastCall = $call; - } - if (null !== $trace->sliceLength) { - $a = \array_slice($a, 0, $trace->sliceLength, true); - } - - return $a; - } - - /** - * @return array - */ - public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, bool $isNested) - { - if (!$isNested) { - return $a; - } - $f = $frame->value; - $prefix = Caster::PREFIX_VIRTUAL; - - if (isset($f['file'], $f['line'])) { - $cacheKey = $f; - unset($cacheKey['object'], $cacheKey['args']); - $cacheKey[] = self::$srcContext; - $cacheKey = implode('-', $cacheKey); - - if (isset(self::$framesCache[$cacheKey])) { - $a[$prefix.'src'] = self::$framesCache[$cacheKey]; - } else { - if (preg_match('/\((\d+)\)(?:\([\da-f]{32}\))? : (?:eval\(\)\'d code|runtime-created function)$/', $f['file'], $match)) { - $f['file'] = substr($f['file'], 0, -\strlen($match[0])); - $f['line'] = (int) $match[1]; - } - $src = $f['line']; - $srcKey = $f['file']; - $ellipsis = new LinkStub($srcKey, 0); - $srcAttr = 'collapse='.(int) $ellipsis->inVendor; - $ellipsisTail = $ellipsis->attr['ellipsis-tail'] ?? 0; - $ellipsis = $ellipsis->attr['ellipsis'] ?? 0; - - if (is_file($f['file']) && 0 <= self::$srcContext) { - if (!empty($f['class']) && (is_subclass_of($f['class'], 'Twig\Template') || is_subclass_of($f['class'], 'Twig_Template')) && method_exists($f['class'], 'getDebugInfo')) { - $template = null; - if (isset($f['object'])) { - $template = $f['object']; - } elseif ((new \ReflectionClass($f['class']))->isInstantiable()) { - $template = unserialize(sprintf('O:%d:"%s":0:{}', \strlen($f['class']), $f['class'])); - } - if (null !== $template) { - $ellipsis = 0; - $templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : ''); - $templateInfo = $template->getDebugInfo(); - if (isset($templateInfo[$f['line']])) { - if (!method_exists($template, 'getSourceContext') || !is_file($templatePath = $template->getSourceContext()->getPath())) { - $templatePath = null; - } - if ($templateSrc) { - $src = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext, 'twig', $templatePath, $f); - $srcKey = ($templatePath ?: $template->getTemplateName()).':'.$templateInfo[$f['line']]; - } - } - } - } - if ($srcKey == $f['file']) { - $src = self::extractSource(file_get_contents($f['file']), $f['line'], self::$srcContext, 'php', $f['file'], $f); - $srcKey .= ':'.$f['line']; - if ($ellipsis) { - $ellipsis += 1 + \strlen($f['line']); - } - } - $srcAttr .= sprintf('&separator= &file=%s&line=%d', rawurlencode($f['file']), $f['line']); - } else { - $srcAttr .= '&separator=:'; - } - $srcAttr .= $ellipsis ? '&ellipsis-type=path&ellipsis='.$ellipsis.'&ellipsis-tail='.$ellipsisTail : ''; - self::$framesCache[$cacheKey] = $a[$prefix.'src'] = new EnumStub(["\0~$srcAttr\0$srcKey" => $src]); - } - } - - unset($a[$prefix.'args'], $a[$prefix.'line'], $a[$prefix.'file']); - if ($frame->inTraceStub) { - unset($a[$prefix.'class'], $a[$prefix.'type'], $a[$prefix.'function']); - } - foreach ($a as $k => $v) { - if (!$v) { - unset($a[$k]); - } - } - if ($frame->keepArgs && !empty($f['args'])) { - $a[$prefix.'arguments'] = new ArgsStub($f['args'], $f['function'], $f['class']); - } - - return $a; - } - - /** - * @return array - */ - public static function castFlattenException(FlattenException $e, array $a, Stub $stub, bool $isNested) - { - if ($isNested) { - $k = sprintf(Caster::PATTERN_PRIVATE, FlattenException::class, 'traceAsString'); - $a[$k] = new CutStub($a[$k]); - } - - return $a; - } - - private static function filterExceptionArray(string $xClass, array $a, string $xPrefix, int $filter): array - { - if (isset($a[$xPrefix.'trace'])) { - $trace = $a[$xPrefix.'trace']; - unset($a[$xPrefix.'trace']); // Ensures the trace is always last - } else { - $trace = []; - } - - if (!($filter & Caster::EXCLUDE_VERBOSE) && $trace) { - if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { - self::traceUnshift($trace, $xClass, $a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']); - } - $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs); - } - if (empty($a[$xPrefix.'previous'])) { - unset($a[$xPrefix.'previous']); - } - unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message']); - - if (isset($a[Caster::PREFIX_PROTECTED.'message']) && str_contains($a[Caster::PREFIX_PROTECTED.'message'], "@anonymous\0")) { - $a[Caster::PREFIX_PROTECTED.'message'] = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $a[Caster::PREFIX_PROTECTED.'message']); - } - - if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { - $a[Caster::PREFIX_PROTECTED.'file'] = new LinkStub($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']); - } - - return $a; - } - - private static function traceUnshift(array &$trace, ?string $class, string $file, int $line): void - { - if (isset($trace[0]['file'], $trace[0]['line']) && $trace[0]['file'] === $file && $trace[0]['line'] === $line) { - return; - } - array_unshift($trace, [ - 'function' => $class ? 'new '.$class : null, - 'file' => $file, - 'line' => $line, - ]); - } - - private static function extractSource(string $srcLines, int $line, int $srcContext, string $lang, ?string $file, array $frame): EnumStub - { - $srcLines = explode("\n", $srcLines); - $src = []; - - for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) { - $src[] = ($srcLines[$i] ?? '')."\n"; - } - - if ($frame['function'] ?? false) { - $stub = new CutStub(new \stdClass()); - $stub->class = (isset($frame['class']) ? $frame['class'].$frame['type'] : '').$frame['function']; - $stub->type = Stub::TYPE_OBJECT; - $stub->attr['cut_hash'] = true; - $stub->attr['file'] = $frame['file']; - $stub->attr['line'] = $frame['line']; - - try { - $caller = isset($frame['class']) ? new \ReflectionMethod($frame['class'], $frame['function']) : new \ReflectionFunction($frame['function']); - $stub->class .= ReflectionCaster::getSignature(ReflectionCaster::castFunctionAbstract($caller, [], $stub, true, Caster::EXCLUDE_VERBOSE)); - - if ($f = $caller->getFileName()) { - $stub->attr['file'] = $f; - $stub->attr['line'] = $caller->getStartLine(); - } - } catch (\ReflectionException) { - // ignore fake class/function - } - - $srcLines = ["\0~separator=\0" => $stub]; - } else { - $stub = null; - $srcLines = []; - } - - $ltrim = 0; - do { - $pad = null; - for ($i = $srcContext << 1; $i >= 0; --$i) { - if (isset($src[$i][$ltrim]) && "\r" !== ($c = $src[$i][$ltrim]) && "\n" !== $c) { - $pad ??= $c; - if ((' ' !== $c && "\t" !== $c) || $pad !== $c) { - break; - } - } - } - ++$ltrim; - } while (0 > $i && null !== $pad); - - --$ltrim; - - foreach ($src as $i => $c) { - if ($ltrim) { - $c = isset($c[$ltrim]) && "\r" !== $c[$ltrim] ? substr($c, $ltrim) : ltrim($c, " \t"); - } - $c = substr($c, 0, -1); - if ($i !== $srcContext) { - $c = new ConstStub('default', $c); - } else { - $c = new ConstStub($c, $stub ? 'in '.$stub->class : ''); - if (null !== $file) { - $c->attr['file'] = $file; - $c->attr['line'] = $line; - } - } - $c->attr['lang'] = $lang; - $srcLines[sprintf("\0~separator=› &%d\0", $i + $line - $srcContext)] = $c; - } - - return new EnumStub($srcLines); - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/FFICaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/FFICaster.php deleted file mode 100644 index f1984eef3..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/FFICaster.php +++ /dev/null @@ -1,161 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use FFI\CData; -use FFI\CType; -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts FFI extension classes to array representation. - * - * @author Nesmeyanov Kirill - */ -final class FFICaster -{ - /** - * In case of "char*" contains a string, the length of which depends on - * some other parameter, then during the generation of the string it is - * possible to go beyond the allowable memory area. - * - * This restriction serves to ensure that processing does not take - * up the entire allowable PHP memory limit. - */ - private const MAX_STRING_LENGTH = 255; - - public static function castCTypeOrCData(CData|CType $data, array $args, Stub $stub): array - { - if ($data instanceof CType) { - $type = $data; - $data = null; - } else { - $type = \FFI::typeof($data); - } - - $stub->class = sprintf('%s<%s> size %d align %d', ($data ?? $type)::class, $type->getName(), $type->getSize(), $type->getAlignment()); - - return match ($type->getKind()) { - CType::TYPE_FLOAT, - CType::TYPE_DOUBLE, - \defined('\FFI\CType::TYPE_LONGDOUBLE') ? CType::TYPE_LONGDOUBLE : -1, - CType::TYPE_UINT8, - CType::TYPE_SINT8, - CType::TYPE_UINT16, - CType::TYPE_SINT16, - CType::TYPE_UINT32, - CType::TYPE_SINT32, - CType::TYPE_UINT64, - CType::TYPE_SINT64, - CType::TYPE_BOOL, - CType::TYPE_CHAR, - CType::TYPE_ENUM => null !== $data ? [Caster::PREFIX_VIRTUAL.'cdata' => $data->cdata] : [], - CType::TYPE_POINTER => self::castFFIPointer($stub, $type, $data), - CType::TYPE_STRUCT => self::castFFIStructLike($type, $data), - CType::TYPE_FUNC => self::castFFIFunction($stub, $type), - default => $args, - }; - } - - private static function castFFIFunction(Stub $stub, CType $type): array - { - $arguments = []; - - for ($i = 0, $count = $type->getFuncParameterCount(); $i < $count; ++$i) { - $param = $type->getFuncParameterType($i); - - $arguments[] = $param->getName(); - } - - $abi = match ($type->getFuncABI()) { - CType::ABI_DEFAULT, - CType::ABI_CDECL => '[cdecl]', - CType::ABI_FASTCALL => '[fastcall]', - CType::ABI_THISCALL => '[thiscall]', - CType::ABI_STDCALL => '[stdcall]', - CType::ABI_PASCAL => '[pascal]', - CType::ABI_REGISTER => '[register]', - CType::ABI_MS => '[ms]', - CType::ABI_SYSV => '[sysv]', - CType::ABI_VECTORCALL => '[vectorcall]', - default => '[unknown abi]' - }; - - $returnType = $type->getFuncReturnType(); - - $stub->class = $abi.' callable('.implode(', ', $arguments).'): ' - .$returnType->getName(); - - return [Caster::PREFIX_VIRTUAL.'returnType' => $returnType]; - } - - private static function castFFIPointer(Stub $stub, CType $type, ?CData $data = null): array - { - $ptr = $type->getPointerType(); - - if (null === $data) { - return [Caster::PREFIX_VIRTUAL.'0' => $ptr]; - } - - return match ($ptr->getKind()) { - CType::TYPE_CHAR => [Caster::PREFIX_VIRTUAL.'cdata' => self::castFFIStringValue($data)], - CType::TYPE_FUNC => self::castFFIFunction($stub, $ptr), - default => [Caster::PREFIX_VIRTUAL.'cdata' => $data[0]], - }; - } - - private static function castFFIStringValue(CData $data): string|CutStub - { - $result = []; - - for ($i = 0; $i < self::MAX_STRING_LENGTH; ++$i) { - $result[$i] = $data[$i]; - - if ("\0" === $result[$i]) { - return implode('', $result); - } - } - - $string = implode('', $result); - $stub = new CutStub($string); - $stub->cut = -1; - $stub->value = $string; - - return $stub; - } - - private static function castFFIStructLike(CType $type, ?CData $data = null): array - { - $isUnion = ($type->getAttributes() & CType::ATTR_UNION) === CType::ATTR_UNION; - - $result = []; - - foreach ($type->getStructFieldNames() as $name) { - $field = $type->getStructFieldType($name); - - // Retrieving the value of a field from a union containing - // a pointer is not a safe operation, because may contain - // incorrect data. - $isUnsafe = $isUnion && CType::TYPE_POINTER === $field->getKind(); - - if ($isUnsafe) { - $result[Caster::PREFIX_VIRTUAL.$name.'?'] = $field; - } elseif (null === $data) { - $result[Caster::PREFIX_VIRTUAL.$name] = $field; - } else { - $fieldName = $data->{$name} instanceof CData ? '' : $field->getName().' '; - $result[Caster::PREFIX_VIRTUAL.$fieldName.$name] = $data->{$name}; - } - } - - return $result; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/FiberCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/FiberCaster.php deleted file mode 100644 index b797dbd63..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/FiberCaster.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts Fiber related classes to array representation. - * - * @author Grégoire Pineau - */ -final class FiberCaster -{ - /** - * @return array - */ - public static function castFiber(\Fiber $fiber, array $a, Stub $stub, bool $isNested, int $filter = 0) - { - $prefix = Caster::PREFIX_VIRTUAL; - - if ($fiber->isTerminated()) { - $status = 'terminated'; - } elseif ($fiber->isRunning()) { - $status = 'running'; - } elseif ($fiber->isSuspended()) { - $status = 'suspended'; - } elseif ($fiber->isStarted()) { - $status = 'started'; - } else { - $status = 'not started'; - } - - $a[$prefix.'status'] = $status; - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/FrameStub.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/FrameStub.php deleted file mode 100644 index 878675528..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/FrameStub.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -/** - * Represents a single backtrace frame as returned by debug_backtrace() or Exception->getTrace(). - * - * @author Nicolas Grekas - */ -class FrameStub extends EnumStub -{ - public $keepArgs; - public $inTraceStub; - - public function __construct(array $frame, bool $keepArgs = true, bool $inTraceStub = false) - { - $this->value = $frame; - $this->keepArgs = $keepArgs; - $this->inTraceStub = $inTraceStub; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/GmpCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/GmpCaster.php deleted file mode 100644 index b018cc7f8..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/GmpCaster.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts GMP objects to array representation. - * - * @author Hamza Amrouche - * @author Nicolas Grekas - * - * @final - */ -class GmpCaster -{ - public static function castGmp(\GMP $gmp, array $a, Stub $stub, bool $isNested, int $filter): array - { - $a[Caster::PREFIX_VIRTUAL.'value'] = new ConstStub(gmp_strval($gmp), gmp_strval($gmp)); - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ImagineCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ImagineCaster.php deleted file mode 100644 index d1289da33..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ImagineCaster.php +++ /dev/null @@ -1,37 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Imagine\Image\ImageInterface; -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * @author Grégoire Pineau - */ -final class ImagineCaster -{ - public static function castImage(ImageInterface $c, array $a, Stub $stub, bool $isNested): array - { - $imgData = $c->get('png'); - if (\strlen($imgData) > 1 * 1000 * 1000) { - $a += [ - Caster::PREFIX_VIRTUAL.'image' => new ConstStub($c->getSize()), - ]; - } else { - $a += [ - Caster::PREFIX_VIRTUAL.'image' => new ImgStub($imgData, 'image/png', $c->getSize()), - ]; - } - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ImgStub.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ImgStub.php deleted file mode 100644 index a16681f73..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ImgStub.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -/** - * @author Grégoire Pineau - */ -class ImgStub extends ConstStub -{ - public function __construct(string $data, string $contentType, string $size = '') - { - $this->value = ''; - $this->attr['img-data'] = $data; - $this->attr['img-size'] = $size; - $this->attr['content-type'] = $contentType; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/IntlCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/IntlCaster.php deleted file mode 100644 index a4590f4b5..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/IntlCaster.php +++ /dev/null @@ -1,187 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * @author Nicolas Grekas - * @author Jan Schädlich - * - * @final - */ -class IntlCaster -{ - /** - * @return array - */ - public static function castMessageFormatter(\MessageFormatter $c, array $a, Stub $stub, bool $isNested) - { - $a += [ - Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), - Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), - ]; - - return self::castError($c, $a); - } - - /** - * @return array - */ - public static function castNumberFormatter(\NumberFormatter $c, array $a, Stub $stub, bool $isNested, int $filter = 0) - { - $a += [ - Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), - Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), - ]; - - if ($filter & Caster::EXCLUDE_VERBOSE) { - $stub->cut += 3; - - return self::castError($c, $a); - } - - $a += [ - Caster::PREFIX_VIRTUAL.'attributes' => new EnumStub( - [ - 'PARSE_INT_ONLY' => $c->getAttribute(\NumberFormatter::PARSE_INT_ONLY), - 'GROUPING_USED' => $c->getAttribute(\NumberFormatter::GROUPING_USED), - 'DECIMAL_ALWAYS_SHOWN' => $c->getAttribute(\NumberFormatter::DECIMAL_ALWAYS_SHOWN), - 'MAX_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_INTEGER_DIGITS), - 'MIN_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_INTEGER_DIGITS), - 'INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::INTEGER_DIGITS), - 'MAX_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_FRACTION_DIGITS), - 'MIN_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_FRACTION_DIGITS), - 'FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::FRACTION_DIGITS), - 'MULTIPLIER' => $c->getAttribute(\NumberFormatter::MULTIPLIER), - 'GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::GROUPING_SIZE), - 'ROUNDING_MODE' => $c->getAttribute(\NumberFormatter::ROUNDING_MODE), - 'ROUNDING_INCREMENT' => $c->getAttribute(\NumberFormatter::ROUNDING_INCREMENT), - 'FORMAT_WIDTH' => $c->getAttribute(\NumberFormatter::FORMAT_WIDTH), - 'PADDING_POSITION' => $c->getAttribute(\NumberFormatter::PADDING_POSITION), - 'SECONDARY_GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::SECONDARY_GROUPING_SIZE), - 'SIGNIFICANT_DIGITS_USED' => $c->getAttribute(\NumberFormatter::SIGNIFICANT_DIGITS_USED), - 'MIN_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_SIGNIFICANT_DIGITS), - 'MAX_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_SIGNIFICANT_DIGITS), - 'LENIENT_PARSE' => $c->getAttribute(\NumberFormatter::LENIENT_PARSE), - ] - ), - Caster::PREFIX_VIRTUAL.'text_attributes' => new EnumStub( - [ - 'POSITIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_PREFIX), - 'POSITIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_SUFFIX), - 'NEGATIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_PREFIX), - 'NEGATIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_SUFFIX), - 'PADDING_CHARACTER' => $c->getTextAttribute(\NumberFormatter::PADDING_CHARACTER), - 'CURRENCY_CODE' => $c->getTextAttribute(\NumberFormatter::CURRENCY_CODE), - 'DEFAULT_RULESET' => $c->getTextAttribute(\NumberFormatter::DEFAULT_RULESET), - 'PUBLIC_RULESETS' => $c->getTextAttribute(\NumberFormatter::PUBLIC_RULESETS), - ] - ), - Caster::PREFIX_VIRTUAL.'symbols' => new EnumStub( - [ - 'DECIMAL_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL), - 'GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL), - 'PATTERN_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::PATTERN_SEPARATOR_SYMBOL), - 'PERCENT_SYMBOL' => $c->getSymbol(\NumberFormatter::PERCENT_SYMBOL), - 'ZERO_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::ZERO_DIGIT_SYMBOL), - 'DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::DIGIT_SYMBOL), - 'MINUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::MINUS_SIGN_SYMBOL), - 'PLUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::PLUS_SIGN_SYMBOL), - 'CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::CURRENCY_SYMBOL), - 'INTL_CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::INTL_CURRENCY_SYMBOL), - 'MONETARY_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL), - 'EXPONENTIAL_SYMBOL' => $c->getSymbol(\NumberFormatter::EXPONENTIAL_SYMBOL), - 'PERMILL_SYMBOL' => $c->getSymbol(\NumberFormatter::PERMILL_SYMBOL), - 'PAD_ESCAPE_SYMBOL' => $c->getSymbol(\NumberFormatter::PAD_ESCAPE_SYMBOL), - 'INFINITY_SYMBOL' => $c->getSymbol(\NumberFormatter::INFINITY_SYMBOL), - 'NAN_SYMBOL' => $c->getSymbol(\NumberFormatter::NAN_SYMBOL), - 'SIGNIFICANT_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL), - 'MONETARY_GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL), - ] - ), - ]; - - return self::castError($c, $a); - } - - /** - * @return array - */ - public static function castIntlTimeZone(\IntlTimeZone $c, array $a, Stub $stub, bool $isNested) - { - $a += [ - Caster::PREFIX_VIRTUAL.'display_name' => $c->getDisplayName(), - Caster::PREFIX_VIRTUAL.'id' => $c->getID(), - Caster::PREFIX_VIRTUAL.'raw_offset' => $c->getRawOffset(), - ]; - - if ($c->useDaylightTime()) { - $a += [ - Caster::PREFIX_VIRTUAL.'dst_savings' => $c->getDSTSavings(), - ]; - } - - return self::castError($c, $a); - } - - /** - * @return array - */ - public static function castIntlCalendar(\IntlCalendar $c, array $a, Stub $stub, bool $isNested, int $filter = 0) - { - $a += [ - Caster::PREFIX_VIRTUAL.'type' => $c->getType(), - Caster::PREFIX_VIRTUAL.'first_day_of_week' => $c->getFirstDayOfWeek(), - Caster::PREFIX_VIRTUAL.'minimal_days_in_first_week' => $c->getMinimalDaysInFirstWeek(), - Caster::PREFIX_VIRTUAL.'repeated_wall_time_option' => $c->getRepeatedWallTimeOption(), - Caster::PREFIX_VIRTUAL.'skipped_wall_time_option' => $c->getSkippedWallTimeOption(), - Caster::PREFIX_VIRTUAL.'time' => $c->getTime(), - Caster::PREFIX_VIRTUAL.'in_daylight_time' => $c->inDaylightTime(), - Caster::PREFIX_VIRTUAL.'is_lenient' => $c->isLenient(), - Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(), - ]; - - return self::castError($c, $a); - } - - /** - * @return array - */ - public static function castIntlDateFormatter(\IntlDateFormatter $c, array $a, Stub $stub, bool $isNested, int $filter = 0) - { - $a += [ - Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), - Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), - Caster::PREFIX_VIRTUAL.'calendar' => $c->getCalendar(), - Caster::PREFIX_VIRTUAL.'time_zone_id' => $c->getTimeZoneId(), - Caster::PREFIX_VIRTUAL.'time_type' => $c->getTimeType(), - Caster::PREFIX_VIRTUAL.'date_type' => $c->getDateType(), - Caster::PREFIX_VIRTUAL.'calendar_object' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getCalendarObject()) : $c->getCalendarObject(), - Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(), - ]; - - return self::castError($c, $a); - } - - private static function castError(object $c, array $a): array - { - if ($errorCode = $c->getErrorCode()) { - $a += [ - Caster::PREFIX_VIRTUAL.'error_code' => $errorCode, - Caster::PREFIX_VIRTUAL.'error_message' => $c->getErrorMessage(), - ]; - } - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/LinkStub.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/LinkStub.php deleted file mode 100644 index 4930436df..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/LinkStub.php +++ /dev/null @@ -1,105 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -/** - * Represents a file or a URL. - * - * @author Nicolas Grekas - */ -class LinkStub extends ConstStub -{ - public $inVendor = false; - - private static array $vendorRoots; - private static array $composerRoots = []; - - public function __construct(string $label, int $line = 0, ?string $href = null) - { - $this->value = $label; - - if (!\is_string($href ??= $label)) { - return; - } - if (str_starts_with($href, 'file://')) { - if ($href === $label) { - $label = substr($label, 7); - } - $href = substr($href, 7); - } elseif (str_contains($href, '://')) { - $this->attr['href'] = $href; - - return; - } - if (!is_file($href)) { - return; - } - if ($line) { - $this->attr['line'] = $line; - } - if ($label !== $this->attr['file'] = realpath($href) ?: $href) { - return; - } - if ($composerRoot = $this->getComposerRoot($href, $this->inVendor)) { - $this->attr['ellipsis'] = \strlen($href) - \strlen($composerRoot) + 1; - $this->attr['ellipsis-type'] = 'path'; - $this->attr['ellipsis-tail'] = 1 + ($this->inVendor ? 2 + \strlen(implode('', \array_slice(explode(\DIRECTORY_SEPARATOR, substr($href, 1 - $this->attr['ellipsis'])), 0, 2))) : 0); - } elseif (3 < \count($ellipsis = explode(\DIRECTORY_SEPARATOR, $href))) { - $this->attr['ellipsis'] = 2 + \strlen(implode('', \array_slice($ellipsis, -2))); - $this->attr['ellipsis-type'] = 'path'; - $this->attr['ellipsis-tail'] = 1; - } - } - - private function getComposerRoot(string $file, bool &$inVendor): string|false - { - if (!isset(self::$vendorRoots)) { - self::$vendorRoots = []; - - foreach (get_declared_classes() as $class) { - if ('C' === $class[0] && str_starts_with($class, 'ComposerAutoloaderInit')) { - $r = new \ReflectionClass($class); - $v = \dirname($r->getFileName(), 2); - if (is_file($v.'/composer/installed.json')) { - self::$vendorRoots[] = $v.\DIRECTORY_SEPARATOR; - } - } - } - } - $inVendor = false; - - if (isset(self::$composerRoots[$dir = \dirname($file)])) { - return self::$composerRoots[$dir]; - } - - foreach (self::$vendorRoots as $root) { - if ($inVendor = str_starts_with($file, $root)) { - return $root; - } - } - - $parent = $dir; - while (!@is_file($parent.'/composer.json')) { - if (!@file_exists($parent)) { - // open_basedir restriction in effect - break; - } - if ($parent === \dirname($parent)) { - return self::$composerRoots[$dir] = false; - } - - $parent = \dirname($parent); - } - - return self::$composerRoots[$dir] = $parent.\DIRECTORY_SEPARATOR; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/MemcachedCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/MemcachedCaster.php deleted file mode 100644 index 2f161e8cb..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/MemcachedCaster.php +++ /dev/null @@ -1,84 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * @author Jan Schädlich - * - * @final - */ -class MemcachedCaster -{ - private static array $optionConstants; - private static array $defaultOptions; - - /** - * @return array - */ - public static function castMemcached(\Memcached $c, array $a, Stub $stub, bool $isNested) - { - $a += [ - Caster::PREFIX_VIRTUAL.'servers' => $c->getServerList(), - Caster::PREFIX_VIRTUAL.'options' => new EnumStub( - self::getNonDefaultOptions($c) - ), - ]; - - return $a; - } - - private static function getNonDefaultOptions(\Memcached $c): array - { - self::$defaultOptions ??= self::discoverDefaultOptions(); - self::$optionConstants ??= self::getOptionConstants(); - - $nonDefaultOptions = []; - foreach (self::$optionConstants as $constantKey => $value) { - if (self::$defaultOptions[$constantKey] !== $option = $c->getOption($value)) { - $nonDefaultOptions[$constantKey] = $option; - } - } - - return $nonDefaultOptions; - } - - private static function discoverDefaultOptions(): array - { - $defaultMemcached = new \Memcached(); - $defaultMemcached->addServer('127.0.0.1', 11211); - - $defaultOptions = []; - self::$optionConstants ??= self::getOptionConstants(); - - foreach (self::$optionConstants as $constantKey => $value) { - $defaultOptions[$constantKey] = $defaultMemcached->getOption($value); - } - - return $defaultOptions; - } - - private static function getOptionConstants(): array - { - $reflectedMemcached = new \ReflectionClass(\Memcached::class); - - $optionConstants = []; - foreach ($reflectedMemcached->getConstants() as $constantKey => $value) { - if (str_starts_with($constantKey, 'OPT_')) { - $optionConstants[$constantKey] = $value; - } - } - - return $optionConstants; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/MysqliCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/MysqliCaster.php deleted file mode 100644 index bfe6f0822..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/MysqliCaster.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * @author Nicolas Grekas - * - * @internal - */ -final class MysqliCaster -{ - public static function castMysqliDriver(\mysqli_driver $c, array $a, Stub $stub, bool $isNested): array - { - foreach ($a as $k => $v) { - if (isset($c->$k)) { - $a[$k] = $c->$k; - } - } - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/PdoCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/PdoCaster.php deleted file mode 100644 index d68eae216..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/PdoCaster.php +++ /dev/null @@ -1,128 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts PDO related classes to array representation. - * - * @author Nicolas Grekas - * - * @final - */ -class PdoCaster -{ - private const PDO_ATTRIBUTES = [ - 'CASE' => [ - \PDO::CASE_LOWER => 'LOWER', - \PDO::CASE_NATURAL => 'NATURAL', - \PDO::CASE_UPPER => 'UPPER', - ], - 'ERRMODE' => [ - \PDO::ERRMODE_SILENT => 'SILENT', - \PDO::ERRMODE_WARNING => 'WARNING', - \PDO::ERRMODE_EXCEPTION => 'EXCEPTION', - ], - 'TIMEOUT', - 'PREFETCH', - 'AUTOCOMMIT', - 'PERSISTENT', - 'DRIVER_NAME', - 'SERVER_INFO', - 'ORACLE_NULLS' => [ - \PDO::NULL_NATURAL => 'NATURAL', - \PDO::NULL_EMPTY_STRING => 'EMPTY_STRING', - \PDO::NULL_TO_STRING => 'TO_STRING', - ], - 'CLIENT_VERSION', - 'SERVER_VERSION', - 'STATEMENT_CLASS', - 'EMULATE_PREPARES', - 'CONNECTION_STATUS', - 'STRINGIFY_FETCHES', - 'DEFAULT_FETCH_MODE' => [ - \PDO::FETCH_ASSOC => 'ASSOC', - \PDO::FETCH_BOTH => 'BOTH', - \PDO::FETCH_LAZY => 'LAZY', - \PDO::FETCH_NUM => 'NUM', - \PDO::FETCH_OBJ => 'OBJ', - ], - ]; - - /** - * @return array - */ - public static function castPdo(\PDO $c, array $a, Stub $stub, bool $isNested) - { - $attr = []; - $errmode = $c->getAttribute(\PDO::ATTR_ERRMODE); - $c->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - - foreach (self::PDO_ATTRIBUTES as $k => $v) { - if (!isset($k[0])) { - $k = $v; - $v = []; - } - - try { - $attr[$k] = 'ERRMODE' === $k ? $errmode : $c->getAttribute(\constant('PDO::ATTR_'.$k)); - if ($v && isset($v[$attr[$k]])) { - $attr[$k] = new ConstStub($v[$attr[$k]], $attr[$k]); - } - } catch (\Exception) { - } - } - if (isset($attr[$k = 'STATEMENT_CLASS'][1])) { - if ($attr[$k][1]) { - $attr[$k][1] = new ArgsStub($attr[$k][1], '__construct', $attr[$k][0]); - } - $attr[$k][0] = new ClassStub($attr[$k][0]); - } - - $prefix = Caster::PREFIX_VIRTUAL; - $a += [ - $prefix.'inTransaction' => method_exists($c, 'inTransaction'), - $prefix.'errorInfo' => $c->errorInfo(), - $prefix.'attributes' => new EnumStub($attr), - ]; - - if ($a[$prefix.'inTransaction']) { - $a[$prefix.'inTransaction'] = $c->inTransaction(); - } else { - unset($a[$prefix.'inTransaction']); - } - - if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) { - unset($a[$prefix.'errorInfo']); - } - - $c->setAttribute(\PDO::ATTR_ERRMODE, $errmode); - - return $a; - } - - /** - * @return array - */ - public static function castPdoStatement(\PDOStatement $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - $a[$prefix.'errorInfo'] = $c->errorInfo(); - - if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) { - unset($a[$prefix.'errorInfo']); - } - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/PgSqlCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/PgSqlCaster.php deleted file mode 100644 index 0d8b3d919..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/PgSqlCaster.php +++ /dev/null @@ -1,165 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts pqsql resources to array representation. - * - * @author Nicolas Grekas - * - * @final - */ -class PgSqlCaster -{ - private const PARAM_CODES = [ - 'server_encoding', - 'client_encoding', - 'is_superuser', - 'session_authorization', - 'DateStyle', - 'TimeZone', - 'IntervalStyle', - 'integer_datetimes', - 'application_name', - 'standard_conforming_strings', - ]; - - private const TRANSACTION_STATUS = [ - \PGSQL_TRANSACTION_IDLE => 'PGSQL_TRANSACTION_IDLE', - \PGSQL_TRANSACTION_ACTIVE => 'PGSQL_TRANSACTION_ACTIVE', - \PGSQL_TRANSACTION_INTRANS => 'PGSQL_TRANSACTION_INTRANS', - \PGSQL_TRANSACTION_INERROR => 'PGSQL_TRANSACTION_INERROR', - \PGSQL_TRANSACTION_UNKNOWN => 'PGSQL_TRANSACTION_UNKNOWN', - ]; - - private const RESULT_STATUS = [ - \PGSQL_EMPTY_QUERY => 'PGSQL_EMPTY_QUERY', - \PGSQL_COMMAND_OK => 'PGSQL_COMMAND_OK', - \PGSQL_TUPLES_OK => 'PGSQL_TUPLES_OK', - \PGSQL_COPY_OUT => 'PGSQL_COPY_OUT', - \PGSQL_COPY_IN => 'PGSQL_COPY_IN', - \PGSQL_BAD_RESPONSE => 'PGSQL_BAD_RESPONSE', - \PGSQL_NONFATAL_ERROR => 'PGSQL_NONFATAL_ERROR', - \PGSQL_FATAL_ERROR => 'PGSQL_FATAL_ERROR', - ]; - - private const DIAG_CODES = [ - 'severity' => \PGSQL_DIAG_SEVERITY, - 'sqlstate' => \PGSQL_DIAG_SQLSTATE, - 'message' => \PGSQL_DIAG_MESSAGE_PRIMARY, - 'detail' => \PGSQL_DIAG_MESSAGE_DETAIL, - 'hint' => \PGSQL_DIAG_MESSAGE_HINT, - 'statement position' => \PGSQL_DIAG_STATEMENT_POSITION, - 'internal position' => \PGSQL_DIAG_INTERNAL_POSITION, - 'internal query' => \PGSQL_DIAG_INTERNAL_QUERY, - 'context' => \PGSQL_DIAG_CONTEXT, - 'file' => \PGSQL_DIAG_SOURCE_FILE, - 'line' => \PGSQL_DIAG_SOURCE_LINE, - 'function' => \PGSQL_DIAG_SOURCE_FUNCTION, - ]; - - /** - * @return array - */ - public static function castLargeObject($lo, array $a, Stub $stub, bool $isNested) - { - $a['seek position'] = pg_lo_tell($lo); - - return $a; - } - - /** - * @return array - */ - public static function castLink($link, array $a, Stub $stub, bool $isNested) - { - $a['status'] = pg_connection_status($link); - $a['status'] = new ConstStub(\PGSQL_CONNECTION_OK === $a['status'] ? 'PGSQL_CONNECTION_OK' : 'PGSQL_CONNECTION_BAD', $a['status']); - $a['busy'] = pg_connection_busy($link); - - $a['transaction'] = pg_transaction_status($link); - if (isset(self::TRANSACTION_STATUS[$a['transaction']])) { - $a['transaction'] = new ConstStub(self::TRANSACTION_STATUS[$a['transaction']], $a['transaction']); - } - - $a['pid'] = pg_get_pid($link); - $a['last error'] = pg_last_error($link); - $a['last notice'] = pg_last_notice($link); - $a['host'] = pg_host($link); - $a['port'] = pg_port($link); - $a['dbname'] = pg_dbname($link); - $a['options'] = pg_options($link); - $a['version'] = pg_version($link); - - foreach (self::PARAM_CODES as $v) { - if (false !== $s = pg_parameter_status($link, $v)) { - $a['param'][$v] = $s; - } - } - - $a['param']['client_encoding'] = pg_client_encoding($link); - $a['param'] = new EnumStub($a['param']); - - return $a; - } - - /** - * @return array - */ - public static function castResult($result, array $a, Stub $stub, bool $isNested) - { - $a['num rows'] = pg_num_rows($result); - $a['status'] = pg_result_status($result); - if (isset(self::RESULT_STATUS[$a['status']])) { - $a['status'] = new ConstStub(self::RESULT_STATUS[$a['status']], $a['status']); - } - $a['command-completion tag'] = pg_result_status($result, \PGSQL_STATUS_STRING); - - if (-1 === $a['num rows']) { - foreach (self::DIAG_CODES as $k => $v) { - $a['error'][$k] = pg_result_error_field($result, $v); - } - } - - $a['affected rows'] = pg_affected_rows($result); - $a['last OID'] = pg_last_oid($result); - - $fields = pg_num_fields($result); - - for ($i = 0; $i < $fields; ++$i) { - $field = [ - 'name' => pg_field_name($result, $i), - 'table' => sprintf('%s (OID: %s)', pg_field_table($result, $i), pg_field_table($result, $i, true)), - 'type' => sprintf('%s (OID: %s)', pg_field_type($result, $i), pg_field_type_oid($result, $i)), - 'nullable' => (bool) pg_field_is_null($result, $i), - 'storage' => pg_field_size($result, $i).' bytes', - 'display' => pg_field_prtlen($result, $i).' chars', - ]; - if (' (OID: )' === $field['table']) { - $field['table'] = null; - } - if ('-1 bytes' === $field['storage']) { - $field['storage'] = 'variable size'; - } elseif ('1 bytes' === $field['storage']) { - $field['storage'] = '1 byte'; - } - if ('1 chars' === $field['display']) { - $field['display'] = '1 char'; - } - $a['fields'][] = new EnumStub($field); - } - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php deleted file mode 100644 index eb6c88db6..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use ProxyManager\Proxy\ProxyInterface; -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * @author Nicolas Grekas - * - * @final - */ -class ProxyManagerCaster -{ - /** - * @return array - */ - public static function castProxy(ProxyInterface $c, array $a, Stub $stub, bool $isNested) - { - if ($parent = get_parent_class($c)) { - $stub->class .= ' - '.$parent; - } - $stub->class .= '@proxy'; - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/RdKafkaCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/RdKafkaCaster.php deleted file mode 100644 index fcaa1b768..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/RdKafkaCaster.php +++ /dev/null @@ -1,222 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use RdKafka\Conf; -use RdKafka\Exception as RdKafkaException; -use RdKafka\KafkaConsumer; -use RdKafka\Message; -use RdKafka\Metadata\Broker as BrokerMetadata; -use RdKafka\Metadata\Collection as CollectionMetadata; -use RdKafka\Metadata\Partition as PartitionMetadata; -use RdKafka\Metadata\Topic as TopicMetadata; -use RdKafka\Topic; -use RdKafka\TopicConf; -use RdKafka\TopicPartition; -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts RdKafka related classes to array representation. - * - * @author Romain Neutron - */ -class RdKafkaCaster -{ - /** - * @return array - */ - public static function castKafkaConsumer(KafkaConsumer $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - try { - $assignment = $c->getAssignment(); - } catch (RdKafkaException) { - $assignment = []; - } - - $a += [ - $prefix.'subscription' => $c->getSubscription(), - $prefix.'assignment' => $assignment, - ]; - - $a += self::extractMetadata($c); - - return $a; - } - - /** - * @return array - */ - public static function castTopic(Topic $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - $a += [ - $prefix.'name' => $c->getName(), - ]; - - return $a; - } - - /** - * @return array - */ - public static function castTopicPartition(TopicPartition $c, array $a) - { - $prefix = Caster::PREFIX_VIRTUAL; - - $a += [ - $prefix.'offset' => $c->getOffset(), - $prefix.'partition' => $c->getPartition(), - $prefix.'topic' => $c->getTopic(), - ]; - - return $a; - } - - /** - * @return array - */ - public static function castMessage(Message $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - $a += [ - $prefix.'errstr' => $c->errstr(), - ]; - - return $a; - } - - /** - * @return array - */ - public static function castConf(Conf $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - foreach ($c->dump() as $key => $value) { - $a[$prefix.$key] = $value; - } - - return $a; - } - - /** - * @return array - */ - public static function castTopicConf(TopicConf $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - foreach ($c->dump() as $key => $value) { - $a[$prefix.$key] = $value; - } - - return $a; - } - - /** - * @return array - */ - public static function castRdKafka(\RdKafka $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - $a += [ - $prefix.'out_q_len' => $c->getOutQLen(), - ]; - - $a += self::extractMetadata($c); - - return $a; - } - - /** - * @return array - */ - public static function castCollectionMetadata(CollectionMetadata $c, array $a, Stub $stub, bool $isNested) - { - $a += iterator_to_array($c); - - return $a; - } - - /** - * @return array - */ - public static function castTopicMetadata(TopicMetadata $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - $a += [ - $prefix.'name' => $c->getTopic(), - $prefix.'partitions' => $c->getPartitions(), - ]; - - return $a; - } - - /** - * @return array - */ - public static function castPartitionMetadata(PartitionMetadata $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - $a += [ - $prefix.'id' => $c->getId(), - $prefix.'err' => $c->getErr(), - $prefix.'leader' => $c->getLeader(), - ]; - - return $a; - } - - /** - * @return array - */ - public static function castBrokerMetadata(BrokerMetadata $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - $a += [ - $prefix.'id' => $c->getId(), - $prefix.'host' => $c->getHost(), - $prefix.'port' => $c->getPort(), - ]; - - return $a; - } - - /** - * @return array - */ - private static function extractMetadata(KafkaConsumer|\RdKafka $c) - { - $prefix = Caster::PREFIX_VIRTUAL; - - try { - $m = $c->getMetadata(true, null, 500); - } catch (RdKafkaException) { - return []; - } - - return [ - $prefix.'orig_broker_id' => $m->getOrigBrokerId(), - $prefix.'orig_broker_name' => $m->getOrigBrokerName(), - $prefix.'brokers' => $m->getBrokers(), - $prefix.'topics' => $m->getTopics(), - ]; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/RedisCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/RedisCaster.php deleted file mode 100644 index 6ff046754..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/RedisCaster.php +++ /dev/null @@ -1,159 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Relay\Relay; -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts Redis class from ext-redis to array representation. - * - * @author Nicolas Grekas - * - * @final - */ -class RedisCaster -{ - private const SERIALIZERS = [ - 0 => 'NONE', // Redis::SERIALIZER_NONE - 1 => 'PHP', // Redis::SERIALIZER_PHP - 2 => 'IGBINARY', // Optional Redis::SERIALIZER_IGBINARY - ]; - - private const MODES = [ - 0 => 'ATOMIC', // Redis::ATOMIC - 1 => 'MULTI', // Redis::MULTI - 2 => 'PIPELINE', // Redis::PIPELINE - ]; - - private const COMPRESSION_MODES = [ - 0 => 'NONE', // Redis::COMPRESSION_NONE - 1 => 'LZF', // Redis::COMPRESSION_LZF - ]; - - private const FAILOVER_OPTIONS = [ - \RedisCluster::FAILOVER_NONE => 'NONE', - \RedisCluster::FAILOVER_ERROR => 'ERROR', - \RedisCluster::FAILOVER_DISTRIBUTE => 'DISTRIBUTE', - \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES => 'DISTRIBUTE_SLAVES', - ]; - - /** - * @return array - */ - public static function castRedis(\Redis|Relay $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - if (!$connected = $c->isConnected()) { - return $a + [ - $prefix.'isConnected' => $connected, - ]; - } - - $mode = $c->getMode(); - - return $a + [ - $prefix.'isConnected' => $connected, - $prefix.'host' => $c->getHost(), - $prefix.'port' => $c->getPort(), - $prefix.'auth' => $c->getAuth(), - $prefix.'mode' => isset(self::MODES[$mode]) ? new ConstStub(self::MODES[$mode], $mode) : $mode, - $prefix.'dbNum' => $c->getDbNum(), - $prefix.'timeout' => $c->getTimeout(), - $prefix.'lastError' => $c->getLastError(), - $prefix.'persistentId' => $c->getPersistentID(), - $prefix.'options' => self::getRedisOptions($c), - ]; - } - - /** - * @return array - */ - public static function castRedisArray(\RedisArray $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - return $a + [ - $prefix.'hosts' => $c->_hosts(), - $prefix.'function' => ClassStub::wrapCallable($c->_function()), - $prefix.'lastError' => $c->getLastError(), - $prefix.'options' => self::getRedisOptions($c), - ]; - } - - /** - * @return array - */ - public static function castRedisCluster(\RedisCluster $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - $failover = $c->getOption(\RedisCluster::OPT_SLAVE_FAILOVER); - - $a += [ - $prefix.'_masters' => $c->_masters(), - $prefix.'_redir' => $c->_redir(), - $prefix.'mode' => new ConstStub($c->getMode() ? 'MULTI' : 'ATOMIC', $c->getMode()), - $prefix.'lastError' => $c->getLastError(), - $prefix.'options' => self::getRedisOptions($c, [ - 'SLAVE_FAILOVER' => isset(self::FAILOVER_OPTIONS[$failover]) ? new ConstStub(self::FAILOVER_OPTIONS[$failover], $failover) : $failover, - ]), - ]; - - return $a; - } - - private static function getRedisOptions(\Redis|Relay|\RedisArray|\RedisCluster $redis, array $options = []): EnumStub - { - $serializer = $redis->getOption(\defined('Redis::OPT_SERIALIZER') ? \Redis::OPT_SERIALIZER : 1); - if (\is_array($serializer)) { - foreach ($serializer as &$v) { - if (isset(self::SERIALIZERS[$v])) { - $v = new ConstStub(self::SERIALIZERS[$v], $v); - } - } - } elseif (isset(self::SERIALIZERS[$serializer])) { - $serializer = new ConstStub(self::SERIALIZERS[$serializer], $serializer); - } - - $compression = \defined('Redis::OPT_COMPRESSION') ? $redis->getOption(\Redis::OPT_COMPRESSION) : 0; - if (\is_array($compression)) { - foreach ($compression as &$v) { - if (isset(self::COMPRESSION_MODES[$v])) { - $v = new ConstStub(self::COMPRESSION_MODES[$v], $v); - } - } - } elseif (isset(self::COMPRESSION_MODES[$compression])) { - $compression = new ConstStub(self::COMPRESSION_MODES[$compression], $compression); - } - - $retry = \defined('Redis::OPT_SCAN') ? $redis->getOption(\Redis::OPT_SCAN) : 0; - if (\is_array($retry)) { - foreach ($retry as &$v) { - $v = new ConstStub($v ? 'RETRY' : 'NORETRY', $v); - } - } else { - $retry = new ConstStub($retry ? 'RETRY' : 'NORETRY', $retry); - } - - $options += [ - 'TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : Relay::OPT_TCP_KEEPALIVE, - 'READ_TIMEOUT' => $redis->getOption(\defined('Redis::OPT_READ_TIMEOUT') ? \Redis::OPT_READ_TIMEOUT : Relay::OPT_READ_TIMEOUT), - 'COMPRESSION' => $compression, - 'SERIALIZER' => $serializer, - 'PREFIX' => $redis->getOption(\defined('Redis::OPT_PREFIX') ? \Redis::OPT_PREFIX : Relay::OPT_PREFIX), - 'SCAN' => $retry, - ]; - - return new EnumStub($options); - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ReflectionCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ReflectionCaster.php deleted file mode 100644 index 4adb9bc9f..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ReflectionCaster.php +++ /dev/null @@ -1,485 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts Reflector related classes to array representation. - * - * @author Nicolas Grekas - * - * @final - */ -class ReflectionCaster -{ - public const UNSET_CLOSURE_FILE_INFO = ['Closure' => __CLASS__.'::unsetClosureFileInfo']; - - private const EXTRA_MAP = [ - 'docComment' => 'getDocComment', - 'extension' => 'getExtensionName', - 'isDisabled' => 'isDisabled', - 'isDeprecated' => 'isDeprecated', - 'isInternal' => 'isInternal', - 'isUserDefined' => 'isUserDefined', - 'isGenerator' => 'isGenerator', - 'isVariadic' => 'isVariadic', - ]; - - /** - * @return array - */ - public static function castClosure(\Closure $c, array $a, Stub $stub, bool $isNested, int $filter = 0) - { - $prefix = Caster::PREFIX_VIRTUAL; - $c = new \ReflectionFunction($c); - - $a = static::castFunctionAbstract($c, $a, $stub, $isNested, $filter); - - if (!str_contains($c->name, '{closure}')) { - $stub->class = isset($a[$prefix.'class']) ? $a[$prefix.'class']->value.'::'.$c->name : $c->name; - unset($a[$prefix.'class']); - } - unset($a[$prefix.'extra']); - - $stub->class .= self::getSignature($a); - - if ($f = $c->getFileName()) { - $stub->attr['file'] = $f; - $stub->attr['line'] = $c->getStartLine(); - } - - unset($a[$prefix.'parameters']); - - if ($filter & Caster::EXCLUDE_VERBOSE) { - $stub->cut += ($c->getFileName() ? 2 : 0) + \count($a); - - return []; - } - - if ($f) { - $a[$prefix.'file'] = new LinkStub($f, $c->getStartLine()); - $a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine(); - } - - return $a; - } - - /** - * @return array - */ - public static function unsetClosureFileInfo(\Closure $c, array $a) - { - unset($a[Caster::PREFIX_VIRTUAL.'file'], $a[Caster::PREFIX_VIRTUAL.'line']); - - return $a; - } - - public static function castGenerator(\Generator $c, array $a, Stub $stub, bool $isNested): array - { - // Cannot create ReflectionGenerator based on a terminated Generator - try { - $reflectionGenerator = new \ReflectionGenerator($c); - } catch (\Exception) { - $a[Caster::PREFIX_VIRTUAL.'closed'] = true; - - return $a; - } - - return self::castReflectionGenerator($reflectionGenerator, $a, $stub, $isNested); - } - - /** - * @return array - */ - public static function castType(\ReflectionType $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - if ($c instanceof \ReflectionNamedType) { - $a += [ - $prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : (string) $c, - $prefix.'allowsNull' => $c->allowsNull(), - $prefix.'isBuiltin' => $c->isBuiltin(), - ]; - } elseif ($c instanceof \ReflectionUnionType || $c instanceof \ReflectionIntersectionType) { - $a[$prefix.'allowsNull'] = $c->allowsNull(); - self::addMap($a, $c, [ - 'types' => 'getTypes', - ]); - } else { - $a[$prefix.'allowsNull'] = $c->allowsNull(); - } - - return $a; - } - - /** - * @return array - */ - public static function castAttribute(\ReflectionAttribute $c, array $a, Stub $stub, bool $isNested) - { - self::addMap($a, $c, [ - 'name' => 'getName', - 'arguments' => 'getArguments', - ]); - - return $a; - } - - /** - * @return array - */ - public static function castReflectionGenerator(\ReflectionGenerator $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - if ($c->getThis()) { - $a[$prefix.'this'] = new CutStub($c->getThis()); - } - $function = $c->getFunction(); - $frame = [ - 'class' => $function->class ?? null, - 'type' => isset($function->class) ? ($function->isStatic() ? '::' : '->') : null, - 'function' => $function->name, - 'file' => $c->getExecutingFile(), - 'line' => $c->getExecutingLine(), - ]; - if ($trace = $c->getTrace(\DEBUG_BACKTRACE_IGNORE_ARGS)) { - $function = new \ReflectionGenerator($c->getExecutingGenerator()); - array_unshift($trace, [ - 'function' => 'yield', - 'file' => $function->getExecutingFile(), - 'line' => $function->getExecutingLine(), - ]); - $trace[] = $frame; - $a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1); - } else { - $function = new FrameStub($frame, false, true); - $function = ExceptionCaster::castFrameStub($function, [], $function, true); - $a[$prefix.'executing'] = $function[$prefix.'src']; - } - - $a[Caster::PREFIX_VIRTUAL.'closed'] = false; - - return $a; - } - - /** - * @return array - */ - public static function castClass(\ReflectionClass $c, array $a, Stub $stub, bool $isNested, int $filter = 0) - { - $prefix = Caster::PREFIX_VIRTUAL; - - if ($n = \Reflection::getModifierNames($c->getModifiers())) { - $a[$prefix.'modifiers'] = implode(' ', $n); - } - - self::addMap($a, $c, [ - 'extends' => 'getParentClass', - 'implements' => 'getInterfaceNames', - 'constants' => 'getReflectionConstants', - ]); - - foreach ($c->getProperties() as $n) { - $a[$prefix.'properties'][$n->name] = $n; - } - - foreach ($c->getMethods() as $n) { - $a[$prefix.'methods'][$n->name] = $n; - } - - self::addAttributes($a, $c, $prefix); - - if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { - self::addExtra($a, $c); - } - - return $a; - } - - /** - * @return array - */ - public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, array $a, Stub $stub, bool $isNested, int $filter = 0) - { - $prefix = Caster::PREFIX_VIRTUAL; - - self::addMap($a, $c, [ - 'returnsReference' => 'returnsReference', - 'returnType' => 'getReturnType', - 'class' => \PHP_VERSION_ID >= 80111 ? 'getClosureCalledClass' : 'getClosureScopeClass', - 'this' => 'getClosureThis', - ]); - - if (isset($a[$prefix.'returnType'])) { - $v = $a[$prefix.'returnType']; - $v = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; - $a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType'] instanceof \ReflectionNamedType && $a[$prefix.'returnType']->allowsNull() && 'mixed' !== $v ? '?'.$v : $v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); - } - if (isset($a[$prefix.'class'])) { - $a[$prefix.'class'] = new ClassStub($a[$prefix.'class']); - } - if (isset($a[$prefix.'this'])) { - $a[$prefix.'this'] = new CutStub($a[$prefix.'this']); - } - - foreach ($c->getParameters() as $v) { - $k = '$'.$v->name; - if ($v->isVariadic()) { - $k = '...'.$k; - } - if ($v->isPassedByReference()) { - $k = '&'.$k; - } - $a[$prefix.'parameters'][$k] = $v; - } - if (isset($a[$prefix.'parameters'])) { - $a[$prefix.'parameters'] = new EnumStub($a[$prefix.'parameters']); - } - - self::addAttributes($a, $c, $prefix); - - if (!($filter & Caster::EXCLUDE_VERBOSE) && $v = $c->getStaticVariables()) { - foreach ($v as $k => &$v) { - if (\is_object($v)) { - $a[$prefix.'use']['$'.$k] = new CutStub($v); - } else { - $a[$prefix.'use']['$'.$k] = &$v; - } - } - unset($v); - $a[$prefix.'use'] = new EnumStub($a[$prefix.'use']); - } - - if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { - self::addExtra($a, $c); - } - - return $a; - } - - /** - * @return array - */ - public static function castClassConstant(\ReflectionClassConstant $c, array $a, Stub $stub, bool $isNested) - { - $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); - $a[Caster::PREFIX_VIRTUAL.'value'] = $c->getValue(); - - self::addAttributes($a, $c); - - return $a; - } - - /** - * @return array - */ - public static function castMethod(\ReflectionMethod $c, array $a, Stub $stub, bool $isNested) - { - $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); - - return $a; - } - - /** - * @return array - */ - public static function castParameter(\ReflectionParameter $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - - self::addMap($a, $c, [ - 'position' => 'getPosition', - 'isVariadic' => 'isVariadic', - 'byReference' => 'isPassedByReference', - 'allowsNull' => 'allowsNull', - ]); - - self::addAttributes($a, $c, $prefix); - - if ($v = $c->getType()) { - $a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; - } - - if (isset($a[$prefix.'typeHint'])) { - $v = $a[$prefix.'typeHint']; - $a[$prefix.'typeHint'] = new ClassStub($v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); - } else { - unset($a[$prefix.'allowsNull']); - } - - if ($c->isOptional()) { - try { - $a[$prefix.'default'] = $v = $c->getDefaultValue(); - if ($c->isDefaultValueConstant() && !\is_object($v)) { - $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v); - } - if (null === $v) { - unset($a[$prefix.'allowsNull']); - } - } catch (\ReflectionException) { - } - } - - return $a; - } - - /** - * @return array - */ - public static function castProperty(\ReflectionProperty $c, array $a, Stub $stub, bool $isNested) - { - $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); - - self::addAttributes($a, $c); - self::addExtra($a, $c); - - return $a; - } - - /** - * @return array - */ - public static function castReference(\ReflectionReference $c, array $a, Stub $stub, bool $isNested) - { - $a[Caster::PREFIX_VIRTUAL.'id'] = $c->getId(); - - return $a; - } - - /** - * @return array - */ - public static function castExtension(\ReflectionExtension $c, array $a, Stub $stub, bool $isNested) - { - self::addMap($a, $c, [ - 'version' => 'getVersion', - 'dependencies' => 'getDependencies', - 'iniEntries' => 'getIniEntries', - 'isPersistent' => 'isPersistent', - 'isTemporary' => 'isTemporary', - 'constants' => 'getConstants', - 'functions' => 'getFunctions', - 'classes' => 'getClasses', - ]); - - return $a; - } - - /** - * @return array - */ - public static function castZendExtension(\ReflectionZendExtension $c, array $a, Stub $stub, bool $isNested) - { - self::addMap($a, $c, [ - 'version' => 'getVersion', - 'author' => 'getAuthor', - 'copyright' => 'getCopyright', - 'url' => 'getURL', - ]); - - return $a; - } - - /** - * @return string - */ - public static function getSignature(array $a) - { - $prefix = Caster::PREFIX_VIRTUAL; - $signature = ''; - - if (isset($a[$prefix.'parameters'])) { - foreach ($a[$prefix.'parameters']->value as $k => $param) { - $signature .= ', '; - if ($type = $param->getType()) { - if (!$type instanceof \ReflectionNamedType) { - $signature .= $type.' '; - } else { - if (!$param->isOptional() && $param->allowsNull() && 'mixed' !== $type->getName()) { - $signature .= '?'; - } - $signature .= substr(strrchr('\\'.$type->getName(), '\\'), 1).' '; - } - } - $signature .= $k; - - if (!$param->isDefaultValueAvailable()) { - continue; - } - $v = $param->getDefaultValue(); - $signature .= ' = '; - - if ($param->isDefaultValueConstant()) { - $signature .= substr(strrchr('\\'.$param->getDefaultValueConstantName(), '\\'), 1); - } elseif (null === $v) { - $signature .= 'null'; - } elseif (\is_array($v)) { - $signature .= $v ? '[…'.\count($v).']' : '[]'; - } elseif (\is_string($v)) { - $signature .= 10 > \strlen($v) && !str_contains($v, '\\') ? "'{$v}'" : "'…".\strlen($v)."'"; - } elseif (\is_bool($v)) { - $signature .= $v ? 'true' : 'false'; - } elseif (\is_object($v)) { - $signature .= 'new '.substr(strrchr('\\'.get_debug_type($v), '\\'), 1); - } else { - $signature .= $v; - } - } - } - $signature = (empty($a[$prefix.'returnsReference']) ? '' : '&').'('.substr($signature, 2).')'; - - if (isset($a[$prefix.'returnType'])) { - $signature .= ': '.substr(strrchr('\\'.$a[$prefix.'returnType'], '\\'), 1); - } - - return $signature; - } - - private static function addExtra(array &$a, \Reflector $c): void - { - $x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : []; - - if (method_exists($c, 'getFileName') && $m = $c->getFileName()) { - $x['file'] = new LinkStub($m, $c->getStartLine()); - $x['line'] = $c->getStartLine().' to '.$c->getEndLine(); - } - - self::addMap($x, $c, self::EXTRA_MAP, ''); - - if ($x) { - $a[Caster::PREFIX_VIRTUAL.'extra'] = new EnumStub($x); - } - } - - private static function addMap(array &$a, object $c, array $map, string $prefix = Caster::PREFIX_VIRTUAL): void - { - foreach ($map as $k => $m) { - if ('isDisabled' === $k) { - continue; - } - - if (method_exists($c, $m) && false !== ($m = $c->$m()) && null !== $m) { - $a[$prefix.$k] = $m instanceof \Reflector ? $m->name : $m; - } - } - } - - private static function addAttributes(array &$a, \Reflector $c, string $prefix = Caster::PREFIX_VIRTUAL): void - { - foreach ($c->getAttributes() as $n) { - $a[$prefix.'attributes'][] = $n; - } - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ResourceCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ResourceCaster.php deleted file mode 100644 index f3bbf3be4..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ResourceCaster.php +++ /dev/null @@ -1,106 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts common resource types to array representation. - * - * @author Nicolas Grekas - * - * @final - */ -class ResourceCaster -{ - public static function castCurl(\CurlHandle $h, array $a, Stub $stub, bool $isNested): array - { - return curl_getinfo($h); - } - - /** - * @return array - */ - public static function castDba($dba, array $a, Stub $stub, bool $isNested) - { - $list = dba_list(); - $a['file'] = $list[(int) $dba]; - - return $a; - } - - /** - * @return array - */ - public static function castProcess($process, array $a, Stub $stub, bool $isNested) - { - return proc_get_status($process); - } - - public static function castStream($stream, array $a, Stub $stub, bool $isNested): array - { - $a = stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested); - if ($a['uri'] ?? false) { - $a['uri'] = new LinkStub($a['uri']); - } - - return $a; - } - - /** - * @return array - */ - public static function castStreamContext($stream, array $a, Stub $stub, bool $isNested) - { - return @stream_context_get_params($stream) ?: $a; - } - - /** - * @return array - */ - public static function castGd($gd, array $a, Stub $stub, bool $isNested) - { - $a['size'] = imagesx($gd).'x'.imagesy($gd); - $a['trueColor'] = imageistruecolor($gd); - - return $a; - } - - /** - * @return array - */ - public static function castOpensslX509($h, array $a, Stub $stub, bool $isNested) - { - $stub->cut = -1; - $info = openssl_x509_parse($h, false); - - $pin = openssl_pkey_get_public($h); - $pin = openssl_pkey_get_details($pin)['key']; - $pin = \array_slice(explode("\n", $pin), 1, -2); - $pin = base64_decode(implode('', $pin)); - $pin = base64_encode(hash('sha256', $pin, true)); - - $a += [ - 'subject' => new EnumStub(array_intersect_key($info['subject'], ['organizationName' => true, 'commonName' => true])), - 'issuer' => new EnumStub(array_intersect_key($info['issuer'], ['organizationName' => true, 'commonName' => true])), - 'expiry' => new ConstStub(date(\DateTimeInterface::ISO8601, $info['validTo_time_t']), $info['validTo_time_t']), - 'fingerprint' => new EnumStub([ - 'md5' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'md5')), 2, ':', true)), - 'sha1' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha1')), 2, ':', true)), - 'sha256' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha256')), 2, ':', true)), - 'pin-sha256' => new ConstStub($pin), - ]), - ]; - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ScalarStub.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ScalarStub.php deleted file mode 100644 index 3bb1935b8..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/ScalarStub.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Represents any arbitrary value. - * - * @author Alexandre Daubois - */ -class ScalarStub extends Stub -{ - public function __construct(mixed $value) - { - $this->value = $value; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/SplCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/SplCaster.php deleted file mode 100644 index 814d824d1..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/SplCaster.php +++ /dev/null @@ -1,286 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts SPL related classes to array representation. - * - * @author Nicolas Grekas - * - * @final - */ -class SplCaster -{ - private const SPL_FILE_OBJECT_FLAGS = [ - \SplFileObject::DROP_NEW_LINE => 'DROP_NEW_LINE', - \SplFileObject::READ_AHEAD => 'READ_AHEAD', - \SplFileObject::SKIP_EMPTY => 'SKIP_EMPTY', - \SplFileObject::READ_CSV => 'READ_CSV', - ]; - - /** - * @return array - */ - public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, bool $isNested) - { - return self::castSplArray($c, $a, $stub, $isNested); - } - - /** - * @return array - */ - public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, bool $isNested) - { - return self::castSplArray($c, $a, $stub, $isNested); - } - - /** - * @return array - */ - public static function castHeap(\Iterator $c, array $a, Stub $stub, bool $isNested) - { - $a += [ - Caster::PREFIX_VIRTUAL.'heap' => iterator_to_array(clone $c), - ]; - - return $a; - } - - /** - * @return array - */ - public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a, Stub $stub, bool $isNested) - { - $prefix = Caster::PREFIX_VIRTUAL; - $mode = $c->getIteratorMode(); - $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE); - - $a += [ - $prefix.'mode' => new ConstStub((($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_DELETE) ? 'IT_MODE_DELETE' : 'IT_MODE_KEEP'), $mode), - $prefix.'dllist' => iterator_to_array($c), - ]; - $c->setIteratorMode($mode); - - return $a; - } - - /** - * @return array - */ - public static function castFileInfo(\SplFileInfo $c, array $a, Stub $stub, bool $isNested) - { - static $map = [ - 'path' => 'getPath', - 'filename' => 'getFilename', - 'basename' => 'getBasename', - 'pathname' => 'getPathname', - 'extension' => 'getExtension', - 'realPath' => 'getRealPath', - 'aTime' => 'getATime', - 'mTime' => 'getMTime', - 'cTime' => 'getCTime', - 'inode' => 'getInode', - 'size' => 'getSize', - 'perms' => 'getPerms', - 'owner' => 'getOwner', - 'group' => 'getGroup', - 'type' => 'getType', - 'writable' => 'isWritable', - 'readable' => 'isReadable', - 'executable' => 'isExecutable', - 'file' => 'isFile', - 'dir' => 'isDir', - 'link' => 'isLink', - 'linkTarget' => 'getLinkTarget', - ]; - - $prefix = Caster::PREFIX_VIRTUAL; - unset($a["\0SplFileInfo\0fileName"]); - unset($a["\0SplFileInfo\0pathName"]); - - try { - $c->isReadable(); - } catch (\RuntimeException $e) { - if ('Object not initialized' !== $e->getMessage()) { - throw $e; - } - - $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; - - return $a; - } catch (\Error $e) { - if ('Object not initialized' !== $e->getMessage()) { - throw $e; - } - - $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; - - return $a; - } - - foreach ($map as $key => $accessor) { - try { - $a[$prefix.$key] = $c->$accessor(); - } catch (\Exception) { - } - } - - if ($a[$prefix.'realPath'] ?? false) { - $a[$prefix.'realPath'] = new LinkStub($a[$prefix.'realPath']); - } - - if (isset($a[$prefix.'perms'])) { - $a[$prefix.'perms'] = new ConstStub(sprintf('0%o', $a[$prefix.'perms']), $a[$prefix.'perms']); - } - - static $mapDate = ['aTime', 'mTime', 'cTime']; - foreach ($mapDate as $key) { - if (isset($a[$prefix.$key])) { - $a[$prefix.$key] = new ConstStub(date('Y-m-d H:i:s', $a[$prefix.$key]), $a[$prefix.$key]); - } - } - - return $a; - } - - /** - * @return array - */ - public static function castFileObject(\SplFileObject $c, array $a, Stub $stub, bool $isNested) - { - static $map = [ - 'csvControl' => 'getCsvControl', - 'flags' => 'getFlags', - 'maxLineLen' => 'getMaxLineLen', - 'fstat' => 'fstat', - 'eof' => 'eof', - 'key' => 'key', - ]; - - $prefix = Caster::PREFIX_VIRTUAL; - - foreach ($map as $key => $accessor) { - try { - $a[$prefix.$key] = $c->$accessor(); - } catch (\Exception) { - } - } - - if (isset($a[$prefix.'flags'])) { - $flagsArray = []; - foreach (self::SPL_FILE_OBJECT_FLAGS as $value => $name) { - if ($a[$prefix.'flags'] & $value) { - $flagsArray[] = $name; - } - } - $a[$prefix.'flags'] = new ConstStub(implode('|', $flagsArray), $a[$prefix.'flags']); - } - - if (isset($a[$prefix.'fstat'])) { - $a[$prefix.'fstat'] = new CutArrayStub($a[$prefix.'fstat'], ['dev', 'ino', 'nlink', 'rdev', 'blksize', 'blocks']); - } - - return $a; - } - - /** - * @return array - */ - public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, bool $isNested) - { - $storage = []; - unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967 - unset($a["\0SplObjectStorage\0storage"]); - - $clone = clone $c; - foreach ($clone as $obj) { - $storage[] = new EnumStub([ - 'object' => $obj, - 'info' => $clone->getInfo(), - ]); - } - - $a += [ - Caster::PREFIX_VIRTUAL.'storage' => $storage, - ]; - - return $a; - } - - /** - * @return array - */ - public static function castOuterIterator(\OuterIterator $c, array $a, Stub $stub, bool $isNested) - { - $a[Caster::PREFIX_VIRTUAL.'innerIterator'] = $c->getInnerIterator(); - - return $a; - } - - /** - * @return array - */ - public static function castWeakReference(\WeakReference $c, array $a, Stub $stub, bool $isNested) - { - $a[Caster::PREFIX_VIRTUAL.'object'] = $c->get(); - - return $a; - } - - /** - * @return array - */ - public static function castWeakMap(\WeakMap $c, array $a, Stub $stub, bool $isNested) - { - $map = []; - - foreach (clone $c as $obj => $data) { - $map[] = new EnumStub([ - 'object' => $obj, - 'data' => $data, - ]); - } - - $a += [ - Caster::PREFIX_VIRTUAL.'map' => $map, - ]; - - return $a; - } - - private static function castSplArray(\ArrayObject|\ArrayIterator $c, array $a, Stub $stub, bool $isNested): array - { - $prefix = Caster::PREFIX_VIRTUAL; - $flags = $c->getFlags(); - - if (!($flags & \ArrayObject::STD_PROP_LIST)) { - $c->setFlags(\ArrayObject::STD_PROP_LIST); - $a = Caster::castObject($c, $c::class, method_exists($c, '__debugInfo'), $stub->class); - $c->setFlags($flags); - } - - unset($a["\0ArrayObject\0storage"], $a["\0ArrayIterator\0storage"]); - - $a += [ - $prefix.'storage' => $c->getArrayCopy(), - $prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST), - $prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS), - ]; - if ($c instanceof \ArrayObject) { - $a[$prefix.'iteratorClass'] = new ClassStub($c->getIteratorClass()); - } - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/StubCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/StubCaster.php deleted file mode 100644 index 4b93ff76f..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/StubCaster.php +++ /dev/null @@ -1,107 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts a caster's Stub. - * - * @author Nicolas Grekas - * - * @final - */ -class StubCaster -{ - /** - * @return array - */ - public static function castStub(Stub $c, array $a, Stub $stub, bool $isNested) - { - if ($isNested) { - $stub->type = $c->type; - $stub->class = $c->class; - $stub->value = $c->value; - $stub->handle = $c->handle; - $stub->cut = $c->cut; - $stub->attr = $c->attr; - - if (Stub::TYPE_REF === $c->type && !$c->class && \is_string($c->value) && !preg_match('//u', $c->value)) { - $stub->type = Stub::TYPE_STRING; - $stub->class = Stub::STRING_BINARY; - } - - $a = []; - } - - return $a; - } - - /** - * @return array - */ - public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, bool $isNested) - { - return $isNested ? $c->preservedSubset : $a; - } - - /** - * @return array - */ - public static function cutInternals($obj, array $a, Stub $stub, bool $isNested) - { - if ($isNested) { - $stub->cut += \count($a); - - return []; - } - - return $a; - } - - /** - * @return array - */ - public static function castEnum(EnumStub $c, array $a, Stub $stub, bool $isNested) - { - if ($isNested) { - $stub->class = $c->dumpKeys ? '' : null; - $stub->handle = 0; - $stub->value = null; - $stub->cut = $c->cut; - $stub->attr = $c->attr; - - $a = []; - - if ($c->value) { - foreach (array_keys($c->value) as $k) { - $keys[] = !isset($k[0]) || "\0" !== $k[0] ? Caster::PREFIX_VIRTUAL.$k : $k; - } - // Preserve references with array_combine() - $a = array_combine($keys, $c->value); - } - } - - return $a; - } - - /** - * @return array - */ - public static function castScalar(ScalarStub $scalarStub, array $a, Stub $stub) - { - $stub->type = Stub::TYPE_SCALAR; - $stub->attr['value'] = $scalarStub->value; - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/SymfonyCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/SymfonyCaster.php deleted file mode 100644 index ebc00f90e..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/SymfonyCaster.php +++ /dev/null @@ -1,139 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Uid\Ulid; -use Symfony\Component\Uid\Uuid; -use Symfony\Component\VarDumper\Cloner\Stub; -use Symfony\Component\VarExporter\Internal\LazyObjectState; - -/** - * @final - */ -class SymfonyCaster -{ - private const REQUEST_GETTERS = [ - 'pathInfo' => 'getPathInfo', - 'requestUri' => 'getRequestUri', - 'baseUrl' => 'getBaseUrl', - 'basePath' => 'getBasePath', - 'method' => 'getMethod', - 'format' => 'getRequestFormat', - ]; - - /** - * @return array - */ - public static function castRequest(Request $request, array $a, Stub $stub, bool $isNested) - { - $clone = null; - - foreach (self::REQUEST_GETTERS as $prop => $getter) { - $key = Caster::PREFIX_PROTECTED.$prop; - if (\array_key_exists($key, $a) && null === $a[$key]) { - $clone ??= clone $request; - $a[Caster::PREFIX_VIRTUAL.$prop] = $clone->{$getter}(); - } - } - - return $a; - } - - /** - * @return array - */ - public static function castHttpClient($client, array $a, Stub $stub, bool $isNested) - { - $multiKey = sprintf("\0%s\0multi", $client::class); - if (isset($a[$multiKey])) { - $a[$multiKey] = new CutStub($a[$multiKey]); - } - - return $a; - } - - /** - * @return array - */ - public static function castHttpClientResponse($response, array $a, Stub $stub, bool $isNested) - { - $stub->cut += \count($a); - $a = []; - - foreach ($response->getInfo() as $k => $v) { - $a[Caster::PREFIX_VIRTUAL.$k] = $v; - } - - return $a; - } - - /** - * @return array - */ - public static function castLazyObjectState($state, array $a, Stub $stub, bool $isNested) - { - if (!$isNested) { - return $a; - } - - $stub->cut += \count($a) - 1; - - $instance = $a['realInstance'] ?? null; - - $a = ['status' => new ConstStub(match ($a['status']) { - LazyObjectState::STATUS_INITIALIZED_FULL => 'INITIALIZED_FULL', - LazyObjectState::STATUS_INITIALIZED_PARTIAL => 'INITIALIZED_PARTIAL', - LazyObjectState::STATUS_UNINITIALIZED_FULL => 'UNINITIALIZED_FULL', - LazyObjectState::STATUS_UNINITIALIZED_PARTIAL => 'UNINITIALIZED_PARTIAL', - }, $a['status'])]; - - if ($instance) { - $a['realInstance'] = $instance; - --$stub->cut; - } - - return $a; - } - - /** - * @return array - */ - public static function castUuid(Uuid $uuid, array $a, Stub $stub, bool $isNested) - { - $a[Caster::PREFIX_VIRTUAL.'toBase58'] = $uuid->toBase58(); - $a[Caster::PREFIX_VIRTUAL.'toBase32'] = $uuid->toBase32(); - - // symfony/uid >= 5.3 - if (method_exists($uuid, 'getDateTime')) { - $a[Caster::PREFIX_VIRTUAL.'time'] = $uuid->getDateTime()->format('Y-m-d H:i:s.u \U\T\C'); - } - - return $a; - } - - /** - * @return array - */ - public static function castUlid(Ulid $ulid, array $a, Stub $stub, bool $isNested) - { - $a[Caster::PREFIX_VIRTUAL.'toBase58'] = $ulid->toBase58(); - $a[Caster::PREFIX_VIRTUAL.'toRfc4122'] = $ulid->toRfc4122(); - - // symfony/uid >= 5.3 - if (method_exists($ulid, 'getDateTime')) { - $a[Caster::PREFIX_VIRTUAL.'time'] = $ulid->getDateTime()->format('Y-m-d H:i:s.v \U\T\C'); - } - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/TraceStub.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/TraceStub.php deleted file mode 100644 index d215d8db0..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/TraceStub.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Represents a backtrace as returned by debug_backtrace() or Exception->getTrace(). - * - * @author Nicolas Grekas - */ -class TraceStub extends Stub -{ - public $keepArgs; - public $sliceOffset; - public $sliceLength; - public $numberingOffset; - - public function __construct(array $trace, bool $keepArgs = true, int $sliceOffset = 0, ?int $sliceLength = null, int $numberingOffset = 0) - { - $this->value = $trace; - $this->keepArgs = $keepArgs; - $this->sliceOffset = $sliceOffset; - $this->sliceLength = $sliceLength; - $this->numberingOffset = $numberingOffset; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/UninitializedStub.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/UninitializedStub.php deleted file mode 100644 index a9bdd9b81..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/UninitializedStub.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -/** - * Represents an uninitialized property. - * - * @author Nicolas Grekas - */ -class UninitializedStub extends ConstStub -{ - public function __construct(\ReflectionProperty $property) - { - parent::__construct('?'.($property->hasType() ? ' '.$property->getType() : ''), 'Uninitialized property'); - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/UuidCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/UuidCaster.php deleted file mode 100644 index b10277457..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/UuidCaster.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Ramsey\Uuid\UuidInterface; -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * @author Grégoire Pineau - */ -final class UuidCaster -{ - public static function castRamseyUuid(UuidInterface $c, array $a, Stub $stub, bool $isNested): array - { - $a += [ - Caster::PREFIX_VIRTUAL.'uuid' => (string) $c, - ]; - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php deleted file mode 100644 index 1cfcf4dd8..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php +++ /dev/null @@ -1,95 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts XmlReader class to array representation. - * - * @author Baptiste Clavié - * - * @final - */ -class XmlReaderCaster -{ - private const NODE_TYPES = [ - \XMLReader::NONE => 'NONE', - \XMLReader::ELEMENT => 'ELEMENT', - \XMLReader::ATTRIBUTE => 'ATTRIBUTE', - \XMLReader::TEXT => 'TEXT', - \XMLReader::CDATA => 'CDATA', - \XMLReader::ENTITY_REF => 'ENTITY_REF', - \XMLReader::ENTITY => 'ENTITY', - \XMLReader::PI => 'PI (Processing Instruction)', - \XMLReader::COMMENT => 'COMMENT', - \XMLReader::DOC => 'DOC', - \XMLReader::DOC_TYPE => 'DOC_TYPE', - \XMLReader::DOC_FRAGMENT => 'DOC_FRAGMENT', - \XMLReader::NOTATION => 'NOTATION', - \XMLReader::WHITESPACE => 'WHITESPACE', - \XMLReader::SIGNIFICANT_WHITESPACE => 'SIGNIFICANT_WHITESPACE', - \XMLReader::END_ELEMENT => 'END_ELEMENT', - \XMLReader::END_ENTITY => 'END_ENTITY', - \XMLReader::XML_DECLARATION => 'XML_DECLARATION', - ]; - - /** - * @return array - */ - public static function castXmlReader(\XMLReader $reader, array $a, Stub $stub, bool $isNested) - { - try { - $properties = [ - 'LOADDTD' => @$reader->getParserProperty(\XMLReader::LOADDTD), - 'DEFAULTATTRS' => @$reader->getParserProperty(\XMLReader::DEFAULTATTRS), - 'VALIDATE' => @$reader->getParserProperty(\XMLReader::VALIDATE), - 'SUBST_ENTITIES' => @$reader->getParserProperty(\XMLReader::SUBST_ENTITIES), - ]; - } catch (\Error) { - $properties = [ - 'LOADDTD' => false, - 'DEFAULTATTRS' => false, - 'VALIDATE' => false, - 'SUBST_ENTITIES' => false, - ]; - } - - $props = Caster::PREFIX_VIRTUAL.'parserProperties'; - $info = [ - 'localName' => $reader->localName, - 'prefix' => $reader->prefix, - 'nodeType' => new ConstStub(self::NODE_TYPES[$reader->nodeType], $reader->nodeType), - 'depth' => $reader->depth, - 'isDefault' => $reader->isDefault, - 'isEmptyElement' => \XMLReader::NONE === $reader->nodeType ? null : $reader->isEmptyElement, - 'xmlLang' => $reader->xmlLang, - 'attributeCount' => $reader->attributeCount, - 'value' => $reader->value, - 'namespaceURI' => $reader->namespaceURI, - 'baseURI' => $reader->baseURI ? new LinkStub($reader->baseURI) : $reader->baseURI, - $props => $properties, - ]; - - if ($info[$props] = Caster::filter($info[$props], Caster::EXCLUDE_EMPTY, [], $count)) { - $info[$props] = new EnumStub($info[$props]); - $info[$props]->cut = $count; - } - - $a = Caster::filter($a, Caster::EXCLUDE_UNINITIALIZED, [], $count); - $info = Caster::filter($info, Caster::EXCLUDE_EMPTY, [], $count); - // +2 because hasValue and hasAttributes are always filtered - $stub->cut += $count + 2; - - return $a + $info; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php b/data/web/inc/lib/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php deleted file mode 100644 index 0cf42584a..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * Casts XML resources to array representation. - * - * @author Nicolas Grekas - * - * @final - */ -class XmlResourceCaster -{ - private const XML_ERRORS = [ - \XML_ERROR_NONE => 'XML_ERROR_NONE', - \XML_ERROR_NO_MEMORY => 'XML_ERROR_NO_MEMORY', - \XML_ERROR_SYNTAX => 'XML_ERROR_SYNTAX', - \XML_ERROR_NO_ELEMENTS => 'XML_ERROR_NO_ELEMENTS', - \XML_ERROR_INVALID_TOKEN => 'XML_ERROR_INVALID_TOKEN', - \XML_ERROR_UNCLOSED_TOKEN => 'XML_ERROR_UNCLOSED_TOKEN', - \XML_ERROR_PARTIAL_CHAR => 'XML_ERROR_PARTIAL_CHAR', - \XML_ERROR_TAG_MISMATCH => 'XML_ERROR_TAG_MISMATCH', - \XML_ERROR_DUPLICATE_ATTRIBUTE => 'XML_ERROR_DUPLICATE_ATTRIBUTE', - \XML_ERROR_JUNK_AFTER_DOC_ELEMENT => 'XML_ERROR_JUNK_AFTER_DOC_ELEMENT', - \XML_ERROR_PARAM_ENTITY_REF => 'XML_ERROR_PARAM_ENTITY_REF', - \XML_ERROR_UNDEFINED_ENTITY => 'XML_ERROR_UNDEFINED_ENTITY', - \XML_ERROR_RECURSIVE_ENTITY_REF => 'XML_ERROR_RECURSIVE_ENTITY_REF', - \XML_ERROR_ASYNC_ENTITY => 'XML_ERROR_ASYNC_ENTITY', - \XML_ERROR_BAD_CHAR_REF => 'XML_ERROR_BAD_CHAR_REF', - \XML_ERROR_BINARY_ENTITY_REF => 'XML_ERROR_BINARY_ENTITY_REF', - \XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF => 'XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF', - \XML_ERROR_MISPLACED_XML_PI => 'XML_ERROR_MISPLACED_XML_PI', - \XML_ERROR_UNKNOWN_ENCODING => 'XML_ERROR_UNKNOWN_ENCODING', - \XML_ERROR_INCORRECT_ENCODING => 'XML_ERROR_INCORRECT_ENCODING', - \XML_ERROR_UNCLOSED_CDATA_SECTION => 'XML_ERROR_UNCLOSED_CDATA_SECTION', - \XML_ERROR_EXTERNAL_ENTITY_HANDLING => 'XML_ERROR_EXTERNAL_ENTITY_HANDLING', - ]; - - /** - * @return array - */ - public static function castXml($h, array $a, Stub $stub, bool $isNested) - { - $a['current_byte_index'] = xml_get_current_byte_index($h); - $a['current_column_number'] = xml_get_current_column_number($h); - $a['current_line_number'] = xml_get_current_line_number($h); - $a['error_code'] = xml_get_error_code($h); - - if (isset(self::XML_ERRORS[$a['error_code']])) { - $a['error_code'] = new ConstStub(self::XML_ERRORS[$a['error_code']], $a['error_code']); - } - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/AbstractCloner.php b/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/AbstractCloner.php deleted file mode 100644 index fc330bae2..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/AbstractCloner.php +++ /dev/null @@ -1,397 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Cloner; - -use Symfony\Component\VarDumper\Caster\Caster; -use Symfony\Component\VarDumper\Exception\ThrowingCasterException; - -/** - * AbstractCloner implements a generic caster mechanism for objects and resources. - * - * @author Nicolas Grekas - */ -abstract class AbstractCloner implements ClonerInterface -{ - public static $defaultCasters = [ - '__PHP_Incomplete_Class' => ['Symfony\Component\VarDumper\Caster\Caster', 'castPhpIncompleteClass'], - - 'Symfony\Component\VarDumper\Caster\CutStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], - 'Symfony\Component\VarDumper\Caster\CutArrayStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'], - 'Symfony\Component\VarDumper\Caster\ConstStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], - 'Symfony\Component\VarDumper\Caster\EnumStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'], - 'Symfony\Component\VarDumper\Caster\ScalarStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castScalar'], - - 'Fiber' => ['Symfony\Component\VarDumper\Caster\FiberCaster', 'castFiber'], - - 'Closure' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClosure'], - 'Generator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castGenerator'], - 'ReflectionType' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castType'], - 'ReflectionAttribute' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castAttribute'], - 'ReflectionGenerator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReflectionGenerator'], - 'ReflectionClass' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClass'], - 'ReflectionClassConstant' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClassConstant'], - 'ReflectionFunctionAbstract' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castFunctionAbstract'], - 'ReflectionMethod' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castMethod'], - 'ReflectionParameter' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castParameter'], - 'ReflectionProperty' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castProperty'], - 'ReflectionReference' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReference'], - 'ReflectionExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castExtension'], - 'ReflectionZendExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castZendExtension'], - - 'Doctrine\Common\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], - 'Doctrine\Common\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castCommonProxy'], - 'Doctrine\ORM\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castOrmProxy'], - 'Doctrine\ORM\PersistentCollection' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castPersistentCollection'], - 'Doctrine\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], - - 'DOMException' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'], - 'DOMStringList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], - 'DOMNameList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], - 'DOMImplementation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'], - 'DOMImplementationList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], - 'DOMNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'], - 'DOMNameSpaceNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNameSpaceNode'], - 'DOMDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocument'], - 'DOMNodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], - 'DOMNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], - 'DOMCharacterData' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'], - 'DOMAttr' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'], - 'DOMElement' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'], - 'DOMText' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'], - 'DOMDocumentType' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'], - 'DOMNotation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'], - 'DOMEntity' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'], - 'DOMProcessingInstruction' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'], - 'DOMXPath' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castXPath'], - - 'XMLReader' => ['Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'], - - 'ErrorException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castErrorException'], - 'Exception' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castException'], - 'Error' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castError'], - 'Symfony\Bridge\Monolog\Logger' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], - 'Symfony\Component\DependencyInjection\ContainerInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], - 'Symfony\Component\EventDispatcher\EventDispatcherInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], - 'Symfony\Component\HttpClient\AmpHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], - 'Symfony\Component\HttpClient\CurlHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], - 'Symfony\Component\HttpClient\NativeHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], - 'Symfony\Component\HttpClient\Response\AmpResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], - 'Symfony\Component\HttpClient\Response\CurlResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], - 'Symfony\Component\HttpClient\Response\NativeResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], - 'Symfony\Component\HttpFoundation\Request' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'], - 'Symfony\Component\Uid\Ulid' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castUlid'], - 'Symfony\Component\Uid\Uuid' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castUuid'], - 'Symfony\Component\VarExporter\Internal\LazyObjectState' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castLazyObjectState'], - 'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'], - 'Symfony\Component\VarDumper\Caster\TraceStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'], - 'Symfony\Component\VarDumper\Caster\FrameStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'], - 'Symfony\Component\VarDumper\Cloner\AbstractCloner' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], - 'Symfony\Component\ErrorHandler\Exception\FlattenException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFlattenException'], - 'Symfony\Component\ErrorHandler\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'], - - 'Imagine\Image\ImageInterface' => ['Symfony\Component\VarDumper\Caster\ImagineCaster', 'castImage'], - - 'Ramsey\Uuid\UuidInterface' => ['Symfony\Component\VarDumper\Caster\UuidCaster', 'castRamseyUuid'], - - 'ProxyManager\Proxy\ProxyInterface' => ['Symfony\Component\VarDumper\Caster\ProxyManagerCaster', 'castProxy'], - 'PHPUnit_Framework_MockObject_MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], - 'PHPUnit\Framework\MockObject\MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], - 'PHPUnit\Framework\MockObject\Stub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], - 'Prophecy\Prophecy\ProphecySubjectInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], - 'Mockery\MockInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], - - 'PDO' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdo'], - 'PDOStatement' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdoStatement'], - - 'AMQPConnection' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castConnection'], - 'AMQPChannel' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castChannel'], - 'AMQPQueue' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castQueue'], - 'AMQPExchange' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castExchange'], - 'AMQPEnvelope' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castEnvelope'], - - 'ArrayObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayObject'], - 'ArrayIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayIterator'], - 'SplDoublyLinkedList' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castDoublyLinkedList'], - 'SplFileInfo' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileInfo'], - 'SplFileObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileObject'], - 'SplHeap' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], - 'SplObjectStorage' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castObjectStorage'], - 'SplPriorityQueue' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], - 'OuterIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castOuterIterator'], - 'WeakMap' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakMap'], - 'WeakReference' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakReference'], - - 'Redis' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], - 'Relay\Relay' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], - 'RedisArray' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'], - 'RedisCluster' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisCluster'], - - 'DateTimeInterface' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castDateTime'], - 'DateInterval' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castInterval'], - 'DateTimeZone' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castTimeZone'], - 'DatePeriod' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castPeriod'], - - 'GMP' => ['Symfony\Component\VarDumper\Caster\GmpCaster', 'castGmp'], - - 'MessageFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castMessageFormatter'], - 'NumberFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castNumberFormatter'], - 'IntlTimeZone' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlTimeZone'], - 'IntlCalendar' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlCalendar'], - 'IntlDateFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlDateFormatter'], - - 'Memcached' => ['Symfony\Component\VarDumper\Caster\MemcachedCaster', 'castMemcached'], - - 'Ds\Collection' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castCollection'], - 'Ds\Map' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castMap'], - 'Ds\Pair' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPair'], - 'Symfony\Component\VarDumper\Caster\DsPairStub' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPairStub'], - - 'mysqli_driver' => ['Symfony\Component\VarDumper\Caster\MysqliCaster', 'castMysqliDriver'], - - 'CurlHandle' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], - - ':dba' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], - ':dba persistent' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], - - 'GdImage' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], - ':gd' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], - - ':pgsql large object' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'], - ':pgsql link' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], - ':pgsql link persistent' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], - ':pgsql result' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'], - ':process' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'], - ':stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], - - 'OpenSSLCertificate' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], - ':OpenSSL X.509' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], - - ':persistent stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], - ':stream-context' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'], - - 'XmlParser' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], - ':xml' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], - - 'RdKafka' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castRdKafka'], - 'RdKafka\Conf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castConf'], - 'RdKafka\KafkaConsumer' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castKafkaConsumer'], - 'RdKafka\Metadata\Broker' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castBrokerMetadata'], - 'RdKafka\Metadata\Collection' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castCollectionMetadata'], - 'RdKafka\Metadata\Partition' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castPartitionMetadata'], - 'RdKafka\Metadata\Topic' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicMetadata'], - 'RdKafka\Message' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castMessage'], - 'RdKafka\Topic' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopic'], - 'RdKafka\TopicPartition' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicPartition'], - 'RdKafka\TopicConf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicConf'], - - 'FFI\CData' => ['Symfony\Component\VarDumper\Caster\FFICaster', 'castCTypeOrCData'], - 'FFI\CType' => ['Symfony\Component\VarDumper\Caster\FFICaster', 'castCTypeOrCData'], - ]; - - protected $maxItems = 2500; - protected $maxString = -1; - protected $minDepth = 1; - - /** - * @var array> - */ - private array $casters = []; - - /** - * @var callable|null - */ - private $prevErrorHandler; - - private array $classInfo = []; - private int $filter = 0; - - /** - * @param callable[]|null $casters A map of casters - * - * @see addCasters - */ - public function __construct(?array $casters = null) - { - $this->addCasters($casters ?? static::$defaultCasters); - } - - /** - * Adds casters for resources and objects. - * - * Maps resources or objects types to a callback. - * Types are in the key, with a callable caster for value. - * Resource types are to be prefixed with a `:`, - * see e.g. static::$defaultCasters. - * - * @param callable[] $casters A map of casters - * - * @return void - */ - public function addCasters(array $casters) - { - foreach ($casters as $type => $callback) { - $this->casters[$type][] = $callback; - } - } - - /** - * Sets the maximum number of items to clone past the minimum depth in nested structures. - * - * @return void - */ - public function setMaxItems(int $maxItems) - { - $this->maxItems = $maxItems; - } - - /** - * Sets the maximum cloned length for strings. - * - * @return void - */ - public function setMaxString(int $maxString) - { - $this->maxString = $maxString; - } - - /** - * Sets the minimum tree depth where we are guaranteed to clone all the items. After this - * depth is reached, only setMaxItems items will be cloned. - * - * @return void - */ - public function setMinDepth(int $minDepth) - { - $this->minDepth = $minDepth; - } - - /** - * Clones a PHP variable. - * - * @param int $filter A bit field of Caster::EXCLUDE_* constants - */ - public function cloneVar(mixed $var, int $filter = 0): Data - { - $this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) { - if (\E_RECOVERABLE_ERROR === $type || \E_USER_ERROR === $type) { - // Cloner never dies - throw new \ErrorException($msg, 0, $type, $file, $line); - } - - if ($this->prevErrorHandler) { - return ($this->prevErrorHandler)($type, $msg, $file, $line, $context); - } - - return false; - }); - $this->filter = $filter; - - if ($gc = gc_enabled()) { - gc_disable(); - } - try { - return new Data($this->doClone($var)); - } finally { - if ($gc) { - gc_enable(); - } - restore_error_handler(); - $this->prevErrorHandler = null; - } - } - - /** - * Effectively clones the PHP variable. - */ - abstract protected function doClone(mixed $var): array; - - /** - * Casts an object to an array representation. - * - * @param bool $isNested True if the object is nested in the dumped structure - */ - protected function castObject(Stub $stub, bool $isNested): array - { - $obj = $stub->value; - $class = $stub->class; - - if (str_contains($class, "@anonymous\0")) { - $stub->class = get_debug_type($obj); - } - if (isset($this->classInfo[$class])) { - [$i, $parents, $hasDebugInfo, $fileInfo] = $this->classInfo[$class]; - } else { - $i = 2; - $parents = [$class]; - $hasDebugInfo = method_exists($class, '__debugInfo'); - - foreach (class_parents($class) as $p) { - $parents[] = $p; - ++$i; - } - foreach (class_implements($class) as $p) { - $parents[] = $p; - ++$i; - } - $parents[] = '*'; - - $r = new \ReflectionClass($class); - $fileInfo = $r->isInternal() || $r->isSubclassOf(Stub::class) ? [] : [ - 'file' => $r->getFileName(), - 'line' => $r->getStartLine(), - ]; - - $this->classInfo[$class] = [$i, $parents, $hasDebugInfo, $fileInfo]; - } - - $stub->attr += $fileInfo; - $a = Caster::castObject($obj, $class, $hasDebugInfo, $stub->class); - - try { - while ($i--) { - if (!empty($this->casters[$p = $parents[$i]])) { - foreach ($this->casters[$p] as $callback) { - $a = $callback($obj, $a, $stub, $isNested, $this->filter); - } - } - } - } catch (\Exception $e) { - $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; - } - - return $a; - } - - /** - * Casts a resource to an array representation. - * - * @param bool $isNested True if the object is nested in the dumped structure - */ - protected function castResource(Stub $stub, bool $isNested): array - { - $a = []; - $res = $stub->value; - $type = $stub->class; - - try { - if (!empty($this->casters[':'.$type])) { - foreach ($this->casters[':'.$type] as $callback) { - $a = $callback($res, $a, $stub, $isNested, $this->filter); - } - } - } catch (\Exception $e) { - $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; - } - - return $a; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/ClonerInterface.php b/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/ClonerInterface.php deleted file mode 100644 index 5a8e2e4c5..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/ClonerInterface.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Cloner; - -/** - * @author Nicolas Grekas - */ -interface ClonerInterface -{ - /** - * Clones a PHP variable. - */ - public function cloneVar(mixed $var): Data; -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/Cursor.php b/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/Cursor.php deleted file mode 100644 index 1fd796d67..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/Cursor.php +++ /dev/null @@ -1,43 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Cloner; - -/** - * Represents the current state of a dumper while dumping. - * - * @author Nicolas Grekas - */ -class Cursor -{ - public const HASH_INDEXED = Stub::ARRAY_INDEXED; - public const HASH_ASSOC = Stub::ARRAY_ASSOC; - public const HASH_OBJECT = Stub::TYPE_OBJECT; - public const HASH_RESOURCE = Stub::TYPE_RESOURCE; - - public $depth = 0; - public $refIndex = 0; - public $softRefTo = 0; - public $softRefCount = 0; - public $softRefHandle = 0; - public $hardRefTo = 0; - public $hardRefCount = 0; - public $hardRefHandle = 0; - public $hashType; - public $hashKey; - public $hashKeyIsBinary; - public $hashIndex = 0; - public $hashLength = 0; - public $hashCut = 0; - public $stop = false; - public $attr = []; - public $skipChildren = false; -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/Data.php b/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/Data.php deleted file mode 100644 index 16f51b0c7..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/Data.php +++ /dev/null @@ -1,434 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Cloner; - -use Symfony\Component\VarDumper\Caster\Caster; -use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; - -/** - * @author Nicolas Grekas - */ -class Data implements \ArrayAccess, \Countable, \IteratorAggregate, \Stringable -{ - private array $data; - private int $position = 0; - private int|string $key = 0; - private int $maxDepth = 20; - private int $maxItemsPerDepth = -1; - private int $useRefHandles = -1; - private array $context = []; - - /** - * @param array $data An array as returned by ClonerInterface::cloneVar() - */ - public function __construct(array $data) - { - $this->data = $data; - } - - public function getType(): ?string - { - $item = $this->data[$this->position][$this->key]; - - if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { - $item = $item->value; - } - if (!$item instanceof Stub) { - return \gettype($item); - } - if (Stub::TYPE_STRING === $item->type) { - return 'string'; - } - if (Stub::TYPE_ARRAY === $item->type) { - return 'array'; - } - if (Stub::TYPE_OBJECT === $item->type) { - return $item->class; - } - if (Stub::TYPE_RESOURCE === $item->type) { - return $item->class.' resource'; - } - - return null; - } - - /** - * Returns a native representation of the original value. - * - * @param array|bool $recursive Whether values should be resolved recursively or not - * - * @return string|int|float|bool|array|Data[]|null - */ - public function getValue(array|bool $recursive = false): string|int|float|bool|array|null - { - $item = $this->data[$this->position][$this->key]; - - if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { - $item = $item->value; - } - if (!($item = $this->getStub($item)) instanceof Stub) { - return $item; - } - if (Stub::TYPE_STRING === $item->type) { - return $item->value; - } - - $children = $item->position ? $this->data[$item->position] : []; - - foreach ($children as $k => $v) { - if ($recursive && !($v = $this->getStub($v)) instanceof Stub) { - continue; - } - $children[$k] = clone $this; - $children[$k]->key = $k; - $children[$k]->position = $item->position; - - if ($recursive) { - if (Stub::TYPE_REF === $v->type && ($v = $this->getStub($v->value)) instanceof Stub) { - $recursive = (array) $recursive; - if (isset($recursive[$v->position])) { - continue; - } - $recursive[$v->position] = true; - } - $children[$k] = $children[$k]->getValue($recursive); - } - } - - return $children; - } - - public function count(): int - { - return \count($this->getValue()); - } - - public function getIterator(): \Traversable - { - if (!\is_array($value = $this->getValue())) { - throw new \LogicException(sprintf('"%s" object holds non-iterable type "%s".', self::class, get_debug_type($value))); - } - - yield from $value; - } - - /** - * @return mixed - */ - public function __get(string $key) - { - if (null !== $data = $this->seek($key)) { - $item = $this->getStub($data->data[$data->position][$data->key]); - - return $item instanceof Stub || [] === $item ? $data : $item; - } - - return null; - } - - public function __isset(string $key): bool - { - return null !== $this->seek($key); - } - - public function offsetExists(mixed $key): bool - { - return $this->__isset($key); - } - - public function offsetGet(mixed $key): mixed - { - return $this->__get($key); - } - - public function offsetSet(mixed $key, mixed $value): void - { - throw new \BadMethodCallException(self::class.' objects are immutable.'); - } - - public function offsetUnset(mixed $key): void - { - throw new \BadMethodCallException(self::class.' objects are immutable.'); - } - - public function __toString(): string - { - $value = $this->getValue(); - - if (!\is_array($value)) { - return (string) $value; - } - - return sprintf('%s (count=%d)', $this->getType(), \count($value)); - } - - /** - * Returns a depth limited clone of $this. - */ - public function withMaxDepth(int $maxDepth): static - { - $data = clone $this; - $data->maxDepth = $maxDepth; - - return $data; - } - - /** - * Limits the number of elements per depth level. - */ - public function withMaxItemsPerDepth(int $maxItemsPerDepth): static - { - $data = clone $this; - $data->maxItemsPerDepth = $maxItemsPerDepth; - - return $data; - } - - /** - * Enables/disables objects' identifiers tracking. - * - * @param bool $useRefHandles False to hide global ref. handles - */ - public function withRefHandles(bool $useRefHandles): static - { - $data = clone $this; - $data->useRefHandles = $useRefHandles ? -1 : 0; - - return $data; - } - - public function withContext(array $context): static - { - $data = clone $this; - $data->context = $context; - - return $data; - } - - public function getContext(): array - { - return $this->context; - } - - /** - * Seeks to a specific key in nested data structures. - */ - public function seek(string|int $key): ?static - { - $item = $this->data[$this->position][$this->key]; - - if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { - $item = $item->value; - } - if (!($item = $this->getStub($item)) instanceof Stub || !$item->position) { - return null; - } - $keys = [$key]; - - switch ($item->type) { - case Stub::TYPE_OBJECT: - $keys[] = Caster::PREFIX_DYNAMIC.$key; - $keys[] = Caster::PREFIX_PROTECTED.$key; - $keys[] = Caster::PREFIX_VIRTUAL.$key; - $keys[] = "\0$item->class\0$key"; - // no break - case Stub::TYPE_ARRAY: - case Stub::TYPE_RESOURCE: - break; - default: - return null; - } - - $data = null; - $children = $this->data[$item->position]; - - foreach ($keys as $key) { - if (isset($children[$key]) || \array_key_exists($key, $children)) { - $data = clone $this; - $data->key = $key; - $data->position = $item->position; - break; - } - } - - return $data; - } - - /** - * Dumps data with a DumperInterface dumper. - * - * @return void - */ - public function dump(DumperInterface $dumper) - { - $refs = [0]; - $cursor = new Cursor(); - $cursor->hashType = -1; - $cursor->attr = $this->context[SourceContextProvider::class] ?? []; - $label = $this->context['label'] ?? ''; - - if ($cursor->attr || '' !== $label) { - $dumper->dumpScalar($cursor, 'label', $label); - } - $cursor->hashType = 0; - $this->dumpItem($dumper, $cursor, $refs, $this->data[$this->position][$this->key]); - } - - /** - * Depth-first dumping of items. - * - * @param mixed $item A Stub object or the original value being dumped - */ - private function dumpItem(DumperInterface $dumper, Cursor $cursor, array &$refs, mixed $item): void - { - $cursor->refIndex = 0; - $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0; - $cursor->hardRefTo = $cursor->hardRefHandle = $cursor->hardRefCount = 0; - $firstSeen = true; - - if (!$item instanceof Stub) { - $cursor->attr = []; - $type = \gettype($item); - if ($item && 'array' === $type) { - $item = $this->getStub($item); - } - } elseif (Stub::TYPE_REF === $item->type) { - if ($item->handle) { - if (!isset($refs[$r = $item->handle - (\PHP_INT_MAX >> 1)])) { - $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; - } else { - $firstSeen = false; - } - $cursor->hardRefTo = $refs[$r]; - $cursor->hardRefHandle = $this->useRefHandles & $item->handle; - $cursor->hardRefCount = 0 < $item->handle ? $item->refCount : 0; - } - $cursor->attr = $item->attr; - $type = $item->class ?: \gettype($item->value); - $item = $this->getStub($item->value); - } - if ($item instanceof Stub) { - if ($item->refCount) { - if (!isset($refs[$r = $item->handle])) { - $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; - } else { - $firstSeen = false; - } - $cursor->softRefTo = $refs[$r]; - } - $cursor->softRefHandle = $this->useRefHandles & $item->handle; - $cursor->softRefCount = $item->refCount; - $cursor->attr = $item->attr; - $cut = $item->cut; - - if ($item->position && $firstSeen) { - $children = $this->data[$item->position]; - - if ($cursor->stop) { - if ($cut >= 0) { - $cut += \count($children); - } - $children = []; - } - } else { - $children = []; - } - switch ($item->type) { - case Stub::TYPE_STRING: - $dumper->dumpString($cursor, $item->value, Stub::STRING_BINARY === $item->class, $cut); - break; - - case Stub::TYPE_ARRAY: - $item = clone $item; - $item->type = $item->class; - $item->class = $item->value; - // no break - case Stub::TYPE_OBJECT: - case Stub::TYPE_RESOURCE: - $withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth; - $dumper->enterHash($cursor, $item->type, $item->class, $withChildren); - if ($withChildren) { - if ($cursor->skipChildren) { - $withChildren = false; - $cut = -1; - } else { - $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type, null !== $item->class); - } - } elseif ($children && 0 <= $cut) { - $cut += \count($children); - } - $cursor->skipChildren = false; - $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut); - break; - - case Stub::TYPE_SCALAR: - $dumper->dumpScalar($cursor, 'default', $item->attr['value']); - break; - - default: - throw new \RuntimeException(sprintf('Unexpected Stub type: "%s".', $item->type)); - } - } elseif ('array' === $type) { - $dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, false); - $dumper->leaveHash($cursor, Cursor::HASH_INDEXED, 0, false, 0); - } elseif ('string' === $type) { - $dumper->dumpString($cursor, $item, false, 0); - } else { - $dumper->dumpScalar($cursor, $type, $item); - } - } - - /** - * Dumps children of hash structures. - * - * @return int The final number of removed items - */ - private function dumpChildren(DumperInterface $dumper, Cursor $parentCursor, array &$refs, array $children, int $hashCut, int $hashType, bool $dumpKeys): int - { - $cursor = clone $parentCursor; - ++$cursor->depth; - $cursor->hashType = $hashType; - $cursor->hashIndex = 0; - $cursor->hashLength = \count($children); - $cursor->hashCut = $hashCut; - foreach ($children as $key => $child) { - $cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key); - $cursor->hashKey = $dumpKeys ? $key : null; - $this->dumpItem($dumper, $cursor, $refs, $child); - if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) { - $parentCursor->stop = true; - - return $hashCut >= 0 ? $hashCut + $cursor->hashLength - $cursor->hashIndex : $hashCut; - } - } - - return $hashCut; - } - - private function getStub(mixed $item): mixed - { - if (!$item || !\is_array($item)) { - return $item; - } - - $stub = new Stub(); - $stub->type = Stub::TYPE_ARRAY; - foreach ($item as $stub->class => $stub->position) { - } - if (isset($item[0])) { - $stub->cut = $item[0]; - } - $stub->value = $stub->cut + ($stub->position ? \count($this->data[$stub->position]) : 0); - - return $stub; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/DumperInterface.php b/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/DumperInterface.php deleted file mode 100644 index 4c5b315b6..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/DumperInterface.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Cloner; - -/** - * DumperInterface used by Data objects. - * - * @author Nicolas Grekas - */ -interface DumperInterface -{ - /** - * Dumps a scalar value. - * - * @return void - */ - public function dumpScalar(Cursor $cursor, string $type, string|int|float|bool|null $value); - - /** - * Dumps a string. - * - * @param string $str The string being dumped - * @param bool $bin Whether $str is UTF-8 or binary encoded - * @param int $cut The number of characters $str has been cut by - * - * @return void - */ - public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut); - - /** - * Dumps while entering an hash. - * - * @param int $type A Cursor::HASH_* const for the type of hash - * @param string|int|null $class The object class, resource type or array count - * @param bool $hasChild When the dump of the hash has child item - * - * @return void - */ - public function enterHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild); - - /** - * Dumps while leaving an hash. - * - * @param int $type A Cursor::HASH_* const for the type of hash - * @param string|int|null $class The object class, resource type or array count - * @param bool $hasChild When the dump of the hash has child item - * @param int $cut The number of items the hash has been cut by - * - * @return void - */ - public function leaveHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild, int $cut); -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/Stub.php b/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/Stub.php deleted file mode 100644 index 0c2a4b9d0..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/Stub.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Cloner; - -/** - * Represents the main properties of a PHP variable. - * - * @author Nicolas Grekas - */ -class Stub -{ - public const TYPE_REF = 1; - public const TYPE_STRING = 2; - public const TYPE_ARRAY = 3; - public const TYPE_OBJECT = 4; - public const TYPE_RESOURCE = 5; - public const TYPE_SCALAR = 6; - - public const STRING_BINARY = 1; - public const STRING_UTF8 = 2; - - public const ARRAY_ASSOC = 1; - public const ARRAY_INDEXED = 2; - - public $type = self::TYPE_REF; - public $class = ''; - public $value; - public $cut = 0; - public $handle = 0; - public $refCount = 0; - public $position = 0; - public $attr = []; - - private static array $defaultProperties = []; - - /** - * @internal - */ - public function __sleep(): array - { - $properties = []; - - if (!isset(self::$defaultProperties[$c = static::class])) { - self::$defaultProperties[$c] = get_class_vars($c); - - foreach ((new \ReflectionClass($c))->getStaticProperties() as $k => $v) { - unset(self::$defaultProperties[$c][$k]); - } - } - - foreach (self::$defaultProperties[$c] as $k => $v) { - if ($this->$k !== $v) { - $properties[] = $k; - } - } - - return $properties; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/VarCloner.php b/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/VarCloner.php deleted file mode 100644 index e168d0d3b..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Cloner/VarCloner.php +++ /dev/null @@ -1,243 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Cloner; - -/** - * @author Nicolas Grekas - */ -class VarCloner extends AbstractCloner -{ - private static array $arrayCache = []; - - protected function doClone(mixed $var): array - { - $len = 1; // Length of $queue - $pos = 0; // Number of cloned items past the minimum depth - $refsCounter = 0; // Hard references counter - $queue = [[$var]]; // This breadth-first queue is the return value - $hardRefs = []; // Map of original zval ids to stub objects - $objRefs = []; // Map of original object handles to their stub object counterpart - $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning - $resRefs = []; // Map of original resource handles to their stub object counterpart - $values = []; // Map of stub objects' ids to original values - $maxItems = $this->maxItems; - $maxString = $this->maxString; - $minDepth = $this->minDepth; - $currentDepth = 0; // Current tree depth - $currentDepthFinalIndex = 0; // Final $queue index for current tree depth - $minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached - $cookie = (object) []; // Unique object used to detect hard references - $a = null; // Array cast for nested structures - $stub = null; // Stub capturing the main properties of an original item value - // or null if the original value is used directly - - $arrayStub = new Stub(); - $arrayStub->type = Stub::TYPE_ARRAY; - $fromObjCast = false; - - for ($i = 0; $i < $len; ++$i) { - // Detect when we move on to the next tree depth - if ($i > $currentDepthFinalIndex) { - ++$currentDepth; - $currentDepthFinalIndex = $len - 1; - if ($currentDepth >= $minDepth) { - $minimumDepthReached = true; - } - } - - $refs = $vals = $queue[$i]; - foreach ($vals as $k => $v) { - // $v is the original value or a stub object in case of hard references - - $zvalRef = ($r = \ReflectionReference::fromArrayElement($vals, $k)) ? $r->getId() : null; - - if ($zvalRef) { - $vals[$k] = &$stub; // Break hard references to make $queue completely - unset($stub); // independent from the original structure - if (null !== $vals[$k] = $hardRefs[$zvalRef] ?? null) { - $v = $vals[$k]; - if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) { - ++$v->value->refCount; - } - ++$v->refCount; - continue; - } - $vals[$k] = new Stub(); - $vals[$k]->value = $v; - $vals[$k]->handle = ++$refsCounter; - $hardRefs[$zvalRef] = $vals[$k]; - } - // Create $stub when the original value $v cannot be used directly - // If $v is a nested structure, put that structure in array $a - switch (true) { - case null === $v: - case \is_bool($v): - case \is_int($v): - case \is_float($v): - continue 2; - case \is_string($v): - if ('' === $v) { - continue 2; - } - if (!preg_match('//u', $v)) { - $stub = new Stub(); - $stub->type = Stub::TYPE_STRING; - $stub->class = Stub::STRING_BINARY; - if (0 <= $maxString && 0 < $cut = \strlen($v) - $maxString) { - $stub->cut = $cut; - $stub->value = substr($v, 0, -$cut); - } else { - $stub->value = $v; - } - } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) { - $stub = new Stub(); - $stub->type = Stub::TYPE_STRING; - $stub->class = Stub::STRING_UTF8; - $stub->cut = $cut; - $stub->value = mb_substr($v, 0, $maxString, 'UTF-8'); - } else { - continue 2; - } - $a = null; - break; - - case \is_array($v): - if (!$v) { - continue 2; - } - $stub = $arrayStub; - - $stub->class = array_is_list($v) ? Stub::ARRAY_INDEXED : Stub::ARRAY_ASSOC; - $a = $v; - break; - - case \is_object($v): - if (empty($objRefs[$h = spl_object_id($v)])) { - $stub = new Stub(); - $stub->type = Stub::TYPE_OBJECT; - $stub->class = $v::class; - $stub->value = $v; - $stub->handle = $h; - $a = $this->castObject($stub, 0 < $i); - if ($v !== $stub->value) { - if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) { - break; - } - $stub->handle = $h = spl_object_id($stub->value); - } - $stub->value = null; - if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { - $stub->cut = \count($a); - $a = null; - } - } - if (empty($objRefs[$h])) { - $objRefs[$h] = $stub; - $objects[] = $v; - } else { - $stub = $objRefs[$h]; - ++$stub->refCount; - $a = null; - } - break; - - default: // resource - if (empty($resRefs[$h = (int) $v])) { - $stub = new Stub(); - $stub->type = Stub::TYPE_RESOURCE; - if ('Unknown' === $stub->class = @get_resource_type($v)) { - $stub->class = 'Closed'; - } - $stub->value = $v; - $stub->handle = $h; - $a = $this->castResource($stub, 0 < $i); - $stub->value = null; - if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { - $stub->cut = \count($a); - $a = null; - } - } - if (empty($resRefs[$h])) { - $resRefs[$h] = $stub; - } else { - $stub = $resRefs[$h]; - ++$stub->refCount; - $a = null; - } - break; - } - - if ($a) { - if (!$minimumDepthReached || 0 > $maxItems) { - $queue[$len] = $a; - $stub->position = $len++; - } elseif ($pos < $maxItems) { - if ($maxItems < $pos += \count($a)) { - $a = \array_slice($a, 0, $maxItems - $pos, true); - if ($stub->cut >= 0) { - $stub->cut += $pos - $maxItems; - } - } - $queue[$len] = $a; - $stub->position = $len++; - } elseif ($stub->cut >= 0) { - $stub->cut += \count($a); - $stub->position = 0; - } - } - - if ($arrayStub === $stub) { - if ($arrayStub->cut) { - $stub = [$arrayStub->cut, $arrayStub->class => $arrayStub->position]; - $arrayStub->cut = 0; - } elseif (isset(self::$arrayCache[$arrayStub->class][$arrayStub->position])) { - $stub = self::$arrayCache[$arrayStub->class][$arrayStub->position]; - } else { - self::$arrayCache[$arrayStub->class][$arrayStub->position] = $stub = [$arrayStub->class => $arrayStub->position]; - } - } - - if (!$zvalRef) { - $vals[$k] = $stub; - } else { - $hardRefs[$zvalRef]->value = $stub; - } - } - - if ($fromObjCast) { - $fromObjCast = false; - $refs = $vals; - $vals = []; - $j = -1; - foreach ($queue[$i] as $k => $v) { - foreach ([$k => true] as $gk => $gv) { - } - if ($gk !== $k) { - $vals = (object) $vals; - $vals->{$k} = $refs[++$j]; - $vals = (array) $vals; - } else { - $vals[$k] = $refs[++$j]; - } - } - } - - $queue[$i] = $vals; - } - - foreach ($values as $h => $v) { - $hardRefs[$h] = $v; - } - - return $queue; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php b/data/web/inc/lib/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php deleted file mode 100644 index 4450fe986..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php +++ /dev/null @@ -1,79 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Command\Descriptor; - -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\VarDumper\Cloner\Data; -use Symfony\Component\VarDumper\Dumper\CliDumper; - -/** - * Describe collected data clones for cli output. - * - * @author Maxime Steinhausser - * - * @final - */ -class CliDescriptor implements DumpDescriptorInterface -{ - private CliDumper $dumper; - private mixed $lastIdentifier = null; - - public function __construct(CliDumper $dumper) - { - $this->dumper = $dumper; - } - - public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void - { - $io = $output instanceof SymfonyStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output); - $this->dumper->setColors($output->isDecorated()); - - $rows = [['date', date('r', (int) $context['timestamp'])]]; - $lastIdentifier = $this->lastIdentifier; - $this->lastIdentifier = $clientId; - - $section = "Received from client #$clientId"; - if (isset($context['request'])) { - $request = $context['request']; - $this->lastIdentifier = $request['identifier']; - $section = sprintf('%s %s', $request['method'], $request['uri']); - if ($controller = $request['controller']) { - $rows[] = ['controller', rtrim($this->dumper->dump($controller, true), "\n")]; - } - } elseif (isset($context['cli'])) { - $this->lastIdentifier = $context['cli']['identifier']; - $section = '$ '.$context['cli']['command_line']; - } - - if ($this->lastIdentifier !== $lastIdentifier) { - $io->section($section); - } - - if (isset($context['source'])) { - $source = $context['source']; - $sourceInfo = sprintf('%s on line %d', $source['name'], $source['line']); - if ($fileLink = $source['file_link'] ?? null) { - $sourceInfo = sprintf('%s', $fileLink, $sourceInfo); - } - $rows[] = ['source', $sourceInfo]; - $file = $source['file_relative'] ?? $source['file']; - $rows[] = ['file', $file]; - } - - $io->table([], $rows); - - $this->dumper->dump($data); - $io->newLine(); - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php b/data/web/inc/lib/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php deleted file mode 100644 index 267d27bfa..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Command\Descriptor; - -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\VarDumper\Cloner\Data; - -/** - * @author Maxime Steinhausser - */ -interface DumpDescriptorInterface -{ - public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void; -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php b/data/web/inc/lib/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php deleted file mode 100644 index 98f150a5e..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php +++ /dev/null @@ -1,119 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Command\Descriptor; - -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\VarDumper\Cloner\Data; -use Symfony\Component\VarDumper\Dumper\HtmlDumper; - -/** - * Describe collected data clones for html output. - * - * @author Maxime Steinhausser - * - * @final - */ -class HtmlDescriptor implements DumpDescriptorInterface -{ - private HtmlDumper $dumper; - private bool $initialized = false; - - public function __construct(HtmlDumper $dumper) - { - $this->dumper = $dumper; - } - - public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void - { - if (!$this->initialized) { - $styles = file_get_contents(__DIR__.'/../../Resources/css/htmlDescriptor.css'); - $scripts = file_get_contents(__DIR__.'/../../Resources/js/htmlDescriptor.js'); - $output->writeln(""); - $this->initialized = true; - } - - $title = '-'; - if (isset($context['request'])) { - $request = $context['request']; - $controller = "{$this->dumper->dump($request['controller'], true, ['maxDepth' => 0])}"; - $title = sprintf('%s %s', $request['method'], $uri = $request['uri'], $uri); - $dedupIdentifier = $request['identifier']; - } elseif (isset($context['cli'])) { - $title = '$ '.$context['cli']['command_line']; - $dedupIdentifier = $context['cli']['identifier']; - } else { - $dedupIdentifier = uniqid('', true); - } - - $sourceDescription = ''; - if (isset($context['source'])) { - $source = $context['source']; - $projectDir = $source['project_dir'] ?? null; - $sourceDescription = sprintf('%s on line %d', $source['name'], $source['line']); - if (isset($source['file_link'])) { - $sourceDescription = sprintf('%s', $source['file_link'], $sourceDescription); - } - } - - $isoDate = $this->extractDate($context, 'c'); - $tags = array_filter([ - 'controller' => $controller ?? null, - 'project dir' => $projectDir ?? null, - ]); - - $output->writeln(<< -
-
-

$title

- -
- {$this->renderTags($tags)} -
-
-

- $sourceDescription -

- {$this->dumper->dump($data, true)} -
- -HTML - ); - } - - private function extractDate(array $context, string $format = 'r'): string - { - return date($format, (int) $context['timestamp']); - } - - private function renderTags(array $tags): string - { - if (!$tags) { - return ''; - } - - $renderedTags = ''; - foreach ($tags as $key => $value) { - $renderedTags .= sprintf('
  • %s%s
  • ', $key, $value); - } - - return << -
      - $renderedTags -
    - -HTML; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Command/ServerDumpCommand.php b/data/web/inc/lib/vendor/symfony/var-dumper/Command/ServerDumpCommand.php deleted file mode 100644 index b64a884b9..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Command/ServerDumpCommand.php +++ /dev/null @@ -1,112 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Command; - -use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\VarDumper\Cloner\Data; -use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor; -use Symfony\Component\VarDumper\Command\Descriptor\DumpDescriptorInterface; -use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor; -use Symfony\Component\VarDumper\Dumper\CliDumper; -use Symfony\Component\VarDumper\Dumper\HtmlDumper; -use Symfony\Component\VarDumper\Server\DumpServer; - -/** - * Starts a dump server to collect and output dumps on a single place with multiple formats support. - * - * @author Maxime Steinhausser - * - * @final - */ -#[AsCommand(name: 'server:dump', description: 'Start a dump server that collects and displays dumps in a single place')] -class ServerDumpCommand extends Command -{ - private DumpServer $server; - - /** @var DumpDescriptorInterface[] */ - private array $descriptors; - - public function __construct(DumpServer $server, array $descriptors = []) - { - $this->server = $server; - $this->descriptors = $descriptors + [ - 'cli' => new CliDescriptor(new CliDumper()), - 'html' => new HtmlDescriptor(new HtmlDumper()), - ]; - - parent::__construct(); - } - - protected function configure(): void - { - $this - ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format (%s)', implode(', ', $this->getAvailableFormats())), 'cli') - ->setHelp(<<<'EOF' -%command.name% starts a dump server that collects and displays -dumps in a single place for debugging you application: - - php %command.full_name% - -You can consult dumped data in HTML format in your browser by providing the --format=html option -and redirecting the output to a file: - - php %command.full_name% --format="html" > dump.html - -EOF - ) - ; - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $io = new SymfonyStyle($input, $output); - $format = $input->getOption('format'); - - if (!$descriptor = $this->descriptors[$format] ?? null) { - throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $format)); - } - - $errorIo = $io->getErrorStyle(); - $errorIo->title('Symfony Var Dumper Server'); - - $this->server->start(); - - $errorIo->success(sprintf('Server listening on %s', $this->server->getHost())); - $errorIo->comment('Quit the server with CONTROL-C.'); - - $this->server->listen(function (Data $data, array $context, int $clientId) use ($descriptor, $io) { - $descriptor->describe($io, $data, $context, $clientId); - }); - - return 0; - } - - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - if ($input->mustSuggestOptionValuesFor('format')) { - $suggestions->suggestValues($this->getAvailableFormats()); - } - } - - private function getAvailableFormats(): array - { - return array_keys($this->descriptors); - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/AbstractDumper.php b/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/AbstractDumper.php deleted file mode 100644 index 53165ba64..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/AbstractDumper.php +++ /dev/null @@ -1,204 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Dumper; - -use Symfony\Component\VarDumper\Cloner\Data; -use Symfony\Component\VarDumper\Cloner\DumperInterface; - -/** - * Abstract mechanism for dumping a Data object. - * - * @author Nicolas Grekas - */ -abstract class AbstractDumper implements DataDumperInterface, DumperInterface -{ - public const DUMP_LIGHT_ARRAY = 1; - public const DUMP_STRING_LENGTH = 2; - public const DUMP_COMMA_SEPARATOR = 4; - public const DUMP_TRAILING_COMMA = 8; - - /** @var callable|resource|string|null */ - public static $defaultOutput = 'php://output'; - - protected $line = ''; - /** @var callable|null */ - protected $lineDumper; - /** @var resource|null */ - protected $outputStream; - protected $decimalPoint = '.'; - protected $indentPad = ' '; - protected $flags; - - private string $charset = ''; - - /** - * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput - * @param string|null $charset The default character encoding to use for non-UTF8 strings - * @param int $flags A bit field of static::DUMP_* constants to fine tune dumps representation - */ - public function __construct($output = null, ?string $charset = null, int $flags = 0) - { - $this->flags = $flags; - $this->setCharset($charset ?: \ini_get('php.output_encoding') ?: \ini_get('default_charset') ?: 'UTF-8'); - $this->setOutput($output ?: static::$defaultOutput); - if (!$output && \is_string(static::$defaultOutput)) { - static::$defaultOutput = $this->outputStream; - } - } - - /** - * Sets the output destination of the dumps. - * - * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path - * - * @return callable|resource|string|null The previous output destination - */ - public function setOutput($output) - { - $prev = $this->outputStream ?? $this->lineDumper; - - if (\is_callable($output)) { - $this->outputStream = null; - $this->lineDumper = $output; - } else { - if (\is_string($output)) { - $output = fopen($output, 'w'); - } - $this->outputStream = $output; - $this->lineDumper = $this->echoLine(...); - } - - return $prev; - } - - /** - * Sets the default character encoding to use for non-UTF8 strings. - * - * @return string The previous charset - */ - public function setCharset(string $charset): string - { - $prev = $this->charset; - - $charset = strtoupper($charset); - $charset = null === $charset || 'UTF-8' === $charset || 'UTF8' === $charset ? 'CP1252' : $charset; - - $this->charset = $charset; - - return $prev; - } - - /** - * Sets the indentation pad string. - * - * @param string $pad A string that will be prepended to dumped lines, repeated by nesting level - * - * @return string The previous indent pad - */ - public function setIndentPad(string $pad): string - { - $prev = $this->indentPad; - $this->indentPad = $pad; - - return $prev; - } - - /** - * Dumps a Data object. - * - * @param callable|resource|string|true|null $output A line dumper callable, an opened stream, an output path or true to return the dump - * - * @return string|null The dump as string when $output is true - */ - public function dump(Data $data, $output = null): ?string - { - if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? setlocale(\LC_NUMERIC, 0) : null) { - setlocale(\LC_NUMERIC, 'C'); - } - - if ($returnDump = true === $output) { - $output = fopen('php://memory', 'r+'); - } - if ($output) { - $prevOutput = $this->setOutput($output); - } - try { - $data->dump($this); - $this->dumpLine(-1); - - if ($returnDump) { - $result = stream_get_contents($output, -1, 0); - fclose($output); - - return $result; - } - } finally { - if ($output) { - $this->setOutput($prevOutput); - } - if ($locale) { - setlocale(\LC_NUMERIC, $locale); - } - } - - return null; - } - - /** - * Dumps the current line. - * - * @param int $depth The recursive depth in the dumped structure for the line being dumped, - * or -1 to signal the end-of-dump to the line dumper callable - * - * @return void - */ - protected function dumpLine(int $depth) - { - ($this->lineDumper)($this->line, $depth, $this->indentPad); - $this->line = ''; - } - - /** - * Generic line dumper callback. - * - * @return void - */ - protected function echoLine(string $line, int $depth, string $indentPad) - { - if (-1 !== $depth) { - fwrite($this->outputStream, str_repeat($indentPad, $depth).$line."\n"); - } - } - - /** - * Converts a non-UTF-8 string to UTF-8. - */ - protected function utf8Encode(?string $s): ?string - { - if (null === $s || preg_match('//u', $s)) { - return $s; - } - - if (!\function_exists('iconv')) { - throw new \RuntimeException('Unable to convert a non-UTF-8 string to UTF-8: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); - } - - if (false !== $c = @iconv($this->charset, 'UTF-8', $s)) { - return $c; - } - if ('CP1252' !== $this->charset && false !== $c = @iconv('CP1252', 'UTF-8', $s)) { - return $c; - } - - return iconv('CP850', 'UTF-8', $s); - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/CliDumper.php b/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/CliDumper.php deleted file mode 100644 index af9637072..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/CliDumper.php +++ /dev/null @@ -1,677 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Dumper; - -use Symfony\Component\VarDumper\Cloner\Cursor; -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * CliDumper dumps variables for command line output. - * - * @author Nicolas Grekas - */ -class CliDumper extends AbstractDumper -{ - public static $defaultColors; - /** @var callable|resource|string|null */ - public static $defaultOutput = 'php://stdout'; - - protected $colors; - protected $maxStringWidth = 0; - protected $styles = [ - // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics - 'default' => '0;38;5;208', - 'num' => '1;38;5;38', - 'const' => '1;38;5;208', - 'str' => '1;38;5;113', - 'note' => '38;5;38', - 'ref' => '38;5;247', - 'public' => '', - 'protected' => '', - 'private' => '', - 'meta' => '38;5;170', - 'key' => '38;5;113', - 'index' => '38;5;38', - ]; - - protected static $controlCharsRx = '/[\x00-\x1F\x7F]+/'; - protected static $controlCharsMap = [ - "\t" => '\t', - "\n" => '\n', - "\v" => '\v', - "\f" => '\f', - "\r" => '\r', - "\033" => '\e', - ]; - protected static $unicodeCharsRx = "/[\u{00A0}\u{00AD}\u{034F}\u{061C}\u{115F}\u{1160}\u{17B4}\u{17B5}\u{180E}\u{2000}-\u{200F}\u{202F}\u{205F}\u{2060}-\u{2064}\u{206A}-\u{206F}\u{3000}\u{2800}\u{3164}\u{FEFF}\u{FFA0}\u{1D159}\u{1D173}-\u{1D17A}]/u"; - - protected $collapseNextHash = false; - protected $expandNextHash = false; - - private array $displayOptions = [ - 'fileLinkFormat' => null, - ]; - - private bool $handlesHrefGracefully; - - public function __construct($output = null, ?string $charset = null, int $flags = 0) - { - parent::__construct($output, $charset, $flags); - - if ('\\' === \DIRECTORY_SEPARATOR && !$this->isWindowsTrueColor()) { - // Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI - $this->setStyles([ - 'default' => '31', - 'num' => '1;34', - 'const' => '1;31', - 'str' => '1;32', - 'note' => '34', - 'ref' => '1;30', - 'meta' => '35', - 'key' => '32', - 'index' => '34', - ]); - } - - $this->displayOptions['fileLinkFormat'] = \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l'; - } - - /** - * Enables/disables colored output. - * - * @return void - */ - public function setColors(bool $colors) - { - $this->colors = $colors; - } - - /** - * Sets the maximum number of characters per line for dumped strings. - * - * @return void - */ - public function setMaxStringWidth(int $maxStringWidth) - { - $this->maxStringWidth = $maxStringWidth; - } - - /** - * Configures styles. - * - * @param array $styles A map of style names to style definitions - * - * @return void - */ - public function setStyles(array $styles) - { - $this->styles = $styles + $this->styles; - } - - /** - * Configures display options. - * - * @param array $displayOptions A map of display options to customize the behavior - * - * @return void - */ - public function setDisplayOptions(array $displayOptions) - { - $this->displayOptions = $displayOptions + $this->displayOptions; - } - - /** - * @return void - */ - public function dumpScalar(Cursor $cursor, string $type, string|int|float|bool|null $value) - { - $this->dumpKey($cursor); - $this->collapseNextHash = $this->expandNextHash = false; - - $style = 'const'; - $attr = $cursor->attr; - - switch ($type) { - case 'default': - $style = 'default'; - break; - - case 'label': - $this->styles += ['label' => $this->styles['default']]; - $style = 'label'; - break; - - case 'integer': - $style = 'num'; - - if (isset($this->styles['integer'])) { - $style = 'integer'; - } - - break; - - case 'double': - $style = 'num'; - - if (isset($this->styles['float'])) { - $style = 'float'; - } - - $value = match (true) { - \INF === $value => 'INF', - -\INF === $value => '-INF', - is_nan($value) => 'NAN', - default => !str_contains($value = (string) $value, $this->decimalPoint) ? $value .= $this->decimalPoint.'0' : $value, - }; - break; - - case 'NULL': - $value = 'null'; - break; - - case 'boolean': - $value = $value ? 'true' : 'false'; - break; - - default: - $attr += ['value' => $this->utf8Encode($value)]; - $value = $this->utf8Encode($type); - break; - } - - $this->line .= $this->style($style, $value, $attr); - - $this->endValue($cursor); - } - - /** - * @return void - */ - public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) - { - $this->dumpKey($cursor); - $this->collapseNextHash = $this->expandNextHash = false; - $attr = $cursor->attr; - - if ($bin) { - $str = $this->utf8Encode($str); - } - if ('' === $str) { - $this->line .= '""'; - if ($cut) { - $this->line .= '…'.$cut; - } - $this->endValue($cursor); - } else { - $attr += [ - 'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0, - 'binary' => $bin, - ]; - $str = $bin && str_contains($str, "\0") ? [$str] : explode("\n", $str); - if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) { - unset($str[1]); - $str[0] .= "\n"; - } - $m = \count($str) - 1; - $i = $lineCut = 0; - - if (self::DUMP_STRING_LENGTH & $this->flags) { - $this->line .= '('.$attr['length'].') '; - } - if ($bin) { - $this->line .= 'b'; - } - - if ($m) { - $this->line .= '"""'; - $this->dumpLine($cursor->depth); - } else { - $this->line .= '"'; - } - - foreach ($str as $str) { - if ($i < $m) { - $str .= "\n"; - } - if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = mb_strlen($str, 'UTF-8')) { - $str = mb_substr($str, 0, $this->maxStringWidth, 'UTF-8'); - $lineCut = $len - $this->maxStringWidth; - } - if ($m && 0 < $cursor->depth) { - $this->line .= $this->indentPad; - } - if ('' !== $str) { - $this->line .= $this->style('str', $str, $attr); - } - if ($i++ == $m) { - if ($m) { - if ('' !== $str) { - $this->dumpLine($cursor->depth); - if (0 < $cursor->depth) { - $this->line .= $this->indentPad; - } - } - $this->line .= '"""'; - } else { - $this->line .= '"'; - } - if ($cut < 0) { - $this->line .= '…'; - $lineCut = 0; - } elseif ($cut) { - $lineCut += $cut; - } - } - if ($lineCut) { - $this->line .= '…'.$lineCut; - $lineCut = 0; - } - - if ($i > $m) { - $this->endValue($cursor); - } else { - $this->dumpLine($cursor->depth); - } - } - } - } - - /** - * @return void - */ - public function enterHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild) - { - $this->colors ??= $this->supportsColors(); - - $this->dumpKey($cursor); - $this->expandNextHash = false; - $attr = $cursor->attr; - - if ($this->collapseNextHash) { - $cursor->skipChildren = true; - $this->collapseNextHash = $hasChild = false; - } - - $class = $this->utf8Encode($class); - if (Cursor::HASH_OBJECT === $type) { - $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class, $attr).(empty($attr['cut_hash']) ? ' {' : '') : '{'; - } elseif (Cursor::HASH_RESOURCE === $type) { - $prefix = $this->style('note', $class.' resource', $attr).($hasChild ? ' {' : ' '); - } else { - $prefix = $class && !(self::DUMP_LIGHT_ARRAY & $this->flags) ? $this->style('note', 'array:'.$class).' [' : '['; - } - - if (($cursor->softRefCount || 0 < $cursor->softRefHandle) && empty($attr['cut_hash'])) { - $prefix .= $this->style('ref', (Cursor::HASH_RESOURCE === $type ? '@' : '#').(0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo), ['count' => $cursor->softRefCount]); - } elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) { - $prefix .= $this->style('ref', '&'.$cursor->hardRefTo, ['count' => $cursor->hardRefCount]); - } elseif (!$hasChild && Cursor::HASH_RESOURCE === $type) { - $prefix = substr($prefix, 0, -1); - } - - $this->line .= $prefix; - - if ($hasChild) { - $this->dumpLine($cursor->depth); - } - } - - /** - * @return void - */ - public function leaveHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild, int $cut) - { - if (empty($cursor->attr['cut_hash'])) { - $this->dumpEllipsis($cursor, $hasChild, $cut); - $this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : '')); - } - - $this->endValue($cursor); - } - - /** - * Dumps an ellipsis for cut children. - * - * @param bool $hasChild When the dump of the hash has child item - * @param int $cut The number of items the hash has been cut by - * - * @return void - */ - protected function dumpEllipsis(Cursor $cursor, bool $hasChild, int $cut) - { - if ($cut) { - $this->line .= ' …'; - if (0 < $cut) { - $this->line .= $cut; - } - if ($hasChild) { - $this->dumpLine($cursor->depth + 1); - } - } - } - - /** - * Dumps a key in a hash structure. - * - * @return void - */ - protected function dumpKey(Cursor $cursor) - { - if (null !== $key = $cursor->hashKey) { - if ($cursor->hashKeyIsBinary) { - $key = $this->utf8Encode($key); - } - $attr = ['binary' => $cursor->hashKeyIsBinary]; - $bin = $cursor->hashKeyIsBinary ? 'b' : ''; - $style = 'key'; - switch ($cursor->hashType) { - default: - case Cursor::HASH_INDEXED: - if (self::DUMP_LIGHT_ARRAY & $this->flags) { - break; - } - $style = 'index'; - // no break - case Cursor::HASH_ASSOC: - if (\is_int($key)) { - $this->line .= $this->style($style, $key).' => '; - } else { - $this->line .= $bin.'"'.$this->style($style, $key).'" => '; - } - break; - - case Cursor::HASH_RESOURCE: - $key = "\0~\0".$key; - // no break - case Cursor::HASH_OBJECT: - if (!isset($key[0]) || "\0" !== $key[0]) { - $this->line .= '+'.$bin.$this->style('public', $key).': '; - } elseif (0 < strpos($key, "\0", 1)) { - $key = explode("\0", substr($key, 1), 2); - - switch ($key[0][0]) { - case '+': // User inserted keys - $attr['dynamic'] = true; - $this->line .= '+'.$bin.'"'.$this->style('public', $key[1], $attr).'": '; - break 2; - case '~': - $style = 'meta'; - if (isset($key[0][1])) { - parse_str(substr($key[0], 1), $attr); - $attr += ['binary' => $cursor->hashKeyIsBinary]; - } - break; - case '*': - $style = 'protected'; - $bin = '#'.$bin; - break; - default: - $attr['class'] = $key[0]; - $style = 'private'; - $bin = '-'.$bin; - break; - } - - if (isset($attr['collapse'])) { - if ($attr['collapse']) { - $this->collapseNextHash = true; - } else { - $this->expandNextHash = true; - } - } - - $this->line .= $bin.$this->style($style, $key[1], $attr).($attr['separator'] ?? ': '); - } else { - // This case should not happen - $this->line .= '-'.$bin.'"'.$this->style('private', $key, ['class' => '']).'": '; - } - break; - } - - if ($cursor->hardRefTo) { - $this->line .= $this->style('ref', '&'.($cursor->hardRefCount ? $cursor->hardRefTo : ''), ['count' => $cursor->hardRefCount]).' '; - } - } - } - - /** - * Decorates a value with some style. - * - * @param string $style The type of style being applied - * @param string $value The value being styled - * @param array $attr Optional context information - */ - protected function style(string $style, string $value, array $attr = []): string - { - $this->colors ??= $this->supportsColors(); - - $this->handlesHrefGracefully ??= 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') - && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100) - && !isset($_SERVER['IDEA_INITIAL_DIRECTORY']); - - if (isset($attr['ellipsis'], $attr['ellipsis-type'])) { - $prefix = substr($value, 0, -$attr['ellipsis']); - if ('cli' === \PHP_SAPI && 'path' === $attr['ellipsis-type'] && isset($_SERVER[$pwd = '\\' === \DIRECTORY_SEPARATOR ? 'CD' : 'PWD']) && str_starts_with($prefix, $_SERVER[$pwd])) { - $prefix = '.'.substr($prefix, \strlen($_SERVER[$pwd])); - } - if (!empty($attr['ellipsis-tail'])) { - $prefix .= substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']); - $value = substr($value, -$attr['ellipsis'] + $attr['ellipsis-tail']); - } else { - $value = substr($value, -$attr['ellipsis']); - } - - $value = $this->style('default', $prefix).$this->style($style, $value); - - goto href; - } - - $map = static::$controlCharsMap; - $startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : ''; - $endCchr = $this->colors ? "\033[m\033[{$this->styles[$style]}m" : ''; - $value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) { - $s = $startCchr; - $c = $c[$i = 0]; - do { - $s .= $map[$c[$i]] ?? sprintf('\x%02X', \ord($c[$i])); - } while (isset($c[++$i])); - - return $s.$endCchr; - }, $value, -1, $cchrCount); - - if (!($attr['binary'] ?? false)) { - $value = preg_replace_callback(static::$unicodeCharsRx, function ($c) use (&$cchrCount, $startCchr, $endCchr) { - ++$cchrCount; - - return $startCchr.'\u{'.strtoupper(dechex(mb_ord($c[0]))).'}'.$endCchr; - }, $value); - } - - if ($this->colors && '' !== $value) { - if ($cchrCount && "\033" === $value[0]) { - $value = substr($value, \strlen($startCchr)); - } else { - $value = "\033[{$this->styles[$style]}m".$value; - } - if ($cchrCount && str_ends_with($value, $endCchr)) { - $value = substr($value, 0, -\strlen($endCchr)); - } else { - $value .= "\033[{$this->styles['default']}m"; - } - } - - href: - if ($this->colors && $this->handlesHrefGracefully) { - if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) { - if ('note' === $style) { - $value .= "\033]8;;{$href}\033\\^\033]8;;\033\\"; - } else { - $attr['href'] = $href; - } - } - if (isset($attr['href'])) { - if ('label' === $style) { - $value .= '^'; - } - $value = "\033]8;;{$attr['href']}\033\\{$value}\033]8;;\033\\"; - } - } - - if ('label' === $style && '' !== $value) { - $value .= ' '; - } - - return $value; - } - - protected function supportsColors(): bool - { - if ($this->outputStream !== static::$defaultOutput) { - return $this->hasColorSupport($this->outputStream); - } - if (isset(static::$defaultColors)) { - return static::$defaultColors; - } - if (isset($_SERVER['argv'][1])) { - $colors = $_SERVER['argv']; - $i = \count($colors); - while (--$i > 0) { - if (isset($colors[$i][5])) { - switch ($colors[$i]) { - case '--ansi': - case '--color': - case '--color=yes': - case '--color=force': - case '--color=always': - case '--colors=always': - return static::$defaultColors = true; - - case '--no-ansi': - case '--color=no': - case '--color=none': - case '--color=never': - case '--colors=never': - return static::$defaultColors = false; - } - } - } - } - - $h = stream_get_meta_data($this->outputStream) + ['wrapper_type' => null]; - $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'w') : $this->outputStream; - - return static::$defaultColors = $this->hasColorSupport($h); - } - - /** - * @return void - */ - protected function dumpLine(int $depth, bool $endOfValue = false) - { - if (null === $this->colors) { - $this->colors = $this->supportsColors(); - } - - if ($this->colors) { - $this->line = sprintf("\033[%sm%s\033[m", $this->styles['default'], $this->line); - } - parent::dumpLine($depth); - } - - /** - * @return void - */ - protected function endValue(Cursor $cursor) - { - if (-1 === $cursor->hashType) { - return; - } - - if (Stub::ARRAY_INDEXED === $cursor->hashType || Stub::ARRAY_ASSOC === $cursor->hashType) { - if (self::DUMP_TRAILING_COMMA & $this->flags && 0 < $cursor->depth) { - $this->line .= ','; - } elseif (self::DUMP_COMMA_SEPARATOR & $this->flags && 1 < $cursor->hashLength - $cursor->hashIndex) { - $this->line .= ','; - } - } - - $this->dumpLine($cursor->depth, true); - } - - /** - * Returns true if the stream supports colorization. - * - * Reference: Composer\XdebugHandler\Process::supportsColor - * https://github.com/composer/xdebug-handler - */ - private function hasColorSupport(mixed $stream): bool - { - if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { - return false; - } - - // Follow https://no-color.org/ - if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { - return false; - } - - if ('Hyper' === getenv('TERM_PROGRAM')) { - return true; - } - - if (\DIRECTORY_SEPARATOR === '\\') { - return (\function_exists('sapi_windows_vt100_support') - && @sapi_windows_vt100_support($stream)) - || false !== getenv('ANSICON') - || 'ON' === getenv('ConEmuANSI') - || 'xterm' === getenv('TERM'); - } - - return stream_isatty($stream); - } - - /** - * Returns true if the Windows terminal supports true color. - * - * Note that this does not check an output stream, but relies on environment - * variables from known implementations, or a PHP and Windows version that - * supports true color. - */ - private function isWindowsTrueColor(): bool - { - $result = 183 <= getenv('ANSICON_VER') - || 'ON' === getenv('ConEmuANSI') - || 'xterm' === getenv('TERM') - || 'Hyper' === getenv('TERM_PROGRAM'); - - if (!$result) { - $version = sprintf( - '%s.%s.%s', - PHP_WINDOWS_VERSION_MAJOR, - PHP_WINDOWS_VERSION_MINOR, - PHP_WINDOWS_VERSION_BUILD - ); - $result = $version >= '10.0.15063'; - } - - return $result; - } - - private function getSourceLink(string $file, int $line): string|false - { - if ($fmt = $this->displayOptions['fileLinkFormat']) { - return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : ($fmt->format($file, $line) ?: 'file://'.$file.'#L'.$line); - } - - return false; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php b/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php deleted file mode 100644 index 38f878971..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Dumper\ContextProvider; - -/** - * Tries to provide context on CLI. - * - * @author Maxime Steinhausser - */ -final class CliContextProvider implements ContextProviderInterface -{ - public function getContext(): ?array - { - if ('cli' !== \PHP_SAPI) { - return null; - } - - return [ - 'command_line' => $commandLine = implode(' ', $_SERVER['argv'] ?? []), - 'identifier' => hash('crc32b', $commandLine.$_SERVER['REQUEST_TIME_FLOAT']), - ]; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php b/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php deleted file mode 100644 index 532aa0f96..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Dumper\ContextProvider; - -/** - * Interface to provide contextual data about dump data clones sent to a server. - * - * @author Maxime Steinhausser - */ -interface ContextProviderInterface -{ - public function getContext(): ?array; -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php b/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php deleted file mode 100644 index 69dff067b..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php +++ /dev/null @@ -1,51 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Dumper\ContextProvider; - -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\VarDumper\Caster\ReflectionCaster; -use Symfony\Component\VarDumper\Cloner\VarCloner; - -/** - * Tries to provide context from a request. - * - * @author Maxime Steinhausser - */ -final class RequestContextProvider implements ContextProviderInterface -{ - private RequestStack $requestStack; - private VarCloner $cloner; - - public function __construct(RequestStack $requestStack) - { - $this->requestStack = $requestStack; - $this->cloner = new VarCloner(); - $this->cloner->setMaxItems(0); - $this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); - } - - public function getContext(): ?array - { - if (null === $request = $this->requestStack->getCurrentRequest()) { - return null; - } - - $controller = $request->attributes->get('_controller'); - - return [ - 'uri' => $request->getUri(), - 'method' => $request->getMethod(), - 'controller' => $controller ? $this->cloner->cloneVar($controller) : $controller, - 'identifier' => spl_object_hash($request), - ]; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php b/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php deleted file mode 100644 index cadddfac4..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php +++ /dev/null @@ -1,127 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Dumper\ContextProvider; - -use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter as LegacyFileLinkFormatter; -use Symfony\Component\VarDumper\Cloner\VarCloner; -use Symfony\Component\VarDumper\Dumper\HtmlDumper; -use Symfony\Component\VarDumper\VarDumper; -use Twig\Template; - -/** - * Tries to provide context from sources (class name, file, line, code excerpt, ...). - * - * @author Nicolas Grekas - * @author Maxime Steinhausser - */ -final class SourceContextProvider implements ContextProviderInterface -{ - private int $limit; - private ?string $charset; - private ?string $projectDir; - private FileLinkFormatter|LegacyFileLinkFormatter|null $fileLinkFormatter; - - public function __construct(?string $charset = null, ?string $projectDir = null, FileLinkFormatter|LegacyFileLinkFormatter|null $fileLinkFormatter = null, int $limit = 9) - { - $this->charset = $charset; - $this->projectDir = $projectDir; - $this->fileLinkFormatter = $fileLinkFormatter; - $this->limit = $limit; - } - - public function getContext(): ?array - { - $trace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, $this->limit); - - $file = $trace[1]['file']; - $line = $trace[1]['line']; - $name = '-' === $file || 'Standard input code' === $file ? 'Standard input code' : false; - $fileExcerpt = false; - - for ($i = 2; $i < $this->limit; ++$i) { - if (isset($trace[$i]['class'], $trace[$i]['function']) - && 'dump' === $trace[$i]['function'] - && VarDumper::class === $trace[$i]['class'] - ) { - $file = $trace[$i]['file'] ?? $file; - $line = $trace[$i]['line'] ?? $line; - - while (++$i < $this->limit) { - if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && !str_starts_with($trace[$i]['function'], 'call_user_func')) { - $file = $trace[$i]['file']; - $line = $trace[$i]['line']; - - break; - } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) { - $template = $trace[$i]['object']; - $name = $template->getTemplateName(); - $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false); - $info = $template->getDebugInfo(); - if (isset($info[$trace[$i - 1]['line']])) { - $line = $info[$trace[$i - 1]['line']]; - $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null; - - if ($src) { - $src = explode("\n", $src); - $fileExcerpt = []; - - for ($i = max($line - 3, 1), $max = min($line + 3, \count($src)); $i <= $max; ++$i) { - $fileExcerpt[] = ''.$this->htmlEncode($src[$i - 1]).''; - } - - $fileExcerpt = '
      '.implode("\n", $fileExcerpt).'
    '; - } - } - break; - } - } - break; - } - } - - if (false === $name) { - $name = str_replace('\\', '/', $file); - $name = substr($name, strrpos($name, '/') + 1); - } - - $context = ['name' => $name, 'file' => $file, 'line' => $line]; - $context['file_excerpt'] = $fileExcerpt; - - if (null !== $this->projectDir) { - $context['project_dir'] = $this->projectDir; - if (str_starts_with($file, $this->projectDir)) { - $context['file_relative'] = ltrim(substr($file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); - } - } - - if ($this->fileLinkFormatter && $fileLink = $this->fileLinkFormatter->format($context['file'], $context['line'])) { - $context['file_link'] = $fileLink; - } - - return $context; - } - - private function htmlEncode(string $s): string - { - $html = ''; - - $dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset); - $dumper->setDumpHeader(''); - $dumper->setDumpBoundaries('', ''); - - $cloner = new VarCloner(); - $dumper->dump($cloner->cloneVar($s)); - - return substr(strip_tags($html), 1, -1); - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php b/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php deleted file mode 100644 index 84cfb4259..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Dumper; - -use Symfony\Component\VarDumper\Cloner\Data; -use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; - -/** - * @author Kévin Thérage - */ -class ContextualizedDumper implements DataDumperInterface -{ - private DataDumperInterface $wrappedDumper; - private array $contextProviders; - - /** - * @param ContextProviderInterface[] $contextProviders - */ - public function __construct(DataDumperInterface $wrappedDumper, array $contextProviders) - { - $this->wrappedDumper = $wrappedDumper; - $this->contextProviders = $contextProviders; - } - - /** - * @return string|null - */ - public function dump(Data $data) - { - $context = $data->getContext(); - foreach ($this->contextProviders as $contextProvider) { - $context[$contextProvider::class] = $contextProvider->getContext(); - } - - return $this->wrappedDumper->dump($data->withContext($context)); - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php b/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php deleted file mode 100644 index df05b6af5..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Dumper; - -use Symfony\Component\VarDumper\Cloner\Data; - -/** - * DataDumperInterface for dumping Data objects. - * - * @author Nicolas Grekas - */ -interface DataDumperInterface -{ - /** - * @return string|null - */ - public function dump(Data $data); -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/HtmlDumper.php b/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/HtmlDumper.php deleted file mode 100644 index ea09e6819..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/HtmlDumper.php +++ /dev/null @@ -1,998 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Dumper; - -use Symfony\Component\VarDumper\Cloner\Cursor; -use Symfony\Component\VarDumper\Cloner\Data; - -/** - * HtmlDumper dumps variables as HTML. - * - * @author Nicolas Grekas - */ -class HtmlDumper extends CliDumper -{ - /** @var callable|resource|string|null */ - public static $defaultOutput = 'php://output'; - - protected static $themes = [ - 'dark' => [ - 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', - 'num' => 'font-weight:bold; color:#1299DA', - 'const' => 'font-weight:bold', - 'str' => 'font-weight:bold; color:#56DB3A', - 'note' => 'color:#1299DA', - 'ref' => 'color:#A0A0A0', - 'public' => 'color:#FFFFFF', - 'protected' => 'color:#FFFFFF', - 'private' => 'color:#FFFFFF', - 'meta' => 'color:#B729D9', - 'key' => 'color:#56DB3A', - 'index' => 'color:#1299DA', - 'ellipsis' => 'color:#FF8400', - 'ns' => 'user-select:none;', - ], - 'light' => [ - 'default' => 'background:none; color:#CC7832; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', - 'num' => 'font-weight:bold; color:#1299DA', - 'const' => 'font-weight:bold', - 'str' => 'font-weight:bold; color:#629755;', - 'note' => 'color:#6897BB', - 'ref' => 'color:#6E6E6E', - 'public' => 'color:#262626', - 'protected' => 'color:#262626', - 'private' => 'color:#262626', - 'meta' => 'color:#B729D9', - 'key' => 'color:#789339', - 'index' => 'color:#1299DA', - 'ellipsis' => 'color:#CC7832', - 'ns' => 'user-select:none;', - ], - ]; - - protected $dumpHeader; - protected $dumpPrefix = '
    ';
    -    protected $dumpSuffix = '
    '; - protected $dumpId = 'sf-dump'; - protected $colors = true; - protected $headerIsDumped = false; - protected $lastDepth = -1; - protected $styles; - - private array $displayOptions = [ - 'maxDepth' => 1, - 'maxStringLength' => 160, - 'fileLinkFormat' => null, - ]; - private array $extraDisplayOptions = []; - - public function __construct($output = null, ?string $charset = null, int $flags = 0) - { - AbstractDumper::__construct($output, $charset, $flags); - $this->dumpId = 'sf-dump-'.mt_rand(); - $this->displayOptions['fileLinkFormat'] = \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); - $this->styles = static::$themes['dark'] ?? self::$themes['dark']; - } - - /** - * @return void - */ - public function setStyles(array $styles) - { - $this->headerIsDumped = false; - $this->styles = $styles + $this->styles; - } - - /** - * @return void - */ - public function setTheme(string $themeName) - { - if (!isset(static::$themes[$themeName])) { - throw new \InvalidArgumentException(sprintf('Theme "%s" does not exist in class "%s".', $themeName, static::class)); - } - - $this->setStyles(static::$themes[$themeName]); - } - - /** - * Configures display options. - * - * @param array $displayOptions A map of display options to customize the behavior - * - * @return void - */ - public function setDisplayOptions(array $displayOptions) - { - $this->headerIsDumped = false; - $this->displayOptions = $displayOptions + $this->displayOptions; - } - - /** - * Sets an HTML header that will be dumped once in the output stream. - * - * @return void - */ - public function setDumpHeader(?string $header) - { - $this->dumpHeader = $header; - } - - /** - * Sets an HTML prefix and suffix that will encapse every single dump. - * - * @return void - */ - public function setDumpBoundaries(string $prefix, string $suffix) - { - $this->dumpPrefix = $prefix; - $this->dumpSuffix = $suffix; - } - - public function dump(Data $data, $output = null, array $extraDisplayOptions = []): ?string - { - $this->extraDisplayOptions = $extraDisplayOptions; - $result = parent::dump($data, $output); - $this->dumpId = 'sf-dump-'.mt_rand(); - - return $result; - } - - /** - * Dumps the HTML header. - * - * @return string - */ - protected function getDumpHeader() - { - $this->headerIsDumped = $this->outputStream ?? $this->lineDumper; - - if (null !== $this->dumpHeader) { - return $this->dumpHeader; - } - - $line = str_replace('{$options}', json_encode($this->displayOptions, \JSON_FORCE_OBJECT), <<<'EOHTML' -'.$this->dumpHeader; - } - - /** - * @return void - */ - public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) - { - if ('' === $str && isset($cursor->attr['img-data'], $cursor->attr['content-type'])) { - $this->dumpKey($cursor); - $this->line .= $this->style('default', $cursor->attr['img-size'] ?? '', []); - $this->line .= $cursor->depth >= $this->displayOptions['maxDepth'] ? ' ' : ' '; - $this->endValue($cursor); - $this->line .= $this->indentPad; - $this->line .= sprintf('', $cursor->attr['content-type'], base64_encode($cursor->attr['img-data'])); - $this->endValue($cursor); - } else { - parent::dumpString($cursor, $str, $bin, $cut); - } - } - - /** - * @return void - */ - public function enterHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild) - { - if (Cursor::HASH_OBJECT === $type) { - $cursor->attr['depth'] = $cursor->depth; - } - parent::enterHash($cursor, $type, $class, false); - - if ($cursor->skipChildren || $cursor->depth >= $this->displayOptions['maxDepth']) { - $cursor->skipChildren = false; - $eol = ' class=sf-dump-compact>'; - } else { - $this->expandNextHash = false; - $eol = ' class=sf-dump-expanded>'; - } - - if ($hasChild) { - $this->line .= 'dumpId, $r); - } - $this->line .= $eol; - $this->dumpLine($cursor->depth); - } - } - - /** - * @return void - */ - public function leaveHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild, int $cut) - { - $this->dumpEllipsis($cursor, $hasChild, $cut); - if ($hasChild) { - $this->line .= ''; - } - parent::leaveHash($cursor, $type, $class, $hasChild, 0); - } - - protected function style(string $style, string $value, array $attr = []): string - { - if ('' === $value && ('label' !== $style || !isset($attr['file']) && !isset($attr['href']))) { - return ''; - } - - $v = esc($value); - - if ('ref' === $style) { - if (empty($attr['count'])) { - return sprintf('%s', $v); - } - $r = ('#' !== $v[0] ? 1 - ('@' !== $v[0]) : 2).substr($value, 1); - - return sprintf('%s', $this->dumpId, $r, 1 + $attr['count'], $v); - } - - if ('const' === $style && isset($attr['value'])) { - $style .= sprintf(' title="%s"', esc(\is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value']))); - } elseif ('public' === $style) { - $style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property'); - } elseif ('str' === $style && 1 < $attr['length']) { - $style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : ''); - } elseif ('note' === $style && 0 < ($attr['depth'] ?? 0) && false !== $c = strrpos($value, '\\')) { - $style .= ' title=""'; - $attr += [ - 'ellipsis' => \strlen($value) - $c, - 'ellipsis-type' => 'note', - 'ellipsis-tail' => 1, - ]; - } elseif ('protected' === $style) { - $style .= ' title="Protected property"'; - } elseif ('meta' === $style && isset($attr['title'])) { - $style .= sprintf(' title="%s"', esc($this->utf8Encode($attr['title']))); - } elseif ('private' === $style) { - $style .= sprintf(' title="Private property defined in class: `%s`"', esc($this->utf8Encode($attr['class']))); - } - - if (isset($attr['ellipsis'])) { - $class = 'sf-dump-ellipsis'; - if (isset($attr['ellipsis-type'])) { - $class = sprintf('"%s sf-dump-ellipsis-%s"', $class, $attr['ellipsis-type']); - } - $label = esc(substr($value, -$attr['ellipsis'])); - $style = str_replace(' title="', " title=\"$v\n", $style); - $v = sprintf('%s', $class, substr($v, 0, -\strlen($label))); - - if (!empty($attr['ellipsis-tail'])) { - $tail = \strlen(esc(substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']))); - $v .= sprintf('%s%s', $class, substr($label, 0, $tail), substr($label, $tail)); - } else { - $v .= $label; - } - } - - $map = static::$controlCharsMap; - $v = "".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) { - $s = $b = ''; - }, $v).''; - - if (!($attr['binary'] ?? false)) { - $v = preg_replace_callback(static::$unicodeCharsRx, function ($c) { - return '\u{'.strtoupper(dechex(mb_ord($c[0]))).'}'; - }, $v); - } - - if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) { - $attr['href'] = $href; - } - if (isset($attr['href'])) { - if ('label' === $style) { - $v .= '^'; - } - $target = isset($attr['file']) ? '' : ' target="_blank"'; - $v = sprintf('%s', esc($this->utf8Encode($attr['href'])), $target, $v); - } - if (isset($attr['lang'])) { - $v = sprintf('%s', esc($attr['lang']), $v); - } - if ('label' === $style) { - $v .= ' '; - } - - return $v; - } - - /** - * @return void - */ - protected function dumpLine(int $depth, bool $endOfValue = false) - { - if (-1 === $this->lastDepth) { - $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line; - } - if ($this->headerIsDumped !== ($this->outputStream ?? $this->lineDumper)) { - $this->line = $this->getDumpHeader().$this->line; - } - - if (-1 === $depth) { - $args = ['"'.$this->dumpId.'"']; - if ($this->extraDisplayOptions) { - $args[] = json_encode($this->extraDisplayOptions, \JSON_FORCE_OBJECT); - } - // Replace is for BC - $this->line .= sprintf(str_replace('"%s"', '%s', $this->dumpSuffix), implode(', ', $args)); - } - $this->lastDepth = $depth; - - $this->line = mb_encode_numericentity($this->line, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8'); - - if (-1 === $depth) { - AbstractDumper::dumpLine(0); - } - AbstractDumper::dumpLine($depth); - } - - private function getSourceLink(string $file, int $line): string|false - { - $options = $this->extraDisplayOptions + $this->displayOptions; - - if ($fmt = $options['fileLinkFormat']) { - return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); - } - - return false; - } -} - -function esc(string $str): string -{ - return htmlspecialchars($str, \ENT_QUOTES, 'UTF-8'); -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ServerDumper.php b/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ServerDumper.php deleted file mode 100644 index 60fdd7ac3..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Dumper/ServerDumper.php +++ /dev/null @@ -1,55 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Dumper; - -use Symfony\Component\VarDumper\Cloner\Data; -use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; -use Symfony\Component\VarDumper\Server\Connection; - -/** - * ServerDumper forwards serialized Data clones to a server. - * - * @author Maxime Steinhausser - */ -class ServerDumper implements DataDumperInterface -{ - private Connection $connection; - private ?DataDumperInterface $wrappedDumper; - - /** - * @param string $host The server host - * @param DataDumperInterface|null $wrappedDumper A wrapped instance used whenever we failed contacting the server - * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name - */ - public function __construct(string $host, ?DataDumperInterface $wrappedDumper = null, array $contextProviders = []) - { - $this->connection = new Connection($host, $contextProviders); - $this->wrappedDumper = $wrappedDumper; - } - - public function getContextProviders(): array - { - return $this->connection->getContextProviders(); - } - - /** - * @return string|null - */ - public function dump(Data $data) - { - if (!$this->connection->write($data) && $this->wrappedDumper) { - return $this->wrappedDumper->dump($data); - } - - return null; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php b/data/web/inc/lib/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php deleted file mode 100644 index fd8eca9f1..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Exception; - -/** - * @author Nicolas Grekas - */ -class ThrowingCasterException extends \Exception -{ - /** - * @param \Throwable $prev The exception thrown from the caster - */ - public function __construct(\Throwable $prev) - { - parent::__construct('Unexpected '.$prev::class.' thrown from a caster: '.$prev->getMessage(), 0, $prev); - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/README.md b/data/web/inc/lib/vendor/symfony/var-dumper/README.md deleted file mode 100644 index a0da8c9ab..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/README.md +++ /dev/null @@ -1,15 +0,0 @@ -VarDumper Component -=================== - -The VarDumper component provides mechanisms for walking through any arbitrary -PHP variable. It provides a better `dump()` function that you can use instead -of `var_dump()`. - -Resources ---------- - - * [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.html) - * [Contributing](https://symfony.com/doc/current/contributing/index.html) - * [Report issues](https://github.com/symfony/symfony/issues) and - [send Pull Requests](https://github.com/symfony/symfony/pulls) - in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Resources/bin/var-dump-server b/data/web/inc/lib/vendor/symfony/var-dumper/Resources/bin/var-dump-server deleted file mode 100755 index f398fcef7..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Resources/bin/var-dump-server +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env php - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -if ('cli' !== PHP_SAPI) { - throw new Exception('This script must be run from the command line.'); -} - -/** - * Starts a dump server to collect and output dumps on a single place with multiple formats support. - * - * @author Maxime Steinhausser - */ - -use Psr\Log\LoggerInterface; -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Logger\ConsoleLogger; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\VarDumper\Command\ServerDumpCommand; -use Symfony\Component\VarDumper\Server\DumpServer; - -function includeIfExists(string $file): bool -{ - return file_exists($file) && include $file; -} - -if ( - !includeIfExists(__DIR__ . '/../../../../autoload.php') && - !includeIfExists(__DIR__ . '/../../vendor/autoload.php') && - !includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php') -) { - fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL); - exit(1); -} - -if (!class_exists(Application::class)) { - fwrite(STDERR, 'You need the "symfony/console" component in order to run the VarDumper server.'.PHP_EOL); - exit(1); -} - -$input = new ArgvInput(); -$output = new ConsoleOutput(); -$defaultHost = '127.0.0.1:9912'; -$host = $input->getParameterOption(['--host'], $_SERVER['VAR_DUMPER_SERVER'] ?? $defaultHost, true); -$logger = interface_exists(LoggerInterface::class) ? new ConsoleLogger($output->getErrorOutput()) : null; - -$app = new Application(); - -$app->getDefinition()->addOption( - new InputOption('--host', null, InputOption::VALUE_REQUIRED, 'The address the server should listen to', $defaultHost) -); - -$app->add($command = new ServerDumpCommand(new DumpServer($host, $logger))) - ->getApplication() - ->setDefaultCommand($command->getName(), true) - ->run($input, $output) -; diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css b/data/web/inc/lib/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css deleted file mode 100644 index 8f706d640..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css +++ /dev/null @@ -1,130 +0,0 @@ -body { - display: flex; - flex-direction: column-reverse; - justify-content: flex-end; - max-width: 1140px; - margin: auto; - padding: 15px; - word-wrap: break-word; - background-color: #F9F9F9; - color: #222; - font-family: Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.4; -} -p { - margin: 0; -} -a { - color: #218BC3; - text-decoration: none; -} -a:hover { - text-decoration: underline; -} -.text-small { - font-size: 12px !important; -} -article { - margin: 5px; - margin-bottom: 10px; -} -article > header > .row { - display: flex; - flex-direction: row; - align-items: baseline; - margin-bottom: 10px; -} -article > header > .row > .col { - flex: 1; - display: flex; - align-items: baseline; -} -article > header > .row > h2 { - font-size: 14px; - color: #222; - font-weight: normal; - font-family: "Lucida Console", monospace, sans-serif; - word-break: break-all; - margin: 20px 5px 0 0; - user-select: all; -} -article > header > .row > h2 > code { - white-space: nowrap; - user-select: none; - color: #cc2255; - background-color: #f7f7f9; - border: 1px solid #e1e1e8; - border-radius: 3px; - margin-right: 5px; - padding: 0 3px; -} -article > header > .row > time.col { - flex: 0; - text-align: right; - white-space: nowrap; - color: #999; - font-style: italic; -} -article > header ul.tags { - list-style: none; - padding: 0; - margin: 0; - font-size: 12px; -} -article > header ul.tags > li { - user-select: all; - margin-bottom: 2px; -} -article > header ul.tags > li > span.badge { - display: inline-block; - padding: .25em .4em; - margin-right: 5px; - border-radius: 4px; - background-color: #6c757d3b; - color: #524d4d; - font-size: 12px; - text-align: center; - font-weight: 700; - line-height: 1; - white-space: nowrap; - vertical-align: baseline; - user-select: none; -} -article > section.body { - border: 1px solid #d8d8d8; - background: #FFF; - padding: 10px; - border-radius: 3px; -} -pre.sf-dump { - border-radius: 3px; - margin-bottom: 0; -} -.hidden { - display: none !important; -} -.dumped-tag > .sf-dump { - display: inline-block; - margin: 0; - padding: 1px 5px; - line-height: 1.4; - vertical-align: top; - background-color: transparent; - user-select: auto; -} -.dumped-tag > pre.sf-dump, -.dumped-tag > .sf-dump-default { - color: #CC7832; - background: none; -} -.dumped-tag > .sf-dump .sf-dump-str { color: #629755; } -.dumped-tag > .sf-dump .sf-dump-private, -.dumped-tag > .sf-dump .sf-dump-protected, -.dumped-tag > .sf-dump .sf-dump-public { color: #262626; } -.dumped-tag > .sf-dump .sf-dump-note { color: #6897BB; } -.dumped-tag > .sf-dump .sf-dump-key { color: #789339; } -.dumped-tag > .sf-dump .sf-dump-ref { color: #6E6E6E; } -.dumped-tag > .sf-dump .sf-dump-ellipsis { color: #CC7832; max-width: 100em; } -.dumped-tag > .sf-dump .sf-dump-ellipsis-path { max-width: 5em; } -.dumped-tag > .sf-dump .sf-dump-ns { user-select: none; } diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Resources/functions/dump.php b/data/web/inc/lib/vendor/symfony/var-dumper/Resources/functions/dump.php deleted file mode 100644 index f2ff74c0c..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Resources/functions/dump.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use Symfony\Component\VarDumper\Caster\ScalarStub; -use Symfony\Component\VarDumper\VarDumper; - -if (!function_exists('dump')) { - /** - * @author Nicolas Grekas - * @author Alexandre Daubois - */ - function dump(mixed ...$vars): mixed - { - if (!$vars) { - VarDumper::dump(new ScalarStub('🐛')); - - return null; - } - - if (array_key_exists(0, $vars) && 1 === count($vars)) { - VarDumper::dump($vars[0]); - $k = 0; - } else { - foreach ($vars as $k => $v) { - VarDumper::dump($v, is_int($k) ? 1 + $k : $k); - } - } - - if (1 < count($vars)) { - return $vars; - } - - return $vars[$k]; - } -} - -if (!function_exists('dd')) { - function dd(mixed ...$vars): never - { - if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) && !headers_sent()) { - header('HTTP/1.1 500 Internal Server Error'); - } - - if (array_key_exists(0, $vars) && 1 === count($vars)) { - VarDumper::dump($vars[0]); - } else { - foreach ($vars as $k => $v) { - VarDumper::dump($v, is_int($k) ? 1 + $k : $k); - } - } - - exit(1); - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js b/data/web/inc/lib/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js deleted file mode 100644 index 63101e57c..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js +++ /dev/null @@ -1,10 +0,0 @@ -document.addEventListener('DOMContentLoaded', function() { - let prev = null; - Array.from(document.getElementsByTagName('article')).reverse().forEach(function (article) { - const dedupId = article.dataset.dedupId; - if (dedupId === prev) { - article.getElementsByTagName('header')[0].classList.add('hidden'); - } - prev = dedupId; - }); -}); diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Server/Connection.php b/data/web/inc/lib/vendor/symfony/var-dumper/Server/Connection.php deleted file mode 100644 index 4383278c9..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Server/Connection.php +++ /dev/null @@ -1,97 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Server; - -use Symfony\Component\VarDumper\Cloner\Data; -use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; - -/** - * Forwards serialized Data clones to a server. - * - * @author Maxime Steinhausser - */ -class Connection -{ - private string $host; - private array $contextProviders; - - /** - * @var resource|null - */ - private $socket; - - /** - * @param string $host The server host - * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name - */ - public function __construct(string $host, array $contextProviders = []) - { - if (!str_contains($host, '://')) { - $host = 'tcp://'.$host; - } - - $this->host = $host; - $this->contextProviders = $contextProviders; - } - - public function getContextProviders(): array - { - return $this->contextProviders; - } - - public function write(Data $data): bool - { - $socketIsFresh = !$this->socket; - if (!$this->socket = $this->socket ?: $this->createSocket()) { - return false; - } - - $context = ['timestamp' => microtime(true)]; - foreach ($this->contextProviders as $name => $provider) { - $context[$name] = $provider->getContext(); - } - $context = array_filter($context); - $encodedPayload = base64_encode(serialize([$data, $context]))."\n"; - - set_error_handler(static fn () => null); - try { - if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { - return true; - } - if (!$socketIsFresh) { - stream_socket_shutdown($this->socket, \STREAM_SHUT_RDWR); - fclose($this->socket); - $this->socket = $this->createSocket(); - } - if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { - return true; - } - } finally { - restore_error_handler(); - } - - return false; - } - - /** - * @return resource|null - */ - private function createSocket() - { - set_error_handler(static fn () => null); - try { - return stream_socket_client($this->host, $errno, $errstr, 3) ?: null; - } finally { - restore_error_handler(); - } - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Server/DumpServer.php b/data/web/inc/lib/vendor/symfony/var-dumper/Server/DumpServer.php deleted file mode 100644 index a9228a2ef..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Server/DumpServer.php +++ /dev/null @@ -1,109 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Server; - -use Psr\Log\LoggerInterface; -use Symfony\Component\VarDumper\Cloner\Data; -use Symfony\Component\VarDumper\Cloner\Stub; - -/** - * A server collecting Data clones sent by a ServerDumper. - * - * @author Maxime Steinhausser - * - * @final - */ -class DumpServer -{ - private string $host; - private ?LoggerInterface $logger; - - /** - * @var resource|null - */ - private $socket; - - public function __construct(string $host, ?LoggerInterface $logger = null) - { - if (!str_contains($host, '://')) { - $host = 'tcp://'.$host; - } - - $this->host = $host; - $this->logger = $logger; - } - - public function start(): void - { - if (!$this->socket = stream_socket_server($this->host, $errno, $errstr)) { - throw new \RuntimeException(sprintf('Server start failed on "%s": ', $this->host).$errstr.' '.$errno); - } - } - - public function listen(callable $callback): void - { - if (null === $this->socket) { - $this->start(); - } - - foreach ($this->getMessages() as $clientId => $message) { - $this->logger?->info('Received a payload from client {clientId}', ['clientId' => $clientId]); - - $payload = @unserialize(base64_decode($message), ['allowed_classes' => [Data::class, Stub::class]]); - - // Impossible to decode the message, give up. - if (false === $payload) { - $this->logger?->warning('Unable to decode a message from {clientId} client.', ['clientId' => $clientId]); - - continue; - } - - if (!\is_array($payload) || \count($payload) < 2 || !$payload[0] instanceof Data || !\is_array($payload[1])) { - $this->logger?->warning('Invalid payload from {clientId} client. Expected an array of two elements (Data $data, array $context)', ['clientId' => $clientId]); - - continue; - } - - [$data, $context] = $payload; - - $callback($data, $context, $clientId); - } - } - - public function getHost(): string - { - return $this->host; - } - - private function getMessages(): iterable - { - $sockets = [(int) $this->socket => $this->socket]; - $write = []; - - while (true) { - $read = $sockets; - stream_select($read, $write, $write, null); - - foreach ($read as $stream) { - if ($this->socket === $stream) { - $stream = stream_socket_accept($this->socket); - $sockets[(int) $stream] = $stream; - } elseif (feof($stream)) { - unset($sockets[(int) $stream]); - fclose($stream); - } else { - yield (int) $stream => fgets($stream); - } - } - } - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php b/data/web/inc/lib/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php deleted file mode 100644 index 4475efd12..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php +++ /dev/null @@ -1,84 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Test; - -use Symfony\Component\VarDumper\Cloner\VarCloner; -use Symfony\Component\VarDumper\Dumper\CliDumper; - -/** - * @author Nicolas Grekas - */ -trait VarDumperTestTrait -{ - /** - * @internal - */ - private array $varDumperConfig = [ - 'casters' => [], - 'flags' => null, - ]; - - protected function setUpVarDumper(array $casters, ?int $flags = null): void - { - $this->varDumperConfig['casters'] = $casters; - $this->varDumperConfig['flags'] = $flags; - } - - /** - * @after - */ - protected function tearDownVarDumper(): void - { - $this->varDumperConfig['casters'] = []; - $this->varDumperConfig['flags'] = null; - } - - public function assertDumpEquals(mixed $expected, mixed $data, int $filter = 0, string $message = '') - { - $this->assertSame($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); - } - - public function assertDumpMatchesFormat(mixed $expected, mixed $data, int $filter = 0, string $message = '') - { - $this->assertStringMatchesFormat($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); - } - - protected function getDump(mixed $data, string|int|null $key = null, int $filter = 0): ?string - { - if (null === $flags = $this->varDumperConfig['flags']) { - $flags = getenv('DUMP_LIGHT_ARRAY') ? CliDumper::DUMP_LIGHT_ARRAY : 0; - $flags |= getenv('DUMP_STRING_LENGTH') ? CliDumper::DUMP_STRING_LENGTH : 0; - $flags |= getenv('DUMP_COMMA_SEPARATOR') ? CliDumper::DUMP_COMMA_SEPARATOR : 0; - } - - $cloner = new VarCloner(); - $cloner->addCasters($this->varDumperConfig['casters']); - $cloner->setMaxItems(-1); - $dumper = new CliDumper(null, null, $flags); - $dumper->setColors(false); - $data = $cloner->cloneVar($data, $filter)->withRefHandles(false); - if (null !== $key && null === $data = $data->seek($key)) { - return null; - } - - return rtrim($dumper->dump($data, true)); - } - - private function prepareExpectation(mixed $expected, int $filter): string - { - if (!\is_string($expected)) { - $expected = $this->getDump($expected, null, $filter); - } - - return rtrim($expected); - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/VarDumper.php b/data/web/inc/lib/vendor/symfony/var-dumper/VarDumper.php deleted file mode 100644 index e1400f150..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/VarDumper.php +++ /dev/null @@ -1,127 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper; - -use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\VarDumper\Caster\ReflectionCaster; -use Symfony\Component\VarDumper\Cloner\VarCloner; -use Symfony\Component\VarDumper\Dumper\CliDumper; -use Symfony\Component\VarDumper\Dumper\ContextProvider\CliContextProvider; -use Symfony\Component\VarDumper\Dumper\ContextProvider\RequestContextProvider; -use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; -use Symfony\Component\VarDumper\Dumper\ContextualizedDumper; -use Symfony\Component\VarDumper\Dumper\HtmlDumper; -use Symfony\Component\VarDumper\Dumper\ServerDumper; - -// Load the global dump() function -require_once __DIR__.'/Resources/functions/dump.php'; - -/** - * @author Nicolas Grekas - */ -class VarDumper -{ - /** - * @var callable|null - */ - private static $handler; - - /** - * @param string|null $label - * - * @return mixed - */ - public static function dump(mixed $var/* , string $label = null */) - { - $label = 2 <= \func_num_args() ? func_get_arg(1) : null; - if (null === self::$handler) { - self::register(); - } - - return (self::$handler)($var, $label); - } - - public static function setHandler(?callable $callable = null): ?callable - { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/var-dumper', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } - $prevHandler = self::$handler; - - // Prevent replacing the handler with expected format as soon as the env var was set: - if (isset($_SERVER['VAR_DUMPER_FORMAT'])) { - return $prevHandler; - } - - self::$handler = $callable; - - return $prevHandler; - } - - private static function register(): void - { - $cloner = new VarCloner(); - $cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); - - $format = $_SERVER['VAR_DUMPER_FORMAT'] ?? null; - switch (true) { - case 'html' === $format: - $dumper = new HtmlDumper(); - break; - case 'cli' === $format: - $dumper = new CliDumper(); - break; - case 'server' === $format: - case $format && 'tcp' === parse_url($format, \PHP_URL_SCHEME): - $host = 'server' === $format ? $_SERVER['VAR_DUMPER_SERVER'] ?? '127.0.0.1:9912' : $format; - $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? new CliDumper() : new HtmlDumper(); - $dumper = new ServerDumper($host, $dumper, self::getDefaultContextProviders()); - break; - default: - $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? new CliDumper() : new HtmlDumper(); - } - - if (!$dumper instanceof ServerDumper) { - $dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]); - } - - self::$handler = function ($var, ?string $label = null) use ($cloner, $dumper) { - $var = $cloner->cloneVar($var); - - if (null !== $label) { - $var = $var->withContext(['label' => $label]); - } - - $dumper->dump($var); - }; - } - - private static function getDefaultContextProviders(): array - { - $contextProviders = []; - - if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) && class_exists(Request::class)) { - $requestStack = new RequestStack(); - $requestStack->push(Request::createFromGlobals()); - $contextProviders['request'] = new RequestContextProvider($requestStack); - } - - $fileLinkFormatter = class_exists(FileLinkFormatter::class) ? new FileLinkFormatter(null, $requestStack ?? null) : null; - - return $contextProviders + [ - 'cli' => new CliContextProvider(), - 'source' => new SourceContextProvider(null, null, $fileLinkFormatter), - ]; - } -} diff --git a/data/web/inc/lib/vendor/symfony/var-dumper/composer.json b/data/web/inc/lib/vendor/symfony/var-dumper/composer.json deleted file mode 100644 index e6166f86d..000000000 --- a/data/web/inc/lib/vendor/symfony/var-dumper/composer.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "symfony/var-dumper", - "type": "library", - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "keywords": ["dump", "debug"], - "homepage": "https://symfony.com", - "license": "MIT", - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^6.3|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/uid": "^5.4|^6.0|^7.0", - "twig/twig": "^2.13|^3.0.4" - }, - "conflict": { - "symfony/console": "<5.4" - }, - "autoload": { - "files": [ "Resources/functions/dump.php" ], - "psr-4": { "Symfony\\Component\\VarDumper\\": "" }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "minimum-stability": "dev" -} diff --git a/data/web/inc/lib/vendor/tightenco/collect/.github/workflows/run-tests.yml b/data/web/inc/lib/vendor/tightenco/collect/.github/workflows/run-tests.yml deleted file mode 100644 index 406c40049..000000000 --- a/data/web/inc/lib/vendor/tightenco/collect/.github/workflows/run-tests.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Run tests - -on: - push: - branches: [laravel-9-ongoing, laravel-8-ongoing] - pull_request: - -jobs: - tests: - strategy: - matrix: - os: [Ubuntu, macOS] - php: [8.0, 8.1, 8.2] - - include: - - os: Ubuntu - os-version: ubuntu-latest - - - os: macOS - os-version: macos-latest - - name: ${{ matrix.os }} - PHP ${{ matrix.php }} - - runs-on: ${{ matrix.os-version }} - - steps: - - name: Checkout code - uses: actions/checkout@v1 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: posix, dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick - coverage: none - - - name: Install dependencies - run: composer update --prefer-stable --prefer-dist --no-interaction - - - name: Run tests - run: bash upgrade.sh diff --git a/data/web/inc/lib/vendor/tightenco/collect/branch-commit-push.sh b/data/web/inc/lib/vendor/tightenco/collect/branch-commit-push.sh deleted file mode 100755 index 124e58331..000000000 --- a/data/web/inc/lib/vendor/tightenco/collect/branch-commit-push.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/bash - - -GREEN='\033[0;32m' -RED='\033[0;31m' -WHITE='\033[0;37m' -RESET='\033[0m' - -function validateVersion() -{ - echo "" - passedVersion=$1 - echo -e "${WHITE}-- Validating tag '$passedVersion'...${RESET}" - - # Todo: validate the version here using a regex; if fail, just exit - # ... expect 8.75.0, with no v in front of it - - if [[ $passedVersion == '' ]]; then - echo -e "\n-- Invalid tag. Tags should be structured without v; e.g. 8.57.0" - exit - fi - - echo -e "${WHITE}-- Tag valid.${RESET}" - echo "" -} - -# Exit script if any command fails (e.g. phpunit) -set -e - - -# Require confirmation it's set up corrctly -echo -echo -e "${WHITE}-- This script is meant to be run after running upgrade.sh, BEFORE committing to Git.${RESET}" - -while true; do - echo -e "${GREEN}-- Is that the current state of your local project?${RESET}" - read -p "-- (y/n) " yn - case $yn in - [Yy]* ) break;; - [Nn]* ) exit;; - * ) echo "Please answer y or n.";; - esac -done - -# Get the version and exit if not valid -validateVersion $1 - -# Create official v prefaced version -version="v$1" - -# Run tests (and bail if they fail) -phpunit -echo -e "\n${WHITE}-- Tests succeeded.${RESET}" - -# Branch -echo -e "\n${WHITE}-- Creating a Git branch '$version-changes'...${RESET}\n" -git checkout -b $version-changes - -# Add and commit, with "v8.57.0 changes" as the commit name -git add -A -git commit -m "$version changes" - -echo -echo -e "${WHITE}-- Git committed.${RESET}" - -# Push -git push -u origin $version-changes diff --git a/data/web/inc/lib/vendor/tightenco/collect/composer.json b/data/web/inc/lib/vendor/tightenco/collect/composer.json deleted file mode 100644 index 949635dcd..000000000 --- a/data/web/inc/lib/vendor/tightenco/collect/composer.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "tightenco/collect", - "description": "Collect - Illuminate Collections as a separate package.", - "keywords": ["laravel", "collection"], - "license": "MIT", - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylorotwell@gmail.com" - } - ], - "require": { - "php": "^8.0", - "symfony/var-dumper": "^3.4 || ^4.0 || ^5.0 || ^6.0" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^8.3", - "nesbot/carbon": "^2.23.0" - }, - "autoload": { - "files": [ - "src/Collect/Support/helpers.php", - "src/Collect/Support/alias.php" - ], - "psr-4": { - "Tightenco\\Collect\\": "src/Collect" - } - }, - "autoload-dev": { - "files": [ - "tests/files/Support/Carbon.php", - "tests/files/Support/HtmlString.php", - "tests/files/Support/HigherOrderTapProxy.php", - "tests/files/Support/Str.php", - "tests/files/Support/Stringable.php", - "tests/files/Support/ItemNotFoundException.php", - "tests/files/Support/MultipleItemsFoundException.php", - "tests/Support/Concerns/CountsEnumerations.php" - ] - }, - "scripts": { - "test": [ - "@composer install", - "phpunit" - ] - }, - "minimum-stability": "dev", - "prefer-stable": true -} diff --git a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Contracts/Support/Arrayable.php b/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Contracts/Support/Arrayable.php deleted file mode 100755 index 190bb9bc3..000000000 --- a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Contracts/Support/Arrayable.php +++ /dev/null @@ -1,17 +0,0 @@ - - */ - public function toArray(); -} diff --git a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Contracts/Support/CanBeEscapedWhenCastToString.php b/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Contracts/Support/CanBeEscapedWhenCastToString.php deleted file mode 100644 index 6f3ba0025..000000000 --- a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Contracts/Support/CanBeEscapedWhenCastToString.php +++ /dev/null @@ -1,14 +0,0 @@ - $needles - * @param bool $ignoreCase - * @return bool - */ - public static function contains($haystack, $needles, $ignoreCase = false) - { - if ($ignoreCase) { - $haystack = mb_strtolower($haystack); - } - - if (! is_iterable($needles)) { - $needles = (array) $needles; - } - - foreach ($needles as $needle) { - if ($ignoreCase) { - $needle = mb_strtolower($needle); - } - - if ($needle !== '' && str_contains($haystack, $needle)) { - return true; - } - } - - return false; - } - - /** - * Determine if a given string contains all array values. - * - * @param string $haystack - * @param iterable $needles - * @param bool $ignoreCase - * @return bool - */ - public static function containsAll($haystack, $needles, $ignoreCase = false) - { - foreach ($needles as $needle) { - if (! static::contains($haystack, $needle, $ignoreCase)) { - return false; - } - } - - return true; - } - - /** - * Determine if a given string ends with a given substring. - * - * @param string $haystack - * @param string|iterable $needles - * @return bool - */ - public static function endsWith($haystack, $needles) - { - if (! is_iterable($needles)) { - $needles = (array) $needles; - } - - foreach ($needles as $needle) { - if ((string) $needle !== '' && str_ends_with($haystack, $needle)) { - return true; - } - } - - return false; - } - - /** - * Extracts an excerpt from text that matches the first instance of a phrase. - * - * @param string $text - * @param string $phrase - * @param array $options - * @return string|null - */ - public static function excerpt($text, $phrase = '', $options = []) - { - $radius = $options['radius'] ?? 100; - $omission = $options['omission'] ?? '...'; - - preg_match('/^(.*?)('.preg_quote((string) $phrase).')(.*)$/iu', (string) $text, $matches); - - if (empty($matches)) { - return null; - } - - $start = ltrim($matches[1]); - - $start = str(mb_substr($start, max(mb_strlen($start, 'UTF-8') - $radius, 0), $radius, 'UTF-8'))->ltrim()->unless( - fn ($startWithRadius) => $startWithRadius->exactly($start), - fn ($startWithRadius) => $startWithRadius->prepend($omission), - ); - - $end = rtrim($matches[3]); - - $end = str(mb_substr($end, 0, $radius, 'UTF-8'))->rtrim()->unless( - fn ($endWithRadius) => $endWithRadius->exactly($end), - fn ($endWithRadius) => $endWithRadius->append($omission), - ); - - return $start->append($matches[2], $end)->toString(); - } - - /** - * Cap a string with a single instance of a given value. - * - * @param string $value - * @param string $cap - * @return string - */ - public static function finish($value, $cap) - { - $quoted = preg_quote($cap, '/'); - - return preg_replace('/(?:'.$quoted.')+$/u', '', $value).$cap; - } - - /** - * Wrap the string with the given strings. - * - * @param string $value - * @param string $before - * @param string|null $after - * @return string - */ - public static function wrap($value, $before, $after = null) - { - return $before.$value.($after ??= $before); - } - - /** - * Determine if a given string matches a given pattern. - * - * @param string|iterable $pattern - * @param string $value - * @return bool - */ - public static function is($pattern, $value) - { - $value = (string) $value; - - if (! is_iterable($pattern)) { - $pattern = [$pattern]; - } - - foreach ($pattern as $pattern) { - $pattern = (string) $pattern; - - // If the given value 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 === $value) { - 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', $value) === 1) { - return true; - } - } - - return false; - } - - /** - * Determine if a given string is 7 bit ASCII. - * - * @param string $value - * @return bool - */ - public static function isAscii($value) - { - return ASCII::is_ascii((string) $value); - } - - /** - * Determine if a given string is valid JSON. - * - * @param string $value - * @return bool - */ - public static function isJson($value) - { - if (! is_string($value)) { - return false; - } - - try { - json_decode($value, true, 512, JSON_THROW_ON_ERROR); - } catch (JsonException) { - return false; - } - - return true; - } - - /** - * Determine if a given string is a valid UUID. - * - * @param string $value - * @return bool - */ - public static function isUuid($value) - { - if (! is_string($value)) { - return false; - } - - return preg_match('/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iD', $value) > 0; - } - - /** - * Determine if a given string is a valid ULID. - * - * @param string $value - * @return bool - */ - public static function isUlid($value) - { - if (! is_string($value)) { - return false; - } - - return Ulid::isValid($value); - } - - /** - * Convert a string to kebab case. - * - * @param string $value - * @return string - */ - public static function kebab($value) - { - return static::snake($value, '-'); - } - - /** - * Return the length of the given string. - * - * @param string $value - * @param string|null $encoding - * @return int - */ - public static function length($value, $encoding = null) - { - if ($encoding) { - return mb_strlen($value, $encoding); - } - - return mb_strlen($value); - } - - /** - * Limit the number of characters in a string. - * - * @param string $value - * @param int $limit - * @param string $end - * @return string - */ - public static function limit($value, $limit = 100, $end = '...') - { - if (mb_strwidth($value, 'UTF-8') <= $limit) { - return $value; - } - - return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')).$end; - } - - /** - * Convert the given string to lower-case. - * - * @param string $value - * @return string - */ - public static function lower($value) - { - return mb_strtolower($value, 'UTF-8'); - } - - /** - * Limit the number of words in a string. - * - * @param string $value - * @param int $words - * @param string $end - * @return string - */ - public static function words($value, $words = 100, $end = '...') - { - preg_match('/^\s*+(?:\S++\s*+){1,'.$words.'}/u', $value, $matches); - - if (! isset($matches[0]) || static::length($value) === static::length($matches[0])) { - return $value; - } - - return rtrim($matches[0]).$end; - } - - /** - * Converts GitHub flavored Markdown into HTML. - * - * @param string $string - * @param array $options - * @return string - */ - public static function markdown($string, array $options = []) - { - $converter = new GithubFlavoredMarkdownConverter($options); - - return (string) $converter->convert($string); - } - - /** - * Converts inline Markdown into HTML. - * - * @param string $string - * @param array $options - * @return string - */ - public static function inlineMarkdown($string, array $options = []) - { - $environment = new Environment($options); - - $environment->addExtension(new GithubFlavoredMarkdownExtension()); - $environment->addExtension(new InlinesOnlyExtension()); - - $converter = new MarkdownConverter($environment); - - return (string) $converter->convert($string); - } - - /** - * Masks a portion of a string with a repeated character. - * - * @param string $string - * @param string $character - * @param int $index - * @param int|null $length - * @param string $encoding - * @return string - */ - public static function mask($string, $character, $index, $length = null, $encoding = 'UTF-8') - { - if ($character === '') { - return $string; - } - - $segment = mb_substr($string, $index, $length, $encoding); - - if ($segment === '') { - return $string; - } - - $strlen = mb_strlen($string, $encoding); - $startIndex = $index; - - if ($index < 0) { - $startIndex = $index < -$strlen ? 0 : $strlen + $index; - } - - $start = mb_substr($string, 0, $startIndex, $encoding); - $segmentLen = mb_strlen($segment, $encoding); - $end = mb_substr($string, $startIndex + $segmentLen); - - return $start.str_repeat(mb_substr($character, 0, 1, $encoding), $segmentLen).$end; - } - - /** - * Get the string matching the given pattern. - * - * @param string $pattern - * @param string $subject - * @return string - */ - public static function match($pattern, $subject) - { - preg_match($pattern, $subject, $matches); - - if (! $matches) { - return ''; - } - - return $matches[1] ?? $matches[0]; - } - - /** - * Get the string matching the given pattern. - * - * @param string $pattern - * @param string $subject - * @return \Tightenco\Collect\Support\Collection - */ - public static function matchAll($pattern, $subject) - { - preg_match_all($pattern, $subject, $matches); - - if (empty($matches[0])) { - return collect(); - } - - return collect($matches[1] ?? $matches[0]); - } - - /** - * Pad both sides of a string with another. - * - * @param string $value - * @param int $length - * @param string $pad - * @return string - */ - public static function padBoth($value, $length, $pad = ' ') - { - $short = max(0, $length - mb_strlen($value)); - $shortLeft = floor($short / 2); - $shortRight = ceil($short / 2); - - return mb_substr(str_repeat($pad, $shortLeft), 0, $shortLeft). - $value. - mb_substr(str_repeat($pad, $shortRight), 0, $shortRight); - } - - /** - * Pad the left side of a string with another. - * - * @param string $value - * @param int $length - * @param string $pad - * @return string - */ - public static function padLeft($value, $length, $pad = ' ') - { - $short = max(0, $length - mb_strlen($value)); - - return mb_substr(str_repeat($pad, $short), 0, $short).$value; - } - - /** - * Pad the right side of a string with another. - * - * @param string $value - * @param int $length - * @param string $pad - * @return string - */ - public static function padRight($value, $length, $pad = ' ') - { - $short = max(0, $length - mb_strlen($value)); - - return $value.mb_substr(str_repeat($pad, $short), 0, $short); - } - - /** - * Parse a Class[@]method style callback into class and method. - * - * @param string $callback - * @param string|null $default - * @return array - */ - public static function parseCallback($callback, $default = null) - { - return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default]; - } - - /** - * Get the plural form of an English word. - * - * @param string $value - * @param int|array|\Countable $count - * @return string - */ - public static function plural($value, $count = 2) - { - return Pluralizer::plural($value, $count); - } - - /** - * Pluralize the last word of an English, studly caps case string. - * - * @param string $value - * @param int|array|\Countable $count - * @return string - */ - public static function pluralStudly($value, $count = 2) - { - $parts = preg_split('/(.)(?=[A-Z])/u', $value, -1, PREG_SPLIT_DELIM_CAPTURE); - - $lastWord = array_pop($parts); - - return implode('', $parts).self::plural($lastWord, $count); - } - - /** - * Generate a more truly "random" alpha-numeric string. - * - * @param int $length - * @return string - */ - public static function random($length = 16) - { - return (static::$randomStringFactory ?? function ($length) { - $string = ''; - - while (($len = strlen($string)) < $length) { - $size = $length - $len; - - $bytesSize = (int) ceil($size / 3) * 3; - - $bytes = random_bytes($bytesSize); - - $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); - } - - return $string; - })($length); - } - - /** - * Set the callable that will be used to generate random strings. - * - * @param callable|null $factory - * @return void - */ - public static function createRandomStringsUsing(callable $factory = null) - { - static::$randomStringFactory = $factory; - } - - /** - * Set the sequence that will be used to generate random strings. - * - * @param array $sequence - * @param callable|null $whenMissing - * @return void - */ - public static function createRandomStringsUsingSequence(array $sequence, $whenMissing = null) - { - $next = 0; - - $whenMissing ??= function ($length) use (&$next) { - $factoryCache = static::$randomStringFactory; - - static::$randomStringFactory = null; - - $randomString = static::random($length); - - static::$randomStringFactory = $factoryCache; - - $next++; - - return $randomString; - }; - - static::createRandomStringsUsing(function ($length) use (&$next, $sequence, $whenMissing) { - if (array_key_exists($next, $sequence)) { - return $sequence[$next++]; - } - - return $whenMissing($length); - }); - } - - /** - * Indicate that random strings should be created normally and not using a custom factory. - * - * @return void - */ - public static function createRandomStringsNormally() - { - static::$randomStringFactory = null; - } - - /** - * Repeat the given string. - * - * @param string $string - * @param int $times - * @return string - */ - public static function repeat(string $string, int $times) - { - return str_repeat($string, $times); - } - - /** - * Replace a given value in the string sequentially with an array. - * - * @param string $search - * @param iterable $replace - * @param string $subject - * @return string - */ - public static function replaceArray($search, $replace, $subject) - { - if ($replace instanceof Traversable) { - $replace = collect($replace)->all(); - } - - $segments = explode($search, $subject); - - $result = array_shift($segments); - - foreach ($segments as $segment) { - $result .= (array_shift($replace) ?? $search).$segment; - } - - return $result; - } - - /** - * Replace the given value in the given string. - * - * @param string|iterable $search - * @param string|iterable $replace - * @param string|iterable $subject - * @return string - */ - public static function replace($search, $replace, $subject) - { - if ($search instanceof Traversable) { - $search = collect($search)->all(); - } - - if ($replace instanceof Traversable) { - $replace = collect($replace)->all(); - } - - if ($subject instanceof Traversable) { - $subject = collect($subject)->all(); - } - - return str_replace($search, $replace, $subject); - } - - /** - * Replace the first occurrence of a given value in the string. - * - * @param string $search - * @param string $replace - * @param string $subject - * @return string - */ - public static function replaceFirst($search, $replace, $subject) - { - $search = (string) $search; - - if ($search === '') { - return $subject; - } - - $position = strpos($subject, $search); - - if ($position !== false) { - return substr_replace($subject, $replace, $position, strlen($search)); - } - - return $subject; - } - - /** - * Replace the last occurrence of a given value in the string. - * - * @param string $search - * @param string $replace - * @param string $subject - * @return string - */ - public static function replaceLast($search, $replace, $subject) - { - if ($search === '') { - return $subject; - } - - $position = strrpos($subject, $search); - - if ($position !== false) { - return substr_replace($subject, $replace, $position, strlen($search)); - } - - return $subject; - } - - /** - * Remove any occurrence of the given string in the subject. - * - * @param string|iterable $search - * @param string $subject - * @param bool $caseSensitive - * @return string - */ - public static function remove($search, $subject, $caseSensitive = true) - { - if ($search instanceof Traversable) { - $search = collect($search)->all(); - } - - $subject = $caseSensitive - ? str_replace($search, '', $subject) - : str_ireplace($search, '', $subject); - - return $subject; - } - - /** - * Reverse the given string. - * - * @param string $value - * @return string - */ - public static function reverse(string $value) - { - return implode(array_reverse(mb_str_split($value))); - } - - /** - * Begin a string with a single instance of a given value. - * - * @param string $value - * @param string $prefix - * @return string - */ - public static function start($value, $prefix) - { - $quoted = preg_quote($prefix, '/'); - - return $prefix.preg_replace('/^(?:'.$quoted.')+/u', '', $value); - } - - /** - * Convert the given string to upper-case. - * - * @param string $value - * @return string - */ - public static function upper($value) - { - return mb_strtoupper($value, 'UTF-8'); - } - - /** - * Convert the given string to title case. - * - * @param string $value - * @return string - */ - public static function title($value) - { - return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); - } - - /** - * Convert the given string to title case for each word. - * - * @param string $value - * @return string - */ - public static function headline($value) - { - $parts = explode(' ', $value); - - $parts = count($parts) > 1 - ? array_map([static::class, 'title'], $parts) - : array_map([static::class, 'title'], static::ucsplit(implode('_', $parts))); - - $collapsed = static::replace(['-', '_', ' '], '_', implode('_', $parts)); - - return implode(' ', array_filter(explode('_', $collapsed))); - } - - /** - * Get the singular form of an English word. - * - * @param string $value - * @return string - */ - public static function singular($value) - { - return Pluralizer::singular($value); - } - - /** - * Generate a URL friendly "slug" from a given string. - * - * @param string $title - * @param string $separator - * @param string|null $language - * @param array $dictionary - * @return string - */ - public static function slug($title, $separator = '-', $language = 'en', $dictionary = ['@' => 'at']) - { - $title = $language ? static::ascii($title, $language) : $title; - - // Convert all dashes/underscores into separator - $flip = $separator === '-' ? '_' : '-'; - - $title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title); - - // Replace dictionary words - foreach ($dictionary as $key => $value) { - $dictionary[$key] = $separator.$value.$separator; - } - - $title = str_replace(array_keys($dictionary), array_values($dictionary), $title); - - // Remove all characters that are not the separator, letters, numbers, or whitespace - $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', static::lower($title)); - - // Replace all separator characters and whitespace by a single separator - $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title); - - return trim($title, $separator); - } - - /** - * Convert a string to snake case. - * - * @param string $value - * @param string $delimiter - * @return string - */ - public static function snake($value, $delimiter = '_') - { - $key = $value; - - if (isset(static::$snakeCache[$key][$delimiter])) { - return static::$snakeCache[$key][$delimiter]; - } - - if (! ctype_lower($value)) { - $value = preg_replace('/\s+/u', '', ucwords($value)); - - $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value)); - } - - return static::$snakeCache[$key][$delimiter] = $value; - } - - /** - * Remove all "extra" blank space from the given string. - * - * @param string $value - * @return string - */ - public static function squish($value) - { - return preg_replace('~(\s|\x{3164})+~u', ' ', preg_replace('~^[\s\x{FEFF}]+|[\s\x{FEFF}]+$~u', '', $value)); - } - - /** - * Determine if a given string starts with a given substring. - * - * @param string $haystack - * @param string|iterable $needles - * @return bool - */ - public static function startsWith($haystack, $needles) - { - if (! is_iterable($needles)) { - $needles = [$needles]; - } - - foreach ($needles as $needle) { - if ((string) $needle !== '' && str_starts_with($haystack, $needle)) { - return true; - } - } - - return false; - } - - /** - * Convert a value to studly caps case. - * - * @param string $value - * @return string - */ - public static function studly($value) - { - $key = $value; - - if (isset(static::$studlyCache[$key])) { - return static::$studlyCache[$key]; - } - - $words = explode(' ', static::replace(['-', '_'], ' ', $value)); - - $studlyWords = array_map(fn ($word) => static::ucfirst($word), $words); - - return static::$studlyCache[$key] = implode($studlyWords); - } - - /** - * Returns the portion of the string specified by the start and length parameters. - * - * @param string $string - * @param int $start - * @param int|null $length - * @param string $encoding - * @return string - */ - public static function substr($string, $start, $length = null, $encoding = 'UTF-8') - { - return mb_substr($string, $start, $length, $encoding); - } - - /** - * Returns the number of substring occurrences. - * - * @param string $haystack - * @param string $needle - * @param int $offset - * @param int|null $length - * @return int - */ - public static function substrCount($haystack, $needle, $offset = 0, $length = null) - { - if (! is_null($length)) { - return substr_count($haystack, $needle, $offset, $length); - } - - return substr_count($haystack, $needle, $offset); - } - - /** - * Replace text within a portion of a string. - * - * @param string|string[] $string - * @param string|string[] $replace - * @param int|int[] $offset - * @param int|int[]|null $length - * @return string|string[] - */ - public static function substrReplace($string, $replace, $offset = 0, $length = null) - { - if ($length === null) { - $length = strlen($string); - } - - return substr_replace($string, $replace, $offset, $length); - } - - /** - * Swap multiple keywords in a string with other keywords. - * - * @param array $map - * @param string $subject - * @return string - */ - public static function swap(array $map, $subject) - { - return strtr($subject, $map); - } - - /** - * Make a string's first character lowercase. - * - * @param string $string - * @return string - */ - public static function lcfirst($string) - { - return static::lower(static::substr($string, 0, 1)).static::substr($string, 1); - } - - /** - * Make a string's first character uppercase. - * - * @param string $string - * @return string - */ - public static function ucfirst($string) - { - return static::upper(static::substr($string, 0, 1)).static::substr($string, 1); - } - - /** - * Split a string into pieces by uppercase characters. - * - * @param string $string - * @return string[] - */ - public static function ucsplit($string) - { - return preg_split('/(?=\p{Lu})/u', $string, -1, PREG_SPLIT_NO_EMPTY); - } - - /** - * Get the number of words a string contains. - * - * @param string $string - * @param string|null $characters - * @return int - */ - public static function wordCount($string, $characters = null) - { - return str_word_count($string, 0, $characters); - } - - /** - * Generate a UUID (version 4). - * - * @return \Ramsey\Uuid\UuidInterface - */ - public static function uuid() - { - return static::$uuidFactory - ? call_user_func(static::$uuidFactory) - : Uuid::uuid4(); - } - - /** - * Generate a time-ordered UUID (version 4). - * - * @return \Ramsey\Uuid\UuidInterface - */ - public static function orderedUuid() - { - if (static::$uuidFactory) { - return call_user_func(static::$uuidFactory); - } - - $factory = new UuidFactory; - - $factory->setRandomGenerator(new CombGenerator( - $factory->getRandomGenerator(), - $factory->getNumberConverter() - )); - - $factory->setCodec(new TimestampFirstCombCodec( - $factory->getUuidBuilder() - )); - - return $factory->uuid4(); - } - - /** - * Set the callable that will be used to generate UUIDs. - * - * @param callable|null $factory - * @return void - */ - public static function createUuidsUsing(callable $factory = null) - { - static::$uuidFactory = $factory; - } - - /** - * Set the sequence that will be used to generate UUIDs. - * - * @param array $sequence - * @param callable|null $whenMissing - * @return void - */ - public static function createUuidsUsingSequence(array $sequence, $whenMissing = null) - { - $next = 0; - - $whenMissing ??= function () use (&$next) { - $factoryCache = static::$uuidFactory; - - static::$uuidFactory = null; - - $uuid = static::uuid(); - - static::$uuidFactory = $factoryCache; - - $next++; - - return $uuid; - }; - - static::createUuidsUsing(function () use (&$next, $sequence, $whenMissing) { - if (array_key_exists($next, $sequence)) { - return $sequence[$next++]; - } - - return $whenMissing(); - }); - } - - /** - * Always return the same UUID when generating new UUIDs. - * - * @param \Closure|null $callback - * @return \Ramsey\Uuid\UuidInterface - */ - public static function freezeUuids(Closure $callback = null) - { - $uuid = Str::uuid(); - - Str::createUuidsUsing(fn () => $uuid); - - if ($callback !== null) { - try { - $callback($uuid); - } finally { - Str::createUuidsNormally(); - } - } - - return $uuid; - } - - /** - * Indicate that UUIDs should be created normally and not using a custom factory. - * - * @return void - */ - public static function createUuidsNormally() - { - static::$uuidFactory = null; - } - - /** - * Generate a ULID. - * - * @return \Symfony\Component\Uid\Ulid - */ - public static function ulid() - { - return new Ulid(); - } - - /** - * Remove all strings from the casing caches. - * - * @return void - */ - public static function flushCache() - { - static::$snakeCache = []; - static::$camelCache = []; - static::$studlyCache = []; - } -} diff --git a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/Traits/Tappable.php b/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/Traits/Tappable.php deleted file mode 100644 index 9d75d26fb..000000000 --- a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/Traits/Tappable.php +++ /dev/null @@ -1,17 +0,0 @@ - Illuminate\Contracts\Support\Arrayable::class, - Tightenco\Collect\Contracts\Support\Jsonable::class => Illuminate\Contracts\Support\Jsonable::class, - Tightenco\Collect\Contracts\Support\Htmlable::class => Illuminate\Contracts\Support\Htmlable::class, - Tightenco\Collect\Contracts\Support\CanBeEscapedWhenCastToString::class => Illuminate\Contracts\Support\CanBeEscapedWhenCastToString::class, - Tightenco\Collect\Support\Arr::class => Illuminate\Support\Arr::class, - Tightenco\Collect\Support\Collection::class => Illuminate\Support\Collection::class, - Tightenco\Collect\Support\Enumerable::class => Illuminate\Support\Enumerable::class, - Tightenco\Collect\Support\HigherOrderCollectionProxy::class => Illuminate\Support\HigherOrderCollectionProxy::class, - Tightenco\Collect\Support\LazyCollection::class => Illuminate\Support\LazyCollection::class, - Tightenco\Collect\Support\Traits\EnumeratesValues::class => Illuminate\Support\Traits\EnumeratesValues::class, -]; - -# echo "\n\n-- Aliasing....\n---------------------------------------------\n\n"; - -foreach ($aliases as $tighten => $illuminate) { - if (! class_exists($illuminate) && ! interface_exists($illuminate) && ! trait_exists($illuminate)) { - # echo "Aliasing {$tighten} to {$illuminate}.\n"; - class_alias($tighten, $illuminate); - } -} diff --git a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/helpers.php b/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/helpers.php deleted file mode 100644 index 886a1414c..000000000 --- a/data/web/inc/lib/vendor/tightenco/collect/src/Collect/Support/helpers.php +++ /dev/null @@ -1,122 +0,0 @@ - $segment) { - unset($key[$i]); - - if (is_null($segment)) { - return $target; - } - - if ($segment === '*') { - if ($target instanceof Collection) { - $target = $target->all(); - } elseif (! is_array($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('tap')) { - /** - * Call the given Closure with the given value then return the value. - * - * @param mixed $value - * @param callable|null $callback - * @return mixed - */ - function tap($value, $callback = null) - { - if (is_null($callback)) { - return new HigherOrderTapProxy($value); - } - - $callback($value); - - return $value; - } - } - - if (! function_exists('class_basename')) { - /** - * Get the class "basename" of the given object / class. - * - * @param string|object $class - * @return string - */ - function class_basename($class) - { - $class = is_object($class) ? get_class($class) : $class; - - return basename(str_replace('\\', '/', $class)); - } - } -} diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index deb5da8fa..5e57a4d41 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -47,7 +47,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/CSSminifierExtended.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/array_merge_real.php'; // T/HOTP API -$qrprovider = new RobThree\Auth\Providers\Qr\QRServerProvider(); +$qrprovider = new RobThree\Auth\Providers\Qr\BaconQrCodeProvider(); $tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL, 6, 30, 'sha1', $qrprovider); // FIDO2 @@ -121,7 +121,7 @@ class mailcowPdo extends OAuth2\Storage\Pdo { $this->config['user_table'] = 'mailbox'; } public function checkUserCredentials($username, $password) { - if (check_login($username, $password) == 'user') { + if (check_login($username, $password, array("role" => "user", "service" => "NONE")) == 'user') { return true; } return false; diff --git a/data/web/inc/sessions.inc.php b/data/web/inc/sessions.inc.php index bbc08cf13..ac308b687 100644 --- a/data/web/inc/sessions.inc.php +++ b/data/web/inc/sessions.inc.php @@ -1,7 +1,9 @@ 'warning', - 'msg' => 'session_token' - ); - 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(); } - unset($_POST['csrf_token']); - $_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32)); - $_SESSION['CSRF']['TIME'] = time(); } return true; } diff --git a/data/web/inc/triggers.admin.inc.php b/data/web/inc/triggers.admin.inc.php index df46a459c..92043190e 100644 --- a/data/web/inc/triggers.admin.inc.php +++ b/data/web/inc/triggers.admin.inc.php @@ -9,6 +9,11 @@ if (isset($_POST["verify_tfa_login"])) { unset($_SESSION['pending_mailcow_cc_role']); unset($_SESSION['pending_tfa_methods']); + // 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(); } @@ -42,14 +47,28 @@ if (isset($_GET["cancel_tfa_login"])) { 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"], false, array("role" => "admin")); + $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(); } diff --git a/data/web/inc/triggers.domainadmin.inc.php b/data/web/inc/triggers.domainadmin.inc.php index a9f913688..2eee8b993 100644 --- a/data/web/inc/triggers.domainadmin.inc.php +++ b/data/web/inc/triggers.domainadmin.inc.php @@ -20,6 +20,11 @@ if (isset($_POST["verify_tfa_login"])) { unset($_SESSION['pending_mailcow_cc_role']); unset($_SESSION['pending_tfa_methods']); + // 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(); } @@ -53,14 +58,28 @@ if (isset($_GET["cancel_tfa_login"])) { 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"], false, array("role" => "domain_admin")); + $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(); } diff --git a/data/web/inc/triggers.global.inc.php b/data/web/inc/triggers.global.inc.php index dd88fad56..1f8f92004 100644 --- a/data/web/inc/triggers.global.inc.php +++ b/data/web/inc/triggers.global.inc.php @@ -36,7 +36,26 @@ if (isset($_SESSION['mailcow_cc_role']) && (isset($_SESSION['acl']['login_as']) if (isset($_SESSION['mailcow_cc_role'])) { if (isset($_POST["set_tfa"])) { + $had_pending_tfa_setup = !empty($_SESSION['pending_tfa_setup']); set_tfa($_POST); + // After TFA setup during forced enrollment + if ($had_pending_tfa_setup && empty($_SESSION['pending_tfa_setup'])) { + if ($_SESSION['mailcow_cc_role'] === 'admin') { + header("Location: /admin/dashboard"); + } elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') { + header("Location: /domainadmin/mailbox"); + } elseif ($_SESSION['mailcow_cc_role'] === 'user') { + // Check if user should go to SOGo or /user + $user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']); + $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false; + if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual && getenv('SKIP_SOGO') != "y") { + header("Location: /SOGo/so/"); + } else { + header("Location: /user"); + } + } + exit(); + } } if (isset($_POST["unset_tfa_key"])) { unset_tfa_key($_POST); diff --git a/data/web/inc/triggers.user.inc.php b/data/web/inc/triggers.user.inc.php index 4dee75a37..5dcde0b18 100644 --- a/data/web/inc/triggers.user.inc.php +++ b/data/web/inc/triggers.user.inc.php @@ -76,11 +76,16 @@ if (isset($_POST["verify_tfa_login"])) { $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/{$_SESSION['mailcow_cc_username']}"); + header("Location: /SOGo/so/"); die(); } else { header("Location: /user"); @@ -117,9 +122,18 @@ if (isset($_GET["cancel_tfa_login"])) { 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"], false, array("role" => "user")); + $as = check_login($login_user, $_POST["pass_user"], array("role" => "user", "service" => "MAILCOWUI")); if ($as == "user") { set_user_loggedin_session($login_user); @@ -142,11 +156,16 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { $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/{$login_user}"); + header("Location: /SOGo/so/"); die(); } else { header("Location: /user"); diff --git a/data/web/inc/twig.inc.php b/data/web/inc/twig.inc.php index a3bc02d92..c4d0669d9 100644 --- a/data/web/inc/twig.inc.php +++ b/data/web/inc/twig.inc.php @@ -13,7 +13,9 @@ $twig = new Environment($loader, [ // functions $twig->addFunction(new TwigFunction('query_string', function (array $params = []) { - return http_build_query(array_merge($_GET, $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) { diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index 568105308..cec402a96 100644 --- a/data/web/inc/vars.inc.php +++ b/data/web/inc/vars.inc.php @@ -33,6 +33,8 @@ if ($https_port === FALSE) { //$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. @@ -85,7 +87,7 @@ $AVAILABLE_LANGUAGES = array( // 'ca-es' => 'Català (Catalan)', 'bg-bg' => 'Български (Bulgarian)', 'cs-cz' => 'Čeština (Czech)', - 'da-dk' => 'Danish (Dansk)', + 'da-dk' => 'Dansk (Danish)', 'de-de' => 'Deutsch (German)', 'en-gb' => 'English', 'es-es' => 'Español (Spanish)', @@ -110,6 +112,8 @@ $AVAILABLE_LANGUAGES = array( '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)', ); @@ -153,6 +157,13 @@ $LOG_PAGINATION_SIZE = 50; // Session lifetime in seconds $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"; @@ -183,6 +194,9 @@ $MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out'] = false; // 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; @@ -207,6 +221,12 @@ $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 diff --git a/data/web/index.php b/data/web/index.php index d21e54364..b037c14f1 100644 --- a/data/web/index.php +++ b/data/web/index.php @@ -9,21 +9,33 @@ if (isset($_SESSION['mailcow_cc_role']) && isset($_SESSION['oauth2_request'])) { exit(); } elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') { - $user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']); - $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false; - if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual && getenv('SKIP_SOGO') != "y") { - header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}"); - } else { - header("Location: /user"); + if (empty($_SESSION['pending_tfa_setup']) && empty($_SESSION['pending_pw_update'])) { + $user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']); + $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false; + if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual && getenv('SKIP_SOGO') != "y") { + header("Location: /SOGo/so/"); + } else { + header("Location: /user"); + } + exit(); } - exit(); } elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') { - header('Location: /admin/dashboard'); - exit(); + if (empty($_SESSION['pending_tfa_setup']) && empty($_SESSION['pending_pw_update'])) { + header('Location: /admin/dashboard'); + exit(); + } } elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') { - header('Location: /domainadmin/mailbox'); + if (empty($_SESSION['pending_tfa_setup']) && empty($_SESSION['pending_pw_update'])) { + header('Location: /domainadmin/mailbox'); + exit(); + } +} + +$host = strtolower($_SERVER['HTTP_HOST'] ?? ''); +if (str_starts_with($host, 'autodiscover.') || str_starts_with($host, 'autoconfig.')) { + http_response_code(404); exit(); } diff --git a/data/web/js/build/013-mailcow.js b/data/web/js/build/013-mailcow.js index f09098310..e4893d7ac 100644 --- a/data/web/js/build/013-mailcow.js +++ b/data/web/js/build/013-mailcow.js @@ -22,8 +22,8 @@ $(document).ready(function() { $.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}}); } - $(".generate_password").click(async function( event ) { - try { + $(".generate_password").click(async function( event ) { + try { var password_policy = await window.fetch("/api/v1/get/passwordpolicy", { method:'GET', cache:'no-cache' }); var password_policy = await password_policy.json(); random_passwd_length = password_policy.length; @@ -48,7 +48,11 @@ $(document).ready(function() { }) } $(".rot-enc").html(function(){ - return str_rot13($(this).html()) + footer_html = $(this).html(); + footer_html = footer_html.replace(/</g, '<').replace(/>/g, '>') + .replace(/&/g, '&').replace(/&nzc;/g, '&') + .replace(/"/g, '"').replace(/'/g, "'"); + return str_rot13(footer_html) }); // https://stackoverflow.com/questions/4399005/implementing-jquerys-shake-effect-with-animate function shake(div,interval,distance,times) { @@ -125,7 +129,7 @@ $(document).ready(function() { } }); })(); - + // responsive tabs, scroll to opened tab $(document).on("shown.bs.collapse shown.bs.tab", function (e) { var target = $(e.target); @@ -341,7 +345,7 @@ $(document).ready(function() { $('.main-logo-dark').addClass('d-none'); if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_dark.png'); if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_dark.png'); - localStorage.setItem('theme', 'light'); + localStorage.setItem('mailcow_theme', 'light'); }else{ $('head').append(''); $('#dark-mode-toggle').prop('checked', true); @@ -349,7 +353,7 @@ $(document).ready(function() { $('.main-logo-dark').removeClass('d-none'); if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_light.png'); if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_light.png'); - localStorage.setItem('theme', 'dark'); + localStorage.setItem('mailcow_theme', 'dark'); } } @@ -409,4 +413,4 @@ function copyToClipboard(id) { // only works with https connections navigator.clipboard.writeText(copyText.value); mailcow_alert_box(lang.copy_to_clipboard, "success"); -} \ No newline at end of file +} diff --git a/data/web/js/site/admin.js b/data/web/js/site/admin.js index f4258ff48..91bb0e4cf 100644 --- a/data/web/js/site/admin.js +++ b/data/web/js/site/admin.js @@ -54,7 +54,16 @@ jQuery(function($){ $.get("/inc/ajax/show_rspamd_global_filters.php"); $("#confirm_show_rspamd_global_filters").hide(); $("#rspamd_global_filters").removeClass("d-none"); + localStorage.setItem('rspamd_global_filters_confirmed', 'true'); }); + + $(document).ready(function() { + if (localStorage.getItem('rspamd_global_filters_confirmed') === 'true') { + $("#confirm_show_rspamd_global_filters").hide(); + $("#rspamd_global_filters").removeClass("d-none"); + } + }); + $("#super_delete").click(function() { return confirm(lang.queue_ays); }); $(".refresh_table").on('click', function(e) { @@ -715,7 +724,6 @@ jQuery(function($){ $('.app_hide').off('change'); $('.app_hide').on('change', function (e) { var value = $(this).is(':checked') ? '1' : '0'; - console.log(value) $(this).parent().children(':first-child').val(value); }) } diff --git a/data/web/js/site/dashboard.js b/data/web/js/site/dashboard.js index 182a09a5b..aee361710 100644 --- a/data/web/js/site/dashboard.js +++ b/data/web/js/site/dashboard.js @@ -47,8 +47,6 @@ $(document).ready(function() { window.fetch("/api/v1/get/status/host/ip", { method:'GET', cache:'no-cache' }).then(function(response) { return response.json(); }).then(function(data) { - console.log(data); - // display host ips if (data.ipv4) $("#host_ipv4").text(data.ipv4); @@ -1007,7 +1005,7 @@ jQuery(function($){ "data-order": cellData.sortBy, "data-sort": cellData.sortBy }); - }, + }, render: function (data) { return data.value; } @@ -1032,7 +1030,7 @@ jQuery(function($){ "data-order": cellData.sortBy, "data-sort": cellData.sortBy }); - }, + }, render: function (data) { return data.value; } @@ -1130,6 +1128,11 @@ jQuery(function($){ item.ua = escapeHtml(item.ua); } item.ua = '' + item.ua + ''; + if (item.user == null) { + item.user = 'unknown'; + } else { + item.user = escapeHtml(item.user); + } if (item.service == "activesync") { item.service = 'ActiveSync'; } @@ -1348,8 +1351,6 @@ function update_stats(timeout=5){ window.fetch("/api/v1/get/status/host", {method:'GET',cache:'no-cache'}).then(function(response) { return response.json(); }).then(function(data) { - console.log(data); - if (data){ // display table data $("#host_date").text(data.system_time); @@ -1399,8 +1400,6 @@ function update_container_stats(timeout=5){ var diskIOCtx = Chart.getChart(container + "_DiskIOChart"); var netIOCtx = Chart.getChart(container + "_NetIOChart"); - console.log(container); - console.log(data); prev_stats = null; if (data.length >= 2){ prev_stats = data[data.length -2]; diff --git a/data/web/js/site/edit.js b/data/web/js/site/edit.js index f9fe707c6..308ec8c13 100644 --- a/data/web/js/site/edit.js +++ b/data/web/js/site/edit.js @@ -66,7 +66,6 @@ $(document).ready(function() { // load tags if ($('#tags').length){ var tagsEl = $('#tags').parent().find('.tag-values')[0]; - console.log($(tagsEl).val()) var tags = JSON.parse($(tagsEl).val()); $(tagsEl).val(""); diff --git a/data/web/js/site/index.js b/data/web/js/site/index.js index 4c812a377..29b72f0d5 100644 --- a/data/web/js/site/index.js +++ b/data/web/js/site/index.js @@ -1,5 +1,6 @@ $(document).ready(function() { - var theme = localStorage.getItem("theme"); - localStorage.clear(); - localStorage.setItem("theme", theme); + var theme = localStorage.getItem("mailcow_theme"); + if (theme !== null) { + localStorage.setItem("mailcow_theme", theme); + } }); diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index 6a04eb241..0a0192798 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -352,6 +352,12 @@ $(document).ready(function() { if (template.sieve_access == 1){ protocol_access.push("sieve"); } + if (template.eas_access == 1){ + protocol_access.push("eas"); + } + if (template.dav_access == 1){ + protocol_access.push("dav"); + } $('#protocol_access').selectpicker('val', protocol_access); var acl = []; @@ -418,6 +424,11 @@ $(document).ready(function() { } else { $('#force_pw_update').prop('checked', false); } + if (template.force_tfa == 1){ + $('#force_tfa').prop('checked', true); + } else { + $('#force_tfa').prop('checked', false); + } if (template.sogo_access == 1){ $('#sogo_access').prop('checked', true); } else { @@ -465,8 +476,13 @@ jQuery(function($){ hideTableExpandCollapseBtn('#tab-domains', '#domain_table'); }, ajax: { - type: "GET", - url: "/api/v1/get/domain/datatables", + type: "POST", + url: "/api/v1/search/domain", + contentType: "application/json", + processData: false, + data: function(d) { + return JSON.stringify(d); + }, dataSrc: function(json){ $.each(json.data, function(i, item) { item.domain_name = escapeHtml(item.domain_name); @@ -892,8 +908,13 @@ jQuery(function($){ hideTableExpandCollapseBtn('#tab-mailboxes', '#mailbox_table'); }, ajax: { - type: "GET", - url: "/api/v1/get/mailbox/datatables", + type: "POST", + url: "/api/v1/search/mailbox", + contentType: "application/json", + processData: false, + data: function(d) { + return JSON.stringify(d); + }, dataSrc: function(json){ $.each(json.data, function (i, item) { item.quota = { @@ -933,6 +954,8 @@ jQuery(function($){ item.imap_access = ''; item.smtp_access = ''; item.sieve_access = ''; + item.eas_access = ''; + item.dav_access = ''; if (item.attributes.quarantine_notification === 'never') { item.quarantine_notification = lang.never; } else if (item.attributes.quarantine_notification === 'hourly') { @@ -1096,6 +1119,18 @@ jQuery(function($){ defaultContent: '', className: 'none' }, + { + title: 'EAS', + data: 'eas_access', + defaultContent: '', + className: 'none' + }, + { + title: 'DAV', + data: 'dav_access', + defaultContent: '', + className: 'none' + }, { title: lang.quarantine_notification, data: 'quarantine_notification', @@ -1209,7 +1244,10 @@ jQuery(function($){ item.attributes.imap_access = '' + (item.attributes.imap_access == 1 ? '1' : '0') + ''; item.attributes.smtp_access = '' + (item.attributes.smtp_access == 1 ? '1' : '0') + ''; item.attributes.sieve_access = '' + (item.attributes.sieve_access == 1 ? '1' : '0') + ''; + item.attributes.eas_access = '' + (item.attributes.eas_access == 1 ? '1' : '0') + ''; + item.attributes.dav_access = '' + (item.attributes.dav_access == 1 ? '1' : '0') + ''; item.attributes.sogo_access = '' + (item.attributes.sogo_access == 1 ? '1' : '0') + ''; + item.attributes.force_tfa = '' + (item.attributes.force_tfa == 1 ? '1' : '0') + ''; if (item.attributes.quarantine_notification === 'never') { item.attributes.quarantine_notification = lang.never; } else if (item.attributes.quarantine_notification === 'hourly') { @@ -1317,6 +1355,16 @@ jQuery(function($){ data: 'attributes.sieve_access', defaultContent: '', }, + { + title: 'EAS', + data: 'attributes.eas_access', + defaultContent: '', + }, + { + title: 'DAV', + data: 'attributes.dav_access', + defaultContent: '', + }, { title: 'SOGO', data: 'attributes.sogo_access', @@ -1343,6 +1391,11 @@ jQuery(function($){ return 1==data?'':''; } }, + { + title: lang.force_tfa, + data: 'attributes.force_tfa', + defaultContent: '' + }, { title: lang_edit.ratelimit, data: 'attributes.ratelimit', @@ -1949,11 +2002,6 @@ jQuery(function($){ defaultContent: '', responsivePriority: 5, }, - { - title: lang.bcc_destinations, - data: 'bcc_dest', - defaultContent: '' - }, { title: lang.sogo_visible, data: 'sogo_visible', diff --git a/data/web/js/site/quarantine.js b/data/web/js/site/quarantine.js index fbf4fe862..a9b7845bc 100644 --- a/data/web/js/site/quarantine.js +++ b/data/web/js/site/quarantine.js @@ -102,18 +102,21 @@ jQuery(function($){ { title: 'ID', data: 'id', - defaultContent: '' + defaultContent: '', + render: $.fn.dataTable.render.text() }, { title: lang.qid, data: 'qid', - defaultContent: '' + defaultContent: '', + render: $.fn.dataTable.render.text() }, { title: lang.sender, data: 'sender', className: 'senders-mw220', - defaultContent: '' + defaultContent: '', + render: $.fn.dataTable.render.text() }, { title: lang.subj, @@ -128,7 +131,8 @@ jQuery(function($){ { title: lang.rcpt, data: 'rcpt', - defaultContent: '' + defaultContent: '', + render: $.fn.dataTable.render.text() }, { title: lang.danger, @@ -226,18 +230,18 @@ jQuery(function($){ } if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) { $.each(data.fuzzy_hashes, function (index, value) { - $('#qid_detail_fuzzy').append('

    ' + value + '

    '); + $('#qid_detail_fuzzy').append('

    ' + escapeHtml(value) + '

    '); }); } else { $('#qid_detail_fuzzy').append('-'); } if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') { if (data.action == "add header") { - $('#qid_detail_score').append('' + data.score + ' - ' + lang.junk_folder + ''); + $('#qid_detail_score').append('' + escapeHtml(data.score) + ' - ' + lang.junk_folder + ''); } else if (data.action == "reject") { - $('#qid_detail_score').append('' + data.score + ' - ' + lang.rejected + ''); + $('#qid_detail_score').append('' + escapeHtml(data.score) + ' - ' + lang.rejected + ''); } else if (data.action == "rewrite subject") { - $('#qid_detail_score').append('' + data.score + ' - ' + lang.rewrite_subject + ''); + $('#qid_detail_score').append('' + escapeHtml(data.score) + ' - ' + lang.rewrite_subject + ''); } } if (typeof data.recipients !== 'undefined') { @@ -254,8 +258,8 @@ jQuery(function($){ qAtts.text(''); $.each(data.attachments, function(index, value) { qAtts.append( - '

    ' + value[0] + ' (' + value[1] + ')' + - ' - ' + lang.check_hash + '

    ' + '

    ' + escapeHtml(value[0]) + ' (' + escapeHtml(value[1]) + ')' + + ' - ' + lang.check_hash + '

    ' ); }); } diff --git a/data/web/js/site/queue.js b/data/web/js/site/queue.js index 26c15091d..9adc79b5f 100644 --- a/data/web/js/site/queue.js +++ b/data/web/js/site/queue.js @@ -48,13 +48,13 @@ jQuery(function($){ url: "/api/v1/get/mailq/all", dataSrc: function(data){ $.each(data, function (i, item) { - item.chkbox = ''; + item.chkbox = ''; rcpts = $.map(item.recipients, function(i) { return escapeHtml(i); }); item.recipients = rcpts.join('
    '); item.action = ''; }); return data; @@ -79,12 +79,14 @@ jQuery(function($){ { title: 'QID', data: 'queue_id', - defaultContent: '' + defaultContent: '', + render: $.fn.dataTable.render.text() }, { title: 'Queue', data: 'queue_name', - defaultContent: '' + defaultContent: '', + render: $.fn.dataTable.render.text() }, { title: lang_admin.arrival_time, @@ -106,7 +108,8 @@ jQuery(function($){ { title: lang_admin.sender, data: 'sender', - defaultContent: '' + defaultContent: '', + render: $.fn.dataTable.render.text() }, { title: lang_admin.recipients, diff --git a/data/web/js/site/user.js b/data/web/js/site/user.js index 1d227469a..288e7abd2 100644 --- a/data/web/js/site/user.js +++ b/data/web/js/site/user.js @@ -97,18 +97,17 @@ jQuery(function($){ var datetime = new Date(item.datetime.replace(/-/g, "/")); var local_datetime = datetime.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); var service = '
    ' + item.service.toUpperCase() + '
    '; - var app_password = item.app_password ? ' ' + escapeHtml(item.app_password_name || "App") + '' : ''; - var real_rip = item.real_rip.startsWith("Web") ? item.real_rip : '' + item.real_rip + ""; - var ip_location = item.location ? ' ' : ''; + var app_password = item.app_password ? ' ' + escapeHtml(item.app_password_name || "App") + '' : ''; + var real_rip = item.real_rip.startsWith("Web") ? escapeHtml(item.real_rip) : '' + escapeHtml(item.real_rip) + ""; + var ip_location = item.location ? ' ' : ''; var ip_data = real_rip + ip_location + app_password; $(".last-sasl-login").append(`
  • -
    ` + real_rip + `
    - ` + service + ` ` + local_datetime + ` +
    ` + ip_location + real_rip + `
    + ` + service + ` ` + local_datetime + `` + app_password + `
    - ` + ip_location + `
  • `); }) @@ -169,7 +168,6 @@ jQuery(function($){ type: "GET", url: "/api/v1/get/time_limited_aliases", dataSrc: function(data){ - console.log(data); $.each(data, function (i, item) { if (acl_data.spam_alias === 1) { item.action = '
    ' + @@ -177,6 +175,10 @@ jQuery(function($){ '
    '; item.chkbox = ''; item.address = escapeHtml(item.address); + item.validity = { + value: item.validity, + permanent: item.permanent + }; } else { item.chkbox = ''; @@ -220,9 +222,21 @@ jQuery(function($){ title: lang.alias_valid_until, data: 'validity', defaultContent: '', - createdCell: function(td, cellData) { - createSortableDate(td, cellData) - } + render: function (data, type) { + var date = new Date(data.value ? data.value * 1000 : 0); + switch (type) { + case "sort": + if (data.permanent) { + return 0; + } + return date.getTime(); + default: + if (data.permanent) { + return lang.forever; + } + return date.toLocaleDateString(LOCALE, DATETIME_FORMAT); + } + }, }, { title: lang.created_on, @@ -262,7 +276,6 @@ jQuery(function($){ type: "GET", url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log', dataSrc: function(data){ - console.log(data); $.each(data, function (i, item) { item.user1 = escapeHtml(item.user1); item.log = '' + lang.open_logs + '' @@ -418,7 +431,6 @@ jQuery(function($){ type: "GET", url: '/api/v1/get/app-passwd/all', dataSrc: function(data){ - console.log(data); $.each(data, function (i, item) { item.name = escapeHtml(item.name) item.protocols = [] @@ -514,7 +526,6 @@ jQuery(function($){ type: "GET", url: '/api/v1/get/policy_wl_mailbox', dataSrc: function(data){ - console.log(data); $.each(data, function (i, item) { if (validateEmail(item.object)) { item.chkbox = ''; @@ -585,7 +596,6 @@ jQuery(function($){ type: "GET", url: '/api/v1/get/policy_bl_mailbox', dataSrc: function(data){ - console.log(data); $.each(data, function (i, item) { if (validateEmail(item.object)) { item.chkbox = ''; diff --git a/data/web/json_api.php b/data/web/json_api.php index 4d7159ad2..2d315a0b1 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -91,6 +91,11 @@ if (isset($_GET['query'])) { if ($action == 'delete') { $_POST['items'] = $request; } + + // search + if ($action == 'search') { + // placeholder for search, as the request body is already decoded and available in $requestDecoded + } } api_log($_POST); @@ -169,6 +174,8 @@ if (isset($_GET['query'])) { exit; } fido2(array("action" => "register", "registration" => $data)); + // Release pending_tfa_setup session hold + unset($_SESSION['pending_tfa_setup']); $return = new stdClass(); $return->success = true; echo json_encode($return); @@ -457,47 +464,6 @@ if (isset($_GET['query'])) { case "domain": switch ($object) { - case "datatables": - $table = ['domain', 'd']; - $primaryKey = 'domain'; - $columns = [ - ['db' => 'domain', 'dt' => 2], - ['db' => 'aliases', 'dt' => 3, 'order_subquery' => "SELECT COUNT(*) FROM `alias` WHERE (`domain`= `d`.`domain` OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = `d`.`domain`)) AND `address` NOT IN (SELECT `username` FROM `mailbox`)"], - ['db' => 'mailboxes', 'dt' => 4, 'order_subquery' => "SELECT COUNT(*) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"], - ['db' => 'quota', 'dt' => 5, 'order_subquery' => "SELECT COALESCE(SUM(`mailbox`.`quota`), 0) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"], - ['db' => 'stats', 'dt' => 6, 'dummy' => true, 'order_subquery' => "SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` IN (SELECT `username` FROM `mailbox` WHERE `domain` = `d`.`domain`)"], - ['db' => 'defquota', 'dt' => 7], - ['db' => 'maxquota', 'dt' => 8], - ['db' => 'backupmx', 'dt' => 10], - ['db' => 'tags', 'dt' => 14, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_domain` AS `td` ON `td`.`domain` = `d`.`domain`', 'where_column' => '`td`.`tag_name`']], - ['db' => 'active', 'dt' => 15], - ]; - - require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php'; - global $pdo; - if($_SESSION['mailcow_cc_role'] === 'admin') { - $data = SSP::simple($_GET, $pdo, $table, $primaryKey, $columns); - } elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') { - $data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns, - 'INNER JOIN domain_admins as da ON da.domain = d.domain', - [ - 'condition' => 'da.active = 1 and da.username = :username', - 'bindings' => ['username' => $_SESSION['mailcow_cc_username']] - ]); - } - - if (!empty($data['data'])) { - $domainsData = []; - foreach ($data['data'] as $domain) { - if ($details = mailbox('get', 'domain_details', $domain[2])) { - $domainsData[] = $details; - } - } - $data['data'] = $domainsData; - } - - process_get_return($data); - break; case "all": $tags = null; if (isset($_GET['tags']) && $_GET['tags'] != '') @@ -997,46 +963,6 @@ if (isset($_GET['query'])) { break; case "mailbox": switch ($object) { - case "datatables": - $table = ['mailbox', 'm']; - $primaryKey = 'username'; - $columns = [ - ['db' => 'username', 'dt' => 2], - ['db' => 'quota', 'dt' => 3], - ['db' => 'last_mail_login', 'dt' => 4, 'dummy' => true, 'order_subquery' => "SELECT MAX(`datetime`) FROM `sasl_log` WHERE `service` != 'SSO' AND `username` = `m`.`username`"], - ['db' => 'last_pw_change', 'dt' => 5, 'dummy' => true, 'order_subquery' => "JSON_EXTRACT(attributes, '$.passwd_update')"], - ['db' => 'in_use', 'dt' => 6, 'dummy' => true, 'order_subquery' => "(SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`) / `m`.`quota`"], - ['db' => 'name', 'dt' => 7], - ['db' => 'messages', 'dt' => 18, 'dummy' => true, 'order_subquery' => "SELECT SUM(messages) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`"], - ['db' => 'tags', 'dt' => 20, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_mailbox` AS `tm` ON `tm`.`username` = `m`.`username`', 'where_column' => '`tm`.`tag_name`']], - ['db' => 'active', 'dt' => 21], - ]; - - require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php'; - global $pdo; - if($_SESSION['mailcow_cc_role'] === 'admin') { - $data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns, null, "(`m`.`kind` = '' OR `m`.`kind` = NULL)"); - } elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') { - $data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns, - 'INNER JOIN domain_admins as da ON da.domain = m.domain', - [ - 'condition' => "(`m`.`kind` = '' OR `m`.`kind` = NULL) AND `da`.`active` = 1 AND `da`.`username` = :username", - 'bindings' => ['username' => $_SESSION['mailcow_cc_username']] - ]); - } - - if (!empty($data['data'])) { - $mailboxData = []; - foreach ($data['data'] as $mailbox) { - if ($details = mailbox('get', 'mailbox_details', $mailbox[2])) { - $mailboxData[] = $details; - } - } - $data['data'] = $mailboxData; - } - - process_get_return($data); - break; case "all": case "reduced": $tags = null; @@ -1625,6 +1551,136 @@ if (isset($_GET['query'])) { } } break; + case "search": + function process_search_return($return) { + if ($return === false) { + echo json_encode(array( + 'type' => 'error', + 'msg' => 'Cannot get item' + )); + } + else { + echo json_encode($return, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + } + } + // only allow POST requests to SEARCH API endpoints + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + http_response_code(405); + echo json_encode(array( + 'type' => 'error', + 'msg' => 'only POST method is allowed' + )); + exit(); + } + + // Load SSP class + require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php'; + global $pdo; + + switch ($category) { + case "domain": + $table = ['domain', 'd']; + $primaryKey = 'domain'; + $columns = [ + ['db' => 'domain', 'dt' => 2], + ['db' => 'aliases', 'dt' => 3, 'order_subquery' => "SELECT COUNT(*) FROM `alias` WHERE (`domain`= `d`.`domain` OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = `d`.`domain`)) AND `address` NOT IN (SELECT `username` FROM `mailbox`)"], + ['db' => 'mailboxes', 'dt' => 4, 'order_subquery' => "SELECT COUNT(*) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"], + ['db' => 'quota', 'dt' => 5, 'order_subquery' => "SELECT COALESCE(SUM(`mailbox`.`quota`), 0) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"], + ['db' => 'stats', 'dt' => 6, 'dummy' => true, 'order_subquery' => "SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` IN (SELECT `username` FROM `mailbox` WHERE `domain` = `d`.`domain`)"], + ['db' => 'defquota', 'dt' => 7], + ['db' => 'maxquota', 'dt' => 8], + ['db' => 'backupmx', 'dt' => 10], + ['db' => 'tags', 'dt' => 14, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_domain` AS `td` ON `td`.`domain` = `d`.`domain`', 'where_column' => '`td`.`tag_name`']], + ['db' => 'active', 'dt' => 15], + ]; + + if($_SESSION['mailcow_cc_role'] === 'admin') { + $data = SSP::simple($requestDecoded, $pdo, $table, $primaryKey, $columns); + } elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') { + $data = SSP::complex($requestDecoded, $pdo, $table, $primaryKey, $columns, + 'INNER JOIN domain_admins as da ON da.domain = d.domain', + [ + 'condition' => 'da.active = 1 and da.username = :username', + 'bindings' => ['username' => $_SESSION['mailcow_cc_username']] + ]); + } else { + http_response_code(403); + echo json_encode(array( + 'type' => 'error', + 'msg' => 'Insufficient permissions' + )); + exit(); + } + + if (!empty($data['data'])) { + $domainsData = []; + foreach ($data['data'] as $domain) { + if ($details = mailbox('get', 'domain_details', $domain[2])) { + $domainsData[] = $details; + } + } + $data['data'] = $domainsData; + } + + process_search_return($data); + break; + + case "mailbox": + $table = ['mailbox', 'm']; + $primaryKey = 'username'; + $columns = [ + ['db' => 'username', 'dt' => 2], + ['db' => 'quota', 'dt' => 3], + ['db' => 'last_mail_login', 'dt' => 4, 'dummy' => true, 'order_subquery' => "SELECT MAX(`datetime`) FROM `sasl_log` WHERE `service` != 'SSO' AND `username` = `m`.`username`"], + ['db' => 'last_pw_change', 'dt' => 5, 'dummy' => true, 'order_subquery' => "JSON_EXTRACT(attributes, '$.passwd_update')"], + ['db' => 'in_use', 'dt' => 6, 'dummy' => true, 'order_subquery' => "(SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`) / `m`.`quota`"], + ['db' => 'name', 'dt' => 7], + ['db' => 'messages', 'dt' => 20, 'dummy' => true, 'order_subquery' => "SELECT SUM(messages) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`"], + ['db' => 'tags', 'dt' => 23, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_mailbox` AS `tm` ON `tm`.`username` = `m`.`username`', 'where_column' => '`tm`.`tag_name`']], + ['db' => 'active', 'dt' => 24], + ]; + + if($_SESSION['mailcow_cc_role'] === 'admin') { + $data = SSP::complex($requestDecoded, $pdo, $table, $primaryKey, $columns, null, + "(`m`.`kind` = '' OR `m`.`kind` = NULL)"); + } elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') { + $data = SSP::complex($requestDecoded, $pdo, $table, $primaryKey, $columns, + 'INNER JOIN domain_admins as da ON da.domain = m.domain', + [ + 'condition' => "(`m`.`kind` = '' OR `m`.`kind` = NULL) AND `da`.`active` = 1 AND `da`.`username` = :username", + 'bindings' => ['username' => $_SESSION['mailcow_cc_username']] + ]); + } else { + http_response_code(403); + echo json_encode(array( + 'type' => 'error', + 'msg' => 'Insufficient permissions' + )); + exit(); + } + + if (!empty($data['data'])) { + $mailboxData = []; + foreach ($data['data'] as $mailbox) { + if ($details = mailbox('get', 'mailbox_details', $mailbox[2])) { + $mailboxData[] = $details; + } + } + $data['data'] = $mailboxData; + } + + process_search_return($data); + break; + + default: + http_response_code(404); + echo json_encode(array( + 'type' => 'error', + 'msg' => 'Invalid search category' + )); + break; + } + break; case "delete": if ($_SESSION['mailcow_cc_api_access'] == 'ro' || isset($_SESSION['pending_mailcow_cc_username']) || !isset($_SESSION["mailcow_cc_username"])) { http_response_code(403); @@ -1992,6 +2048,7 @@ if (isset($_GET['query'])) { break; case "cors": process_edit_return(cors('edit', $attr)); + break; case "identity-provider": process_edit_return(identity_provider('edit', $attr)); break; diff --git a/data/web/lang/lang.az-az.json b/data/web/lang/lang.az-az.json new file mode 100644 index 000000000..958ed8e2a --- /dev/null +++ b/data/web/lang/lang.az-az.json @@ -0,0 +1,17 @@ +{ + "acl": { + "login_as": "E-poçt qutusu istifadəçisi olaraq daxil ol", + "alias_domains": "Alternativ domenlər əlavə et", + "app_passwds": "Tətbiq parollarını idarə et", + "bcc_maps": "BCC yönləndirmə xəritələri", + "delimiter_action": "Ayırıcı əməliyyatı", + "domain_desc": "Domen təsvirini dəyiş", + "domain_relayhost": "Domen üçün relay serveri dəyiş", + "eas_reset": "EAS cihazlarını sıfırla", + "extend_sender_acl": "Göndərən ACL-ni xarici ünvanlarla genişləndirməyə icazə ver", + "filters": "Filtrlər", + "mailbox_relayhost": "E-poçt qutusu üçün relay serveri dəyiş", + "prohibited": "ACL tərəfindən məhdudlaşdırılıb", + "protocol_access": "Protokol girişini dəyiş" + } +} diff --git a/data/web/lang/lang.ca-es.json b/data/web/lang/lang.ca-es.json index ba84543a2..5b8e9b64d 100644 --- a/data/web/lang/lang.ca-es.json +++ b/data/web/lang/lang.ca-es.json @@ -19,7 +19,16 @@ "spam_alias": "Àlies temporals", "spam_score": "Puntuació de correu brossa", "tls_policy": "Política TLS", - "unlimited_quota": "Quota ilimitada per bústies de correo" + "unlimited_quota": "Quota ilimitada per bústies de correo", + "delimiter_action": "Acció delimitadora", + "domain_relayhost": "Canviar relayhost per un domini", + "extend_sender_acl": "Permetre extendre l'ACL del remitent per adreces externes", + "mailbox_relayhost": "Canvia el host de reenviament per una bústia", + "pushover": "Pushover", + "pw_reset": "Permetre el restabliment de la contrasenya de l'usuari mailcow", + "ratelimit": "Límit de peticions", + "smtp_ip_access": "Canvia hosts permesos per SMTP", + "sogo_access": "Permetre la gestió d'accés a SOGo" }, "add": { "activate_filter_warn": "All other filters will be deactivated, when active is checked.", @@ -73,7 +82,25 @@ "validate": "Validar", "validation_success": "Validated successfully", "app_name": "Nom de l'aplicació", - "app_password": "Afegir contrasenya a l'aplicació" + "app_password": "Afegir contrasenya a l'aplicació", + "app_passwd_protocols": "Protocols autoritzats per la contrasenya de l'aplicació", + "bcc_dest_format": "La destinació c/o ha de ser una única adreça de correu vàlida.
    Si necessiteu enviar una còpia a diverses adreces, creeu un àlies i utilitzeu-lo aquí.", + "comment_info": "Els comentaris privats no són visibles per l'usuari, mentre que els comentaris públics apareixen com una descripció emergent a la informació de l'usuari", + "custom_params": "Paràmetres personalitzats", + "custom_params_hint": "Correcte: --param=xy, incorrecte: --param xy", + "destination": "Destí", + "disable_login": "No permetre l'inici de sessió (els missatges entrants continuen sent acceptats)", + "domain_matches_hostname": "El domini %s coincideix amb el nom del servidor", + "dry": "Simular la sincronització", + "gal": "Llista d'adreces global", + "generate": "genereu", + "inactive": "Inactiu", + "internal": "Intern", + "internal_info": "Els àlies interns són només accessibles des del mateix domini o els àlies de dominis.", + "mailbox_quota_def": "Quota per defecte de la bústia", + "nexthop": "Següent salt", + "private_comment": "Comentari privat", + "public_comment": "Comentari púlbic" }, "admin": { "access": "Accés", diff --git a/data/web/lang/lang.cs-cz.json b/data/web/lang/lang.cs-cz.json index 9ef0304c8..708de8486 100644 --- a/data/web/lang/lang.cs-cz.json +++ b/data/web/lang/lang.cs-cz.json @@ -24,7 +24,7 @@ "sogo_access": "Správa přístupu do SOGo", "sogo_profile_reset": "Resetování profilu SOGo", "spam_alias": "Dočasné aliasy", - "spam_policy": "Blacklist/Whitelist", + "spam_policy": "Denylist/Allowlist", "spam_score": "Skóre spamu", "syncjobs": "Synchronizační úlohy", "tls_policy": "Pravidla TLS", @@ -109,7 +109,10 @@ "validate": "Ověřit", "validation_success": "Úspěšně ověřeno", "tags": "Štítky", - "dry": "Simulovat synchronizaci" + "dry": "Simulovat synchronizaci", + "internal": "Interní", + "internal_info": "Interní aliasy jsou přístupné jen z vlastních domén nebo jejich aliasů.", + "sender_allowed": "Povolit jako alias pro odesílání" }, "admin": { "access": "Přístupy", @@ -147,7 +150,7 @@ "arrival_time": "Čas zařazení do fronty (čas na serveru)", "authed_user": "Přihlášený uživatel", "ays": "Opravdu chcete pokračovat?", - "ban_list_info": "Seznam blokovaných IP adres je zobrazen níže: síť (zbývající čas blokování) - [akce].
    IP adresy zařazené pro odblokování budou z aktivního seznamu odebrány během několika sekund.
    Červeně označené položky jsou pernamentní bloky z blacklistu.", + "ban_list_info": "Viz seznam zablokovaných IP níže: síť (zbývající doba zablokování) - [akce].
    IP adresy zařazené pro odblokování budou z aktivního seznamu odebrány během pár sekund.
    Červeně označeny jsou položky z trvalých seznamů.", "change_logo": "Změnit logo", "logo_normal_label": "Normální", "logo_dark_label": "Inverzní pro tmavý režim", @@ -181,16 +184,16 @@ "empty": "Žádné výsledky", "excludes": "Vyloučit tyto příjemce", "f2b_ban_time": "Doba blokování (s)", - "f2b_blacklist": "Sítě/hostitelé na blacklistu", + "f2b_blacklist": "Sítě či hostitelé na seznamu zákazů", "f2b_filter": "Regex filtre", - "f2b_list_info": "Síť nebo hostitelé na blacklistu mají vždy větší váhu než položky na whitelistu. Každá úprava seznamů trvá pár sekund.", + "f2b_list_info": "Sítě či hostitelé na seznamu zákazů mají vždy větší váhu než položky na seznamu povolení. Každá úprava seznamu trvá pár sekund.", "f2b_max_attempts": "Max. pokusů", "f2b_netban_ipv4": "Rozsah IPv4 podsítě k zablokování (8-32)", "f2b_netban_ipv6": "Rozsah IPv6 podsítě k zablokování (8-128)", "f2b_parameters": "Parametry automatického firewallu", "f2b_regex_info": "Záznamy které se berou v úvahu: SOGo, Postfix, Dovecot, PHP-FPM.", "f2b_retry_window": "Časový horizont pro maximum pokusů (s)", - "f2b_whitelist": "Sítě/hostitelé na whitelistu", + "f2b_whitelist": "Sítě či hostitelé na seznamu povolení", "filter_table": "Tabulka filtrů", "forwarding_hosts": "Předávající servery", "forwarding_hosts_add_hint": "Lze zadat IPv4/IPv6 adresy, sítě ve formátu CIDR, názvy serverů (budou převedeny na IP adresy) nebo názvy domén (budou převedeny na IP pomocí SPF záznamů, příp. MX záznamů).", @@ -302,8 +305,8 @@ "rspamd_com_settings": "Název nastavení se vygeneruje automaticky, viz ukázky nastavení níže. Více informací viz Rspamd dokumentace", "rspamd_global_filters": "Mapa globálních filtrů", "rspamd_global_filters_agree": "Budu opatrný!", - "rspamd_global_filters_info": "Mapa globálních filtrů obsahuje jiné globální black- a whitelisty.", - "rspamd_global_filters_regex": "Názvy jsou dostatečným vysvětlením. Musí obsahovat jen platné regulární výrazy ve formátu \"/vyraz/parametry\" (e.g. /.+@domena\\.tld/i).
    \r\n Každý výraz bude podroben základní kontrole, přesto je možné Rspamd 'rozbít', nebude-li syntax zcela korektní.
    \r\n Rspamd se pokusí načíst mapu po každé změně. V případě potíží, restartujte Rspamd, aby se konfigurace načetla explicitně.", + "rspamd_global_filters_info": "Mapa globálních filtrů obsahuje různé seznamy povolených a zakázaných serverů.", + "rspamd_global_filters_regex": "Názvy stačí k vysvětlení. Položky musejí obsahovat jen platné regulární výrazy ve tvaru \"/vyraz/parametry\" (e.g. /.+@domena\\.tld/i).
    \n Každý výraz bude podroben základní kontrole, přesto je možné Rspamd 'rozbít', nebude-li syntax zcela korektní.
    \n Rspamd se pokusí po každé změně načíst mapu znovu. V případě potíží restartujte Rspamd, aby se konfigurace načetla explicitně.", "rspamd_settings_map": "Nastavení Rspamd", "sal_level": "Úroveň 'Moo'", "save": "Uložit změny", @@ -407,7 +410,9 @@ "iam_extra_permission": "Aby vše fungovalo, musí mít mailcow klient v Keycloaku nastavený servisní účet a povolení view-users.", "iam_host": "Hostitel", "iam_host_info": "Zadejte jeden či více hostitelů, oddělte čárkou.", - "iam_import_users": "Importovat uživatele" + "iam_import_users": "Importovat uživatele", + "iam_auth_flow": "Proces autentizace", + "needs_restart": "potřebuje restart" }, "danger": { "access_denied": "Přístup odepřen nebo jsou neplatná data ve formuláři", @@ -548,7 +553,11 @@ "img_size_exceeded": "Obrázek má větší než povolenou velikost souboru", "invalid_reset_token": "Neplatný resetovací token", "required_data_missing": "Chybí potřebný údaj %s", - "reset_token_limit_exceeded": "Byl překročen limit na reset tokeny. Zkuste to později." + "reset_token_limit_exceeded": "Byl překročen limit na reset tokeny. Zkuste to později.", + "max_age_invalid": "Maximální životnost %s není platná", + "mode_invalid": "Mód %s není platný", + "mx_invalid": "Záznam MX %s není platný", + "version_invalid": "Verze %s není platná" }, "datatables": { "emptyTable": "Tabulka neobsahuje žádná data", @@ -725,7 +734,7 @@ "sogo_visible_info": "Tato volba určuje objekty, jež lze zobrazit v SOGo (sdílené nebo nesdílené aliasy, jež ukazuje alespoň na jednu schránku).", "spam_alias": "Vytvořit nebo změnit dočasné aliasy", "spam_filter": "Spam filtr", - "spam_policy": "Přidat nebo odebrat položky whitelistu/blacklistu", + "spam_policy": "Přidat nebo odebrat položky seznamu", "spam_score": "Nastavte vlastní skóre spamu", "subfolder2": "Synchronizace do podsložky v cílovém umístění
    (prázdné = nepoužívat podsložku)", "syncjob": "Upravit synchronizační úlohu", @@ -759,7 +768,22 @@ "mailbox_rename_warning": "DŮLEŽITÉ! Vytvořte si zálohu schránky, než ji přejmenujete.", "mailbox_rename_alias": "Automaticky vytvořit alias", "mailbox_rename_title": "Nový název zdejší schránky", - "pushover": "Pushover" + "pushover": "Pushover", + "internal": "Interní", + "internal_info": "Interní aliasy jsou přístupné jen z vlastních domén nebo jejich aliasů.", + "mta_sts": "MTA-STS", + "mta_sts_info": "MTA-STS je standard, jenž říká poštovním serverům, aby komunikovaly pomocí TLS s platnými certifikáty.
    Používá se, pokud není k dispozici DANE, např. chybí-li či není podporováno DNSSEC.
    Pozn.: Podporuje-li přijímající doména DANE a DNSSEC, bude vždy použito DANE; MTA-STS zůstane jako plán B.", + "mta_sts_version": "Verze", + "mta_sts_version_info": "Určuje verzi standardu MTA-STS – zatím je podporována jen STSv1.", + "mta_sts_mode": "Mód", + "mta_sts_mode_info": "K dispozici jsou tři módy:
    • testing – pravidlo se jen sleduje, porušení je bez následků.
    • enforce – pravidlo je důsledně dodržováno, spojení bez platného TLS jsou odmítána.
    • none – pravidlo je zveřejněno, ale neuplatňuje se.
    ", + "mta_sts_max_age": "Maximální životnost", + "mta_sts_max_age_info": "Doba v sekundách, po niž poštovní servery mohou toho pravidlo držet v mezipaměti bez nutnosti obnovení.", + "mta_sts_mx": "Server MX", + "mta_sts_mx_info": "Dovoluje odesílání jen výslovně vypsaným poštovním serverům; odesílající server kontroluje, že server MX určený v DNS odpovídá pravidlu, a povolí doručení jen s platným certifikátem TLS (chrání přes útokem typu MITM).", + "mta_sts_mx_notice": "Lze zadat více serverů MX (oddělte čárkou).", + "sender_allowed": "Povolit jako alias pro odesílání", + "sender_allowed_info": "Není-li povoleno, lze alias použít jen pro příjem pošty. Lze také obejít v Sender ACL a nastavit konkrétní schránky, jež tento alias mohou používat" }, "fido2": { "confirm": "Potvrdit", @@ -829,7 +853,8 @@ "login_admintext": "Přihlášení správce", "login_user": "Přihlášení uživatele", "login_dadmin": "Přihlášení správce domény", - "login_admin": "Přihlášení správce" + "login_admin": "Přihlášení správce", + "email": "Mailová adresa" }, "mailbox": { "action": "Akce", @@ -861,7 +886,7 @@ "bcc": "BCC", "bcc_destination": "Cíl kopie", "bcc_destinations": "Cíl kopií", - "bcc_info": "Skrytá kopie (mapa BCC) se používá pro tiché předávání kopií všech zpráv na jinou adresu. Mapa příjemců se použije, funguje-li je místní cíl jako adresát zprávy. Totéž platí pro mapy odesílatelů.\nMístní cíl se nedozví, selže-li doručení na cíl BCC.", + "bcc_info": "Skrytá kopie (mapa BCC) se používá pro tiché předávání kopií všech zpráv na jinou adresu. Mapa příjemců se použije, funguje-li je místní cíl jako adresát zprávy. Totéž platí pro mapy odesílatelů.
    \n Místní cíl se nedozví, selže-li doručení na cíl BCC.", "bcc_local_dest": "Týká se", "bcc_map": "Skrytá kopie", "bcc_map_type": "Typ skryté kopie", @@ -1005,7 +1030,8 @@ "weekly": "Každý týden", "yes": "✓", "relay_unknown": "Předávání neexistujících schránek", - "iam": "Poskytovatel identity" + "iam": "Poskytovatel identity", + "internal": "Interní" }, "oauth2": { "access_denied": "K udělení přístupu se přihlašte jako vlastník mailové schránky.", @@ -1037,7 +1063,7 @@ "notified": "Oznámeno", "qhandler_success": "Požadavek úspěšně přijat. Můžete nyní zavřít okno.", "qid": "Rspamd QID", - "qinfo": "Karanténní systém uloží odmítnutou poštu do databáze (odesílatel se nedozví, že pošta byla doručena) jakož i pošta, která bude jako kopie doručena do složky Nevyžádaná pošta. \r\n
    \"Naučit jako spam a smazat\" naučí zprávu jako spam přes Bayesian theorem a současně vypočítá fuzzy hashes pro odmítnutí podobných zpráv v budoucnosti. \r\n
    Prosím, berte na vědomí, že naučení více zpráv může být - záleží na vašem systému - časově náročné .
    Položky na černé listině jsou z karantény vyloučeny.", + "qinfo": "Karanténa uloží do databáze odmítnutou poštu (odesílatel se nedozví, že pošta byla doručena) jakož i poštu, jež se jako kopie doručuje do složky Nevyžádaná pošta.\n
    \"Naučit jako spam a smazat\" předá zprávu systému k naučení bayesiánskou analýzou jako spam a současně stanoví fuzzy hashe pro odmítání podobných zpráv v budoucnosti.\n
    Vezměte na vědomí, že učení více zpráv může být podle výkonnosti systému zabrat více času.
    Položky na seznamu zákazů jsou z karantény vyloučeny.", "qitem": "Položka v karanténě", "quarantine": "Karanténa", "quick_actions": "Akce", @@ -1082,7 +1108,8 @@ "hold_mail_legend": "Podrží vybrané e-maily. (Zabrání dalším pokusům o doručení)", "show_message": "Zobrazit zprávu", "unhold_mail": "Uvolnit", - "unhold_mail_legend": "Uvolnit vybrané e-maily k doručení. (Pouze v případě předchozího podržení)" + "unhold_mail_legend": "Uvolnit vybrané e-maily k doručení. (Pouze v případě předchozího podržení)", + "unban": "odblokovat" }, "ratelimit": { "disabled": "Vypnuto", @@ -1323,12 +1350,12 @@ "sogo_profile_reset": "Resetovat profil SOGo", "sogo_profile_reset_help": "Tato volba odstraní uživatelský profil SOGo a nenávratně vymaže všechna data.", "sogo_profile_reset_now": "Resetovat profil", - "spam_aliases": "Dočasné e-mailové aliasy", + "spam_aliases": "Spam aliasy", "spam_score_reset": "Obnovit výchozí nastavení serveru", "spamfilter": "Filtr spamu", "spamfilter_behavior": "Hodnocení", - "spamfilter_bl": "Seznam zakázaných adres (blacklist)", - "spamfilter_bl_desc": "Zakázané emailové adresy budou vždy klasifikovány jako spam a odmítnuty. Odmítnutá pošta nebude uložena do karantény. Lze použít zástupné znaky (*). Filtr se použije pouze na přímé aliasy (s jednou cílovou poštovní schránkou), s výjimkou doménových košů a samotné poštovní schránky.", + "spamfilter_bl": "Seznam zákazů", + "spamfilter_bl_desc": "Zakázané emailové adresy budou vždy klasifikovány jako spam a odmítnuty. Odmítnutá pošta se neukládá do karantény. Lze použít zástupné znaky (*). Filtr se použije pouze na přímé aliasy (s jednou cílovou poštovní schránkou), s výjimkou doménových košů a samotné poštovní schránky.", "spamfilter_default_score": "Výchozí hodnoty", "spamfilter_green": "Zelená: tato zpráva není spam", "spamfilter_hint": "První hodnota představuje \"nízké spam skóre\" a druhá \"vysoké spam skóre\".", @@ -1339,7 +1366,7 @@ "spamfilter_table_empty": "Žádná data k zobrazení", "spamfilter_table_remove": "smazat", "spamfilter_table_rule": "Pravidlo", - "spamfilter_wl": "Seznam povolených adres (whitelist)", + "spamfilter_wl": "Seznam povolení", "spamfilter_wl_desc": "Povolené emailové adresy nebudou nikdy klasifikovány jako spam. Lze použít zástupné znaky (*). Filtr se použije pouze na přímé aliasy (s jednou cílovou mailovou schránkou), s výjimkou doménových košů a samotné mailové schránky.", "spamfilter_yellow": "Žlutá: tato zpráva může být spam, bude označena jako spam a přesunuta do složky nevyžádané pošty", "status": "Stav", @@ -1383,7 +1410,10 @@ "authentication": "Autentifikace", "overview": "Přehled", "protocols": "Protokoly", - "value": "Hodnota" + "value": "Hodnota", + "expire_never": "Nikdy nevyprší", + "forever": "Navždy", + "spam_aliases_info": "Spam alias je dočasná adresa, již lze použít k ochraně skutečných adres.
    Případně lze nastavit také dobu platnosti, po níž je alias automaticky deaktivován, čímž se řeší případy zneužitých či odcizených adres." }, "warning": { "cannot_delete_self": "Nelze smazat právě přihlášeného uživatele", diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index b5f3f325d..c19eb209b 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -73,6 +73,7 @@ "inactive": "Inaktiv", "internal": "Intern", "internal_info": "Interne Aliasse sind nur von der eigenen Domäne oder Alias-Domänen erreichbar.", + "sender_allowed": "Als dieser Alias senden erlauben", "kind": "Art", "mailbox_quota_def": "Standard-Quota einer Mailbox", "mailbox_quota_m": "Max. Speicherplatz pro Mailbox (MiB)", @@ -149,7 +150,7 @@ "arrival_time": "Ankunftszeit (Serverzeit)", "authed_user": "Auth. Benutzer", "ays": "Soll der Vorgang wirklich ausgeführt werden?", - "ban_list_info": "Übersicht ausgesperrter Netzwerke: Netzwerk (verbleibende Bannzeit) - [Aktionen].
    IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Banns nach wenigen Sekunden.
    Rote Labels sind Indikatoren für aktive Allowlist-Einträge.", + "ban_list_info": "Übersicht ausgesperrter Netzwerke: Netzwerk (verbleibende Bannzeit) - [Aktionen].
    IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Banns nach wenigen Sekunden.
    Rote Labels kennzeichnen aktive permanente Sperren durch Denylisting.", "change_logo": "Logo ändern", "configuration": "Konfiguration", "convert_html_to_text": "Konvertiere HTML zu reinem Text", @@ -511,6 +512,7 @@ "pushover_credentials_missing": "Pushover Token und/oder Key fehlen", "pushover_key": "Pushover Key hat das falsche Format", "pushover_token": "Pushover Token hat das falsche Format", + "quarantine_category_invalid": "Quarantäne-Kategorie muss eine der folgenden sein: add_header, reject, all", "quota_not_0_not_numeric": "Speicherplatz muss numerisch und >= 0 sein", "recipient_map_entry_exists": "Eine Empfängerumschreibung für Objekt \"%s\" existiert bereits", "recovery_email_failed": "E-Mail zur Wiederherstellung konnte nicht gesendet werden. Bitte wenden Sie sich an Ihren Administrator.", @@ -535,6 +537,7 @@ "temp_error": "Temporärer Fehler", "text_empty": "Text darf nicht leer sein", "tfa_token_invalid": "TFA-Token ungültig", + "tfa_removal_blocked": "Zwei-Faktor-Authentifizierung kann nicht entfernt werden, da sie für Ihr Konto erforderlich ist.", "tls_policy_map_dest_invalid": "Ziel ist ungültig", "tls_policy_map_entry_exists": "Eine TLS-Richtlinie \"%s\" existiert bereits", "tls_policy_map_parameter_invalid": "Parameter ist ungültig", @@ -694,6 +697,8 @@ "inactive": "Inaktiv", "internal": "Intern", "internal_info": "Interne Aliasse sind nur von der eigenen Domäne oder Alias-Domänen erreichbar.", + "sender_allowed": "Als dieser Alias senden erlauben", + "sender_allowed_info": "Wenn deaktiviert, kann dieser Alias nur E-Mails empfangen. Verwenden Sie Sender-ACL, um bestimmten Postfächern die Berechtigung zum Senden zu erteilen.", "kind": "Art", "last_modified": "Zuletzt geändert", "lookup_mx": "Ziel mit MX vergleichen (Regex, etwa .*\\.google\\.com, um alle Ziele mit MX *google.com zu routen)", @@ -987,7 +992,7 @@ "sogo_visible": "Alias Sichtbarkeit in SOGo", "sogo_visible_n": "Alias in SOGo verbergen", "sogo_visible_y": "Alias in SOGo anzeigen", - "spam_aliases": "Temp. Alias", + "spam_aliases": "Spam-Alias", "stats": "Statistik", "status": "Status", "sync_jobs": "Synchronisationen", @@ -1081,7 +1086,7 @@ "rspamd_result": "Rspamd-Ergebnis", "sender": "Sender (SMTP)", "sender_header": "Sender (\"From\"-Header)", - "settings_info": "Maximale Anzahl der zurückgehaltenen E-Mails: %s
    Maximale Größe einer zu speichernden E-Mail: %s MiB", + "settings_info": "Maximale Anzahl der zurückgehaltenen E-Mails (pro Mailbox): %s
    Maximale Größe einer zu speichernden E-Mail: %s MiB", "show_item": "Details", "spam": "Spam", "spam_score": "Bewertung", @@ -1100,7 +1105,7 @@ "legend": "Funktionen der Mailqueue Aktionen:", "ays": "Soll die derzeitige Queue wirklich komplett bereinigt werden?", "deliver_mail": "Ausliefern", - "deliver_mail_legend": "Versucht eine erneute Zustellung der ausgwählten Mails.", + "deliver_mail_legend": "Versucht eine erneute Zustellung der ausgewählten Mails.", "hold_mail": "Zurückhalten", "hold_mail_legend": "Hält die ausgewählten Mails zurück. (Verhindert weitere Zustellversuche)", "queue_manager": "Queue Manager", @@ -1230,7 +1235,12 @@ "webauthn": "WebAuthn-Authentifizierung", "waiting_usb_auth": "Warte auf USB-Gerät...

    Bitte jetzt den vorgesehenen Taster des USB-Gerätes berühren.", "waiting_usb_register": "Warte auf USB-Gerät...

    Bitte zuerst das obere Passwortfeld ausfüllen und erst dann den vorgesehenen Taster des USB-Gerätes berühren.", - "yubi_otp": "Yubico OTP-Authentifizierung" + "yubi_otp": "Yubico OTP-Authentifizierung", + "force_tfa": "Zwei-Faktor-Authentifizierung beim Login erzwingen", + "force_tfa_info": "Der Benutzer muss Zwei-Faktor-Authentifizierung einrichten, bevor er auf den Bereich zugreifen kann.", + "setup_title": "Zwei-Faktor-Authentifizierung erforderlich", + "setup_required": "Ihr Konto erfordert Zwei-Faktor-Authentifizierung. Bitte richten Sie eine 2FA-Methode ein, um fortzufahren.", + "cancel_setup": "Abbrechen und abmelden" }, "user": { "action": "Aktion", @@ -1281,7 +1291,9 @@ "encryption": "Verschlüsselung", "excludes": "Ausschlüsse", "expire_in": "Ungültig in", + "expire_never": "Niemals ungültig", "fido2_webauthn": "FIDO2/WebAuthn", + "forever": "Für immer", "force_pw_update": "Das Passwort für diesen Benutzer muss geändert werden, damit die Zugriffssperre auf die Groupware-Komponenten wieder freigeschaltet wird.", "from": "von", "generate": "generieren", @@ -1307,6 +1319,7 @@ "never": "Niemals", "new_password": "Neues Passwort", "new_password_repeat": "Neues Passwort (Wiederholung)", + "pw_update_required": "Ihr Konto erfordert eine Passwortänderung. Bitte setzen Sie ein neues Passwort, um fortzufahren.", "no_active_filter": "Kein aktiver Filter vorhanden", "no_last_login": "Keine letzte UI-Anmeldung gespeichert", "no_record": "Kein Eintrag", @@ -1346,7 +1359,8 @@ "sogo_profile_reset": "SOGo-Profil zurücksetzen", "sogo_profile_reset_help": "Das Profil wird inklusive aller Kalender- und Kontaktdaten unwiederbringlich gelöscht.", "sogo_profile_reset_now": "Profil jetzt zurücksetzen", - "spam_aliases": "Temporäre E-Mail-Aliasse", + "spam_aliases": "Spam E-Mail-Aliasse", + "spam_aliases_info": "Ein Spam-Alias ist eine temporäre E-Mailadresse, die benutzt werden kann, um eine echte E-Mail Adressen zu schützen.
    Optional kann eine Ablaufzeit gesetzt werden, sodass der Alias nach dem definierten Zeitraum automatisch deaktiviert wird, was missbrauchte oder geleakte Adressen effektiv entsorgt.", "spam_score_reset": "Auf Server-Standard zurücksetzen", "spamfilter": "Spamfilter", "spamfilter_behavior": "Bewertung", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index d46e4606c..873d7b98d 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -73,6 +73,7 @@ "inactive": "Inactive", "internal": "Internal", "internal_info": "Internal aliases are only accessible from the own domain or alias domains.", + "sender_allowed": "Allow to send as this alias", "kind": "Kind", "mailbox_quota_def": "Default mailbox quota", "mailbox_quota_m": "Max. quota per mailbox (MiB)", @@ -415,6 +416,7 @@ }, "danger": { "access_denied": "Access denied or invalid form data", + "tfa_removal_blocked": "Two-factor authentication cannot be removed because it is required for your account.", "alias_domain_invalid": "Alias domain %s is invalid", "alias_empty": "Alias address must not be empty", "alias_goto_identical": "Alias and goto address must not be identical", @@ -511,6 +513,7 @@ "pushover_credentials_missing": "Pushover token and or key missing", "pushover_key": "Pushover key has a wrong format", "pushover_token": "Pushover token has a wrong format", + "quarantine_category_invalid": "Quarantine category must be one of: add_header, reject, all", "quota_not_0_not_numeric": "Quota must be numeric and >= 0", "recipient_map_entry_exists": "A Recipient map entry \"%s\" exists", "recovery_email_failed": "Could not send a recovery email. Please contact your administrator.", @@ -694,6 +697,8 @@ "inactive": "Inactive", "internal": "Internal", "internal_info": "Internal aliases are only accessible from the own domain or alias domains.", + "sender_allowed": "Allow to send as this alias", + "sender_allowed_info": "If disabled, this alias can only receive mail. Use sender ACL to override and grant specific mailboxes permission to send.", "kind": "Kind", "last_modified": "Last modified", "lookup_mx": "Destination is a regular expression to match against MX name (.*\\.google\\.com to route all mail targeted to a MX ending in google.com over this hop)", @@ -924,6 +929,7 @@ "filters": "Filters", "fname": "Full name", "force_pw_update": "Force password update at next login", + "force_tfa": "TFA", "gal": "Global Address List", "goto_ham": "Learn as ham", "goto_spam": "Learn as spam", @@ -1081,7 +1087,7 @@ "rspamd_result": "Rspamd result", "sender": "Sender (SMTP)", "sender_header": "Sender (\"From\" header)", - "settings_info": "Maximum amount of elements to be quarantined: %s
    Maximum email size: %s MiB", + "settings_info": "Maximum amount of elements to be quarantined (per mailbox): %s
    Maximum email size: %s MiB", "show_item": "Show item", "spam": "Spam", "spam_score": "Score", @@ -1218,6 +1224,11 @@ "confirm_totp_token": "Please confirm your changes by entering the generated token", "delete_tfa": "Disable TFA", "disable_tfa": "Disable TFA until next successful login", + "force_tfa": "Force 2FA enrollment at login", + "force_tfa_info": "The user will be required to set up two-factor authentication before accessing the panel.", + "setup_title": "Two-Factor Authentication Required", + "setup_required": "Your account requires two-factor authentication. Please set up a 2FA method to continue.", + "cancel_setup": "Cancel and log out", "enter_qr_code": "Your TOTP code if your device cannot scan QR codes", "error_code": "Error code", "init_webauthn": "Initializing, please wait...", @@ -1288,7 +1299,9 @@ "encryption": "Encryption", "excludes": "Excludes", "expire_in": "Expire in", + "expire_never": "Never Expire", "fido2_webauthn": "FIDO2/WebAuthn", + "forever": "Forever", "force_pw_update": "You must set a new password to be able to access groupware related services.", "from": "from", "generate": "generate", @@ -1322,6 +1335,7 @@ "overview": "Overview", "password": "Password", "password_now": "Current password (confirm changes)", + "pw_update_required": "Your account requires a password change. Please set a new password to continue.", "password_repeat": "Password (repeat)", "password_reset_info": "If no email for password recovery is provided, this function cannot be used.", "protocols": "Protocols", @@ -1355,7 +1369,8 @@ "sogo_profile_reset": "Reset SOGo profile", "sogo_profile_reset_help": "This will destroy a user's SOGo profile and delete all contact and calendar data irretrievable.", "sogo_profile_reset_now": "Reset profile now", - "spam_aliases": "Temporary email aliases", + "spam_aliases": "Spam email aliases", + "spam_aliases_info": "A spam alias is a temporary email address that can be used to protect real email addresses.
    Optionally, an expiration time can be set so that the alias is automatically deactivated after the defined period, effectively disposing of abused or leaked addresses.", "spam_score_reset": "Reset to server default", "spamfilter": "Spam filter", "spamfilter_behavior": "Rating", diff --git a/data/web/lang/lang.es-es.json b/data/web/lang/lang.es-es.json index f357b9a80..7d86973ac 100644 --- a/data/web/lang/lang.es-es.json +++ b/data/web/lang/lang.es-es.json @@ -1084,6 +1084,7 @@ "aliases_send_as_all": "No verificar permisos del remitente para los siguientes dominios (y sus aliases)", "change_password": "Cambiar contraseña", "create_syncjob": "Crear nuevo trabajo de sincronización", + "created_on": "Creado", "daily": "Cada día", "day": "Día", "description": "Descripción", @@ -1095,6 +1096,9 @@ "edit": "Editar", "encryption": "Cifrado", "excludes": "Excluye", + "expire_in": "Expirará en", + "expire_never": "Nunca expirará", + "forever": "Siempre", "hour": "Hora", "hourly": "Cada hora", "hours": "Horas", @@ -1115,7 +1119,8 @@ "shared_aliases": "Alias compartidos", "shared_aliases_desc": "Los alias compartidos no se ven afectados por la configuración específica del usuario, como el filtro de correo no deseado o la política de cifrado. Los filtros de spam correspondientes solo pueden ser realizados por un administrador como una política de dominio.", "sogo_profile_reset": "Resetear perfil SOGo", - "spam_aliases": "Alias de email temporales", + "spam_aliases": "Alias de email de spam", + "spam_aliases_info": "Un alias de spam es una dirección de correo electrónico temporal que se puede usar para proteger direcciones de correo electrónico reales.
    Opcionalmente, se puede establecer un tiempo de expiración para que el alias se desactive automáticamente después del período definido, eliminando efectivamente las direcciones abusadas o filtradas.", "spamfilter": "Filtro anti-spam", "spamfilter_behavior": "Clasificación", "spamfilter_bl": "Lista negra", diff --git a/data/web/lang/lang.fr-fr.json b/data/web/lang/lang.fr-fr.json index 07ac641b8..9ee916713 100644 --- a/data/web/lang/lang.fr-fr.json +++ b/data/web/lang/lang.fr-fr.json @@ -16,7 +16,7 @@ "quarantine_notification": "Modifier la notification de quarantaine", "quarantine_category": "Modifier la catégorie de la notification de quarantaine", "ratelimit": "Limite d'envoi", - "recipient_maps": "Cartes destinataire", + "recipient_maps": "Cartes des destinataires", "smtp_ip_access": "Changer les hôtes autorisés pour SMTP", "sogo_access": "Autoriser la gestion des accès à SOGo", "sogo_profile_reset": "Réinitialiser le profil SOGo", @@ -109,7 +109,10 @@ "bcc_dest_format": "La destination Cci doit être une seule adresse de courriel valide.
    Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici.", "tags": "Etiquettes", "app_passwd_protocols": "Protocoles autorisés pour le mot de passe de l'application", - "dry": "Simuler la synchronisation" + "dry": "Simuler la synchronisation", + "internal": "Interne", + "internal_info": "Les alias internes sont accessibles uniquement depuis le domaine ou les alias du domaine.", + "sender_allowed": "Autoriser l'envoi sous cet alias" }, "admin": { "access": "Accès", @@ -407,7 +410,9 @@ "iam_host": "Hôte", "iam_host_info": "Saisissez un ou plusieurs hôtes LDAP, séparés par des virgules.", "iam_import_users": "Importer des utilisateurs", - "filter": "Filtrer" + "filter": "Filtrer", + "needs_restart": "nécessite un redémarrage", + "iam": "Fournisseur d'identité" }, "danger": { "access_denied": "Accès refusé ou données de formulaire non valides", @@ -441,7 +446,7 @@ "global_filter_write_error": "Impossible d’écrire le fichier de filtre : %s", "global_map_invalid": "ID de carte globale %s non valide", "global_map_write_error": "Impossible d’écrire l’ID de la carte globale %s : %s", - "goto_empty": "Une adresse alias doit contenir au moins une adresse 'goto'valide", + "goto_empty": "Une adresse alias doit contenir au moins une adresse 'goto' valide", "goto_invalid": "Adresse Goto %s non valide", "ham_learn_error": "Erreur d'apprentissage Ham : %s", "imagick_exception": "Erreur : Exception Imagick lors de la lecture de l’image", @@ -548,7 +553,13 @@ "generic_server_error": "Une erreur de serveur inattendue s'est produite. Veuillez contacter votre administrateur.", "authsource_in_use": "Le fournisseur d'identité ne peut pas être modifié ou supprimé car il est actuellement utilisé par un ou plusieurs utilisateurs.", "iam_test_connection": "Échec de la connexion", - "required_data_missing": "La donnée requise %s est manquante" + "required_data_missing": "La donnée requise %s est manquante", + "max_age_invalid": "L'âge maximum %s est invalide", + "mode_invalid": "Le mode %s est invalide", + "mx_invalid": "L'enregistrement MX %s est invalide", + "version_invalid": "La version %s est invalide", + "tfa_removal_blocked": "L’authentification à deux facteurs ne peut pas être supprimée, car elle est requise pour votre compte.", + "quarantine_category_invalid": "La catégorie de quarantaine doit être l’une des suivantes : add_header, reject, all\"" }, "debug": { "chart_this_server": "Graphique (ce serveur)", @@ -693,7 +704,7 @@ "spam_score": "Définir un score spam personnalisé", "subfolder2": "Synchronisation dans le sous-dossier sur la destination
    (vide = ne pas utiliser de sous-dossier)", "syncjob": "Modifier la tâche de synchronisation", - "target_address": "Adresse(s) Goto(séparé(s) par des virgules)", + "target_address": "Adresse(s) Goto (séparé(s) par des virgules)", "target_domain": "Domaine cible", "timeout1": "Délai de connexion à l’hôte distant", "timeout2": "Délai de connexion à l’hôte local", @@ -734,7 +745,22 @@ "mailbox_rename_alias": "Créer un alias automatiquement", "sogo_access": "Redirection directe vers SOGo", "pushover": "Pushover", - "pushover_sound": "Son" + "pushover_sound": "Son", + "internal": "Interne", + "internal_info": "Les alias internes sont accessibles uniquement depuis le domaine ou les alias du domaine.", + "mta_sts": "MTA-STS", + "mta_sts_version": "Version", + "mta_sts_version_info": "Défini la version du standard MTA-STS – actuellement seul STSv1 est valide.", + "mta_sts_mode": "Mode", + "mta_sts_max_age": "Âge maximum", + "mta_sts_mx": "Serveur MX", + "mta_sts_mx_notice": "Plusieurs serveurs MX peuvent être spécifiés (séparés par des virgules).", + "mta_sts_info": "MTA-STS est un standard qui oblige la délivrance des courriels entre les serveurs de courriels à utiliser TLS avec des certificats valides.
    Il est utilisé quand DANE n'est pas possible à cause d'un manque ou d'un non support de DNSSEC.
    Note : Si le domaine du destinataire supporte DANE avec DNSSEC, DANE est toujours préféré – MTA-STS sert seulement en secours.", + "mta_sts_mode_info": "Il y a trois modes parmi lesquels choisir :
    • testing – la politique est seulement surveillée, les violations n'ont pas d'impact.
    • enforce – la politique est appliquée strictement, les connexions sans TLS valide sont rejetées.
    • none – la politique est publiée mais non appliquée.
    ", + "mta_sts_max_age_info": "Durée en secondes pendant laquelle les serveurs de courriel peuvent mettre en cache cette politique avant de revérifier.", + "mta_sts_mx_info": "Autoriser l'envoi uniquement aux noms d'hôtes des serveurs de courriels indiqués explicitement ; le MTA émetteur vérifie si le nom d'hôte DNS du MX correspond à la liste de la politique, et autorise la délivrance seulement avec un certificat TLS valide (protège contre le MITM).", + "sender_allowed": "Autoriser l'envoi sous cet alias", + "sender_allowed_info": "Si cette option est désactivée, cet alias peut uniquement recevoir des courriels. Utilisez les ACL d’expéditeur pour autoriser certaines boîtes aux lettres à envoyer des messages via cet alias." }, "footer": { "cancel": "Annuler", @@ -789,7 +815,8 @@ "login_linkstext": "L'identifiant n'est pas correct ?", "login_usertext": "Se connecter en tant qu'utilisateur", "login_domainadmintext": "Se connecter en tant qu'administrateur du domaine", - "login_admintext": "Se connecter en tant qu'administrateur" + "login_admintext": "Se connecter en tant qu'administrateur", + "email": "Adresse de courriel" }, "mailbox": { "action": "Action", @@ -896,7 +923,7 @@ "recipient_map_new_info": "La destination de la carte du destinataire doit être une adresse de courriel valide ou un nom de domaine.", "recipient_map_old": "Destinataire original", "recipient_map_old_info": "La destination originale des cartes des destinataires doit être une adresse de courriel valide ou un nom de domaine.", - "recipient_maps": "Cartes des bénéficiaires", + "recipient_maps": "Cartes des destinataires", "relay_all": "Relayer tous les destinataires", "remove": "Supprimer", "resources": "Ressources", @@ -965,7 +992,9 @@ "syncjob_check_log": "Vérifier le journal", "recipient": "Destinataire", "open_logs": "Afficher les journaux", - "iam": "Fournisseur d'identité" + "iam": "Fournisseur d'identité", + "internal": "Interne", + "force_tfa": "TFA" }, "oauth2": { "access_denied": "Veuillez vous connecter en tant que propriétaire de la boîte de réception pour accorder l’accès via Oauth2.", @@ -1166,7 +1195,11 @@ "yubi_otp": "Authentification OTP Yubico", "authenticators": "Authentificateurs", "u2f_deprecated_important": "Veuillez enregistrer votre clé dans le panneau d'administration avec la nouvelle méthode WebAuthn.", - "u2f_deprecated": "Il semble que votre clé ait été enregistrée à l'aide de la méthode U2F obsolète. Nous allons désactiver l'authentification à deux facteurs pour vous et supprimer votre clé." + "u2f_deprecated": "Il semble que votre clé ait été enregistrée à l'aide de la méthode U2F obsolète. Nous allons désactiver l'authentification à deux facteurs pour vous et supprimer votre clé.", + "force_tfa": "Imposer l'authentification à deux facteurs lors de la connexion", + "force_tfa_info": "L’utilisateur doit configurer l’authentification à deux facteurs avant de pouvoir accéder à l’interface.", + "setup_title": "Authentification à deux facteurs requise", + "setup_required": "Votre compte nécessite l’authentification à deux facteurs. Veuillez configurer une méthode 2FA pour continuer." }, "fido2": { "set_fn": "Définir un nom", @@ -1222,7 +1255,7 @@ "email_and_dav": "Courriel, calendriers et contacts", "encryption": "Chiffrement", "excludes": "Exclus", - "expire_in": "Expire dans", + "expire_in": "Expirer dans", "force_pw_update": "Vous devez définir un nouveau mot de passe pour pouvoir accéder aux services liés aux logiciels de groupe.", "generate": "générer", "hour": "heure", @@ -1243,7 +1276,7 @@ "no_last_login": "Aucune dernière information de connexion à l'interface", "no_record": "Pas d'enregistrement", "password": "Mot de passe", - "password_now": "Mot de passe courant (confirmer les changements)", + "password_now": "Mot de passe actuel (confirmer les changements)", "password_repeat": "Mot de passe (répéter)", "pushover_evaluate_x_prio": "Acheminement du courrier hautement prioritaire [X-Priority: 1]", "pushover_info": "Les paramètres de notification push s’appliqueront à tout le courrier propre (non spam) livré à %s y compris les alias (partagés, non partagés, étiquetés).", @@ -1350,7 +1383,13 @@ "mailbox_general": "Général", "mailbox_settings": "Paramètres", "tfa_info": "L'authentification à deux facteurs permet de protéger votre compte. Si vous l'activez, vous aurez besoin de mots de passe d'application pour vous connecter à des applications ou des services qui ne prennent pas en charge l'authentification à deux facteurs (par exemple les clients e-mails).", - "overview": "Vue d'ensemble" + "overview": "Vue d'ensemble", + "expire_never": "Ne jamais expirer", + "forever": "Pour toujours", + "spam_aliases_info": "Un alias de spam est une adresse de courriel temporaire qui peut être utilisée pour protéger les véritables adresses de courriel.
    De manière optionnelle, une durée d'expiration peut être définie afin que l'alias soit automatiquement désactivé après la période définie, éliminant ainsi les adresses étant abusées ou ayant fuité.", + "authentication": "Authentification", + "protocols": "Protocoles", + "pw_update_required": "Votre compte nécessite un changement de mot de passe. Veuillez définir un nouveau mot de passe pour continuer." }, "warning": { "cannot_delete_self": "Impossible de supprimer l’utilisateur connecté", diff --git a/data/web/lang/lang.gr-gr.json b/data/web/lang/lang.gr-gr.json index df9127aec..a070ea820 100644 --- a/data/web/lang/lang.gr-gr.json +++ b/data/web/lang/lang.gr-gr.json @@ -6,7 +6,8 @@ "weeks": "Εβδομάδες", "with_app_password": "με κωδικό εφαρμογής", "year": "χρόνος", - "years": "χρόνια" + "years": "χρόνια", + "value": "Τιμή" }, "warning": { "cannot_delete_self": "Αδυναμία διαγραφής συνδεδεμένου χρήστη", @@ -16,5 +17,170 @@ "hash_not_found": "Η κατακερματισμένη τιμή (hash value) δεν βρέθηκε ή έχει είδη διαγραφεί.", "ip_invalid": "Παραλείφθηκε μη έγκυρη διεύθυνση IP: %s", "is_not_primary_alias": "Παραλείφθηκε μη πρωτεύον ψευδώνυμο %s" + }, + "acl": { + "alias_domains": "Προσθήκη ψευδωνύμων τομέων", + "app_passwds": "Διαχείριση κωδικών εφαρμογής", + "bcc_maps": "χαρτογράφηση BCC", + "delimiter_action": "Ενέργεια οριοθέτη", + "domain_desc": "Αλλαγή περιγραφής τομέα", + "domain_relayhost": "Αλλαγή του διακομιστή αναμετάδοσης για ένα τομέα", + "eas_reset": "Επαναφορά συσκευών EAS", + "extend_sender_acl": "Να επιτρέπεται η επέκταση ACL του αποστολέα με εξωτερικές διευθύνσεις", + "filters": "Φίλτρα", + "login_as": "Είσοδος ως χρήστης e-mail", + "mailbox_relayhost": "Αλλαγή διακομιστή αναμετάδοσης για ένα γραμματοκιβώτιο", + "prohibited": "Απαγορεύεται από την ACL", + "protocol_access": "Αλλαγή πρόσβασης πρωτοκόλλου", + "pushover": "Pushover", + "pw_reset": "Επιτρέψτε την επαναφορά κωδικού πρόσβασης του χρήστη", + "quarantine": "Ενέργειες καραντίνας", + "quarantine_attachments": "Συνημμένα καραντίνας", + "quarantine_category": "Αλλαγή κατηγορίας ειδοποιήσεων καραντίνας", + "quarantine_notification": "Αλλαγή ειδοποιήσεων καραντίνας", + "ratelimit": "Όριο τιμής", + "recipient_maps": "Χάρτες παραληπτών", + "smtp_ip_access": "Αλλαγή επιτρεπόμενων διακομιστών SMTP", + "sogo_access": "Επιτρέψτε τη διαχείριση της πρόσβασης στο SOGo", + "sogo_profile_reset": "Επαναφορά του προφίλ SOGo", + "spam_alias": "Προσωρινά ψευδώνυμα", + "spam_policy": "Λίστα απορρίψεων/Λίστα επιτρεπόμενων", + "spam_score": "Βαθμολογία ανεπιθύμητης αλληλογραφίας", + "syncjobs": "Εργασίες συγχρονισμού", + "tls_policy": "Πολιτική TLS", + "unlimited_quota": "Απεριόριστο όριο για γραμματοκιβώτια" + }, + "add": { + "activate_filter_warn": "Όλα τα άλλα φίλτρα θα απενεργοποιηθούν, όταν επιλεγεί η επιλογή \"ενεργό\".", + "active": "Ενεργό", + "add": "Προσθήκη", + "add_domain_only": "Προσθήκη μόνο του τομέα", + "add_domain_restart": "Προσθήκη του τομέα και επανεκκίνηση του SOGo", + "alias_address": "Διευθύνσεις ψευδωνύμων", + "alias_address_info": "Πλήρης διεύθυνση(εις) e-mail ή @example.com, για να λαμβάνετε ΟΛΑ τα μηνύματα ενός τομέα (χωρισμένα με κόμα). μόνο τομείς του mailcow.", + "alias_domain": "Ψευδώνυμο τομέα", + "alias_domain_info": "Μόνο έγκυρα ονόματα τομέα (χωρισμένα με κόμα).", + "app_name": "Όνομα εφαρμογής", + "app_password": "Προσθήκη κωδικού εφαρμογής", + "app_passwd_protocols": "Επιτρεπόμενα πρωτόκολλα για κωδικούς εφαρμογών", + "automap": "Αυτόματη αντιστοίχηση φακέλων (\"Απεσταλμένα μηνύματα\", \"Απεσταλμένα\" => \"Στάλθηκαν\" κ.τ.λ.)", + "backup_mx_options": "Επιλογές αναμετάδοσης", + "bcc_dest_format": "Η BCC διεύθυνση πρέπει να είναι μία και έγκυρη διεύθυνση e-mail.
    Αν θέλετε να στείλετε αντίγραφα σε πολλούς παραλήπτες, δημιουργήστε ένα ψευδόνυμο για όλους και χρησιμοποιήστε το εδώ.", + "comment_info": "Τα προσωπικά σχόλια δεν είναι ορατά στον χρήστη. Τα δημόσια σχόλια εμφανίζονται ως tooltips.", + "custom_params": "Προσαρμοσμένες παράμετροι", + "custom_params_hint": "Σωστή σύνταξη: --param=xy, λάθος σύνταξη: --param xy", + "delete1": "Διαγραφή όταν ολοκληρωθεί", + "delete2": "Διαγραφή μηνυμάτων στον προορισμό που δεν βρίσκονται στην πηγή", + "delete2duplicates": "Διαγραφή διπλότυπων στον προορισμό", + "description": "Περιγραφή", + "destination": "Προορισμός", + "disable_login": "Απαγόρευση εισόδου (η εισερχόμενη αλληλογραφία εξακολουθεί να γίνεται δεκτή)", + "domain": "Τομέας", + "domain_matches_hostname": "Ο τομέας %s είναι ο ίδιος με το όνομα του διακομιστή", + "domain_quota_m": "Συνολικό όριο τομέα (MiB)", + "dry": "Προσομοίωση συγχρονισμού", + "enc_method": "Μέθοδος κρυπτογράφησης", + "exclude": "Εξαίρεση αντικειμένων (regex)", + "full_name": "Πλήρες όνομα", + "gal": "Κοινόχρηστη λίστα διευθύνσεων" + }, + "danger": { + "unknown": "Παρουσιάστηκε κάποιο άγωνστο σφάλμα", + "unknown_tfa_method": "Άγνωστη μέθοδος TFA", + "unlimited_quota_acl": "Το απεριόριστο όριο απαγορεύεται από την ACL", + "username_invalid": "Το όνομα χρήστη %s δεν μπορεί να χρησιμοποιηθεί", + "validity_missing": "Παρακαλώ ορίστε μία περίοδο εγκυρότητας", + "value_missing": "Παρακαλώ συμπληρώστε όλα τα δεδομένα", + "version_invalid": "Η έκδοση %s δεν είναι έγκυρη", + "yotp_verification_failed": "Η επαλήθευση μέσω Yubico OTP απέτυχε: %s" + }, + "datatables": { + "collapse_all": "Σύμπτυξη όλων", + "decimal": ".", + "emptyTable": "Δεν υπάρχουν εγγραφές", + "expand_all": "Επέκταση όλων", + "info": "Εμφανίζονται _START_ εώς _END_ από _TOTAL_ εγγραφές", + "infoEmpty": "Εμφανίζονται 0 εώς 0 από 0 εγγραφές", + "infoFiltered": "(φιλτραρισμένες από _MAX_ συνολικές εγγραφές)", + "thousands": ",", + "lengthMenu": "Εμφάνιση _MENU_ εγγραφών", + "loadingRecords": "Γίνεται φόρτωση...", + "processing": "Παρακαλώ περιμένετε...", + "search": "Αναζήτηση:", + "zeroRecords": "Δε βρέθηκαν εγγραφές", + "paginate": { + "first": "Πρώτη", + "last": "Τελευταία", + "next": "Επόμενη", + "previous": "Προηγούμενη" + }, + "aria": { + "sortAscending": ": ενεργοποίηση αύξουσας ταξινόμησης", + "sortDescending": ": ενεργοποίηση φθίνουσας ταξινόμησης" + } + }, + "debug": { + "architecture": "Αρχιτεκτονική", + "chart_this_server": "Γράφημα (αυτός ο διακομιστής)", + "containers_info": "Πληροφορίες για τον container", + "container_running": "Εκτελείται", + "container_disabled": "Ο container έχει σταματήσει ή απενεργοποιηθεί", + "container_stopped": "Σταματημένος", + "cores": "Πυρήνες", + "current_time": "Ώρα συστήματος", + "disk_usage": "Χρήση αποθ. χώρου", + "docs": "Έγγραφα", + "error_show_ip": "Δεν είναι δυνατή η επίλυση της δημόσιας IP διεύθυνσης", + "external_logs": "Εξωτερικά αρχεία καταγραφής", + "history_all_servers": "Ιστορικό (Όλοι οι διακομιστές)", + "in_memory_logs": "Αρχεία καταγραφής στη μνήμη", + "last_modified": "Τελευταία τροποποίηση", + "log_info": "

    mailcow in-memory logs are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\n
    In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\n
    The in-memory log type should be used for debugging minor issues with containers.

    \n

    External logs are collected via API of the given application.

    \n

    Static logs are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).

    ", + "login_time": "Ώρα", + "logs": "Αρχεία καταγραφής", + "memory": "Μνήμη", + "online_users": "Συνδεδεμένοι χρήστες", + "restart_container": "Επανεκκίνηση", + "service": "Υπηρεσία", + "show_ip": "Εμφάνιση δημόσιας IP", + "size": "Μέγεθος", + "started_at": "Ξεκίνησε στις", + "started_on": "Ξεκίνησε στις", + "static_logs": "Στατικά αρχεία καταγραφής", + "success": "Επιτυχία", + "system_containers": "Σύστημα και Containers", + "timezone": "Ζώνη ώρας", + "uptime": "Χρόνος λειτουργίας", + "update_available": "Υπάρχει διαθέσιμη ενημέρωση", + "no_update_available": "Έχετε τη τελευταία έκδοση του συστήματος", + "update_failed": "Δεν ήταν δυνατός ο έλεγχος για ενημερώσεις", + "username": "Όνομα χρήστη", + "wip": "Currently Work in Progress" + }, + "diagnostics": { + "cname_from_a": "Value derived from A/AAAA record. This is supported as long as the record points to the correct resource.", + "dns_records": "Εγγραφές DNS", + "dns_records_24hours": "Παρακαλώ σημειώστε ότι οι αλλαγές στο DNS μπορεί να χρειαστούν μέχρι 24 ώρες για να ενημερωθούν σωστά και να εμφανιστούν σε αυτή τη σελίδα. Ο σκοπός της είναι να δείτε πως μπορείτε να ρυθμίσετε σωστά τις εγγραφές DNS και να ελέγξετε αν είναι σωστές.", + "dns_records_data": "Σωστά δεδομένα", + "dns_records_docs": "Παρακαλώ συμβουλευτείτε επίσης την τεκμηρίωση.", + "dns_records_name": "Όνομα", + "dns_records_status": "Τρέχουσα κατάσταση", + "dns_records_type": "Τύπος", + "optional": "Αυτή η εγγραφή είναι προαιρετική." + }, + "edit": { + "acl": "ACL (Δικαίωμα)", + "active": "Ενεργό", + "admin": "Επεξεργασία διαχειριστή", + "advanced_settings": "Ρυθμίσεις για προχωρημένους", + "alias": "Επεξεργασία ψευδώνυμου", + "allow_from_smtp": "Επέτρεψε μόνο σε αυτές τις IPs να χρησιμοποιήσουν το SMTP", + "allow_from_smtp_info": "Αφήστε το κενό για να επιτρέψετε όλους τους αποστολείς.
    IPv4/IPv6 διευθύνσεις και δίκτυα.", + "allowed_protocols": "Επιτρεπόμενα πρωτόκολλα για απ' ευθείας πρόσβαση από τους χρήστες (δεν επηρεάζει τα πρωτόκολλα κωδικών πρόσβασης εφαρμογής)", + "app_name": "Όνομα εφαρμογής", + "app_passwd": "Κωδικός πρόσβασης εφαρμογής", + "app_passwd_protocols": "Επιτρέπομενα πρωτόκολλα για τον κωδικό εφαρμογής", + "automap": "Αυτόματη αντιστοίχηση φακέλων (\"Απεσταλμένα μηνύματα\", \"Απεσταλμένα\" => \"Στάλθηκαν\" κ.τ.λ.)", + "backup_mx_options": "Επιλογές αναμετάδοσης" } } diff --git a/data/web/lang/lang.hu-hu.json b/data/web/lang/lang.hu-hu.json index 2f78d5b21..ff49bc5ee 100644 --- a/data/web/lang/lang.hu-hu.json +++ b/data/web/lang/lang.hu-hu.json @@ -295,7 +295,9 @@ "user_quicklink": "Gyorshivatkozás elrejtése a Felhasználói bejelentkezési oldalra", "validate_license_now": "GUID érvényesítése a licenszszerverrel szemben", "yes": "✓", - "success": "Siker" + "success": "Siker", + "login_page": "Belépő oldal", + "needs_restart": "újraindítást igényel" }, "edit": { "active": "Aktív", @@ -1070,7 +1072,7 @@ "post_domain_add": "A \"sogo-mailcow\" SOGo konténert újra kell indítani egy új tartomány hozzáadása után!

    Kiegészítésképpen a tartományok DNS-konfigurációját is felül kell vizsgálni. A DNS-konfiguráció jóváhagyása után indítsa újra az \"acme-mailcow\"-t, hogy automatikusan generáljon tanúsítványokat az új tartományhoz (autoconfig.<domain>, autodiscover.<domain>).
    Ez a lépés opcionális, és 24 óránként megismétlődik.", "dry": "Szinkronizálás szimulálása", "inactive": "Inaktív", - "kind": "Kedves", + "kind": "Típus", "mailbox_quota_m": "Maximális kvóta postafiókonként (MiB)", "mailbox_username": "Felhasználónév (az e-mail cím bal oldali része)", "max_aliases": "Max. lehetséges álnevek", @@ -1092,9 +1094,9 @@ "exclude": "Objektumok kizárása (regex)", "full_name": "Teljes név", "gal": "Globális címlista", - "goto_ham": "Tanulj sonkaként", + "goto_ham": "Tanítás valódi levélként", "goto_null": "Leveleket csendben eldobni", - "goto_spam": "Tanuld spamként", + "goto_spam": "Tanítás spamként", "syncjob_hint": "Ne feledje, hogy a jelszavakat egyszerű szöveges formában kell elmenteni!", "target_address": "Továbbítási címek", "target_address_info": "Teljes e-mail cím(ek) (vesszővel elválasztva).", @@ -1102,7 +1104,7 @@ "comment_info": "A privát megjegyzés nem látható a felhasználó számára, míg a nyilvános megjegyzés tooltip-ként jelenik meg, amikor a felhasználó áttekintésében a megjegyzésre mutat.", "custom_params": "Egyéni paraméterek", "gal_info": "A GAL tartalmazza a tartomány összes objektumát, és egyetlen felhasználó sem szerkesztheti. A SOGo-ban a Szabad/Elfoglalt információ hiányzik, ha ki van kapcsolva! Indítsa újra a SOGo-t a változások alkalmazásához.", - "hostname": "Házigazda", + "hostname": "Hoszt", "backup_mx_options": "Továbbítási opciók", "custom_params_hint": "Megfelelő: --param=xy, Rossz: --param xy", "delete1": "Törlés a forrásból, ha befejeződött", @@ -1140,6 +1142,125 @@ "sieve_type": "Szűrő típusa", "skipcrossduplicates": "Duplikált üzenetek átugrása mappák között (érkezési sorrendben)", "subscribeall": "Feliratkozás minden mappára", - "syncjob": "Szinkronizálási feladat hozzáadása" + "syncjob": "Szinkronizálási feladat hozzáadása", + "internal": "Belső", + "internal_info": "Belső álnevek csak a saját domain vagy domain álnév számára elérhető.", + "sender_allowed": "Küldés engedélyezése ezzel az aliasszal" + }, + "danger": { + "access_denied": "Hozzáférés megtagatva vagy nem megfelelő űrlap adat", + "alias_domain_invalid": "Az alias domain %s érvénytelen", + "alias_empty": "Az alias cím nem lehet üres", + "alias_goto_identical": "Az alias és a goto cím nem lehetnek azonosak", + "alias_invalid": "Az alias cím %s érvénytelen", + "aliasd_targetd_identical": "Az alias tartomány nem lehet azonos a céltartománnyal: %s", + "aliases_in_use": "A maximális aliasoknak nagyobbnak vagy egyenlőnek kell lenniük mint %d", + "app_name_empty": "Az alkalmazás neve nem lehet üres", + "app_passwd_id_invalid": "Alkalmazás jelszó ID %s érvénytelen", + "authsource_in_use": "A személyazonosság szolgáltatót nem lehet megváltoztatni vagy törölni, mivel ez jelenleg használatban van legalább 1 felhasználónál.", + "bcc_empty": "BCC cél nem lehet üres", + "bcc_exists": "A %s típushoz létezik egy %s típusú BCC térkép.", + "bcc_must_be_email": "A BCC cél %s nem érvényes e-mail cím", + "comment_too_long": "Túl hosszú megjegyzés, max 160 karakter megengedett", + "cors_invalid_method": "Érvénytelen Allow-Method lett megadva", + "cors_invalid_origin": "Érvénytelen Allow-Origin lett megadva", + "defquota_empty": "A postafiókonkénti alapértelmezett kvóta nem lehet 0.", + "demo_mode_enabled": "Demo mód engedélyezve", + "description_invalid": "A %s erőforrás leírása érvénytelen", + "dkim_domain_or_sel_exists": "A \"%s\" DKIM-kulcs létezik, és nem lesz felülírva", + "dkim_domain_or_sel_invalid": "DKIM tartomány vagy szelektor érvénytelen: %s", + "domain_cannot_match_hostname": "A tartomány nem egyezik a hostnévvel", + "domain_exists": "A %s domain már létezik", + "domain_invalid": "A domain név üres vagy érvénytelen", + "domain_not_empty": "Nem lehet eltávolítani a nem üres domaint %s", + "domain_not_found": "Nem található domain %s", + "domain_quota_m_in_use": "A domain kvótának nagyobbnak vagy egyenlőnek kell lennie %s MiB-nál", + "extended_sender_acl_denied": "hiányzó ACL külső küldő cím beállításához", + "extra_acl_invalid": "A \"%s\" külső feladó címe érvénytelen", + "extra_acl_invalid_domain": "Külső feladó \"%s\" érvénytelen tartományt használ", + "fido2_verification_failed": "FIDO2 ellenőrzés sikertelen: %s", + "file_open_error": "A fájl nem nyitható meg írásra", + "filter_type": "Rossz szűrőtípus", + "from_invalid": "A feladó nem lehet üres", + "generic_server_error": "Váratlan szerver hiba keletkezett. Vedd fel a kapcsolatot az adminisztrátorral.", + "global_filter_write_error": "Nem tudott szűrőfájlt írni: %s", + "global_map_invalid": "Globális térkép azonosítója %s érvénytelen", + "global_map_write_error": "Nem tudott globális térképet írni ID %s: %s", + "goto_empty": "Egy alias címnek legalább egy érvényes goto címet kell tartalmaznia.", + "goto_invalid": "Goto cím %s érvénytelen", + "ham_learn_error": "Ham tanulási hiba: %s", + "iam_test_connection": "Kapcsolódás sikertelen", + "imagick_exception": "Hiba: Kép olvasása közben Imagick hiba keletkezett", + "img_dimensions_exceeded": "A kép meghaladja a maximális méretet", + "img_invalid": "A képfájlt nem lehet érvényesíteni", + "img_size_exceeded": "A kép meghaladja a maximális fájl méretet", + "img_tmp_missing": "A képfájlt nem lehet érvényesíteni: Ideiglenes fájl nem található", + "invalid_bcc_map_type": "Érvénytelen a BCC térkép típusa", + "invalid_destination": "A \"%s\" célállomás formátum érvénytelen", + "invalid_filter_type": "Érvénytelen szűrőtípus", + "invalid_host": "Érvénytelen host megadva: %s", + "invalid_mime_type": "Érvénytelen mime típus", + "invalid_nexthop": "A következő ugrás formátuma érvénytelen", + "invalid_nexthop_authenticated": "A következő ugrás más hitelesítő adatokkal létezik, kérjük, először frissítse a meglévő hitelesítő adatokat ehhez a következő ugráshoz.", + "invalid_recipient_map_new": "Érvénytelen új címzett megadása: %s", + "invalid_recipient_map_old": "Érvénytelen eredeti címzett van megadva: %s", + "invalid_reset_token": "Érvénytelen visszaállító kulcs", + "ip_list_empty": "Az engedélyezett IP-k listája nem lehet üres", + "is_alias": "%s már ismert álnév címként", + "is_alias_or_mailbox": "%s már ismert alias, egy postafiók vagy egy alias tartományból kiterjesztett alias cím.", + "is_spam_alias": "%s már ismert ideiglenes alias cím (spam alias cím)", + "last_key": "Az utolsó kulcs nem törölhető, kérjük, helyette deaktiválja a TFA-t.", + "login_failed": "A bejelentkezés sikertelen", + "mailbox_defquota_exceeds_mailbox_maxquota": "Az alapértelmezett kvóta meghaladja a maximális kvótakorlátot", + "mailbox_invalid": "A postafiók neve érvénytelen", + "mailbox_quota_exceeded": "A kvóta meghaladja a tartományi korlátot (max. %d MiB)", + "mailbox_quota_exceeds_domain_quota": "A maximális kvóta meghaladja a tartományi kvótakorlátot", + "mailbox_quota_left_exceeded": "Nincs elég hely (maradék hely: %d MiB)", + "mailboxes_in_use": "A maximális postafiókoknak nagyobbnak vagy egyenlőnek kell lenniük %d-vel.", + "malformed_username": "Hibás felhasználónév", + "map_content_empty": "A térkép tartalma nem lehet üres", + "max_age_invalid": "Maximális kor %s érvénytelen", + "max_alias_exceeded": "Max. aliasok túllépése", + "max_mailbox_exceeded": "Max. postafiókok túllépése (%d %d-ből %d)", + "max_quota_in_use": "A postafiók kvótának nagyobbnak vagy egyenlőnek kell lennie %d MiB-nél", + "maxquota_empty": "A postafiókonkénti maximális kvóta nem lehet 0.", + "mode_invalid": "%s mód érvénytelen", + "mx_invalid": "%s MX rekord érvénytelen", + "mysql_error": "MySQL hiba: %s", + "network_host_invalid": "Érvénytelen hálózat vagy állomás: %s", + "next_hop_interferes": "%s zavarja a nexthop %s-t", + "next_hop_interferes_any": "Egy meglévő következő ugrás zavarja a %s-t.", + "nginx_reload_failed": "Az Nginx újratöltése sikertelen: %s", + "no_user_defined": "Nincs felhasználó által meghatározott", + "object_exists": "Az objektum %s már létezik", + "object_is_not_numeric": "Az érték %s nem numerikus", + "password_complexity": "A jelszó nem felel meg a szabályzatnak", + "password_empty": "A jelszó nem lehet üres", + "password_mismatch": "A megerősítő jelszó nem egyezik", + "password_reset_invalid_user": "A fiók nem található vagy nem lett megadva visszaállításhoz email cím", + "password_reset_na": "A jelszó visszaállítás jelenleg nem elérhető. Vedd fel a kapcsolatot az adminisztrátorral.", + "policy_list_from_exists": "A megadott nevű rekord létezik", + "policy_list_from_invalid": "A rekord érvénytelen formátumú", + "private_key_error": "Privát kulcs hiba: %s", + "pushover_credentials_missing": "Pushover token és/vagy kulcs hiányzik", + "pushover_key": "A pushover kulcs rossz formátumú", + "pushover_token": "A Pushover token rossz formátumú", + "quota_not_0_not_numeric": "A kvótának numerikusnak és >= 0-nak kell lennie.", + "recipient_map_entry_exists": "Létezik egy \"%s\" címzett-térkép bejegyzés", + "redis_error": "Redis hiba lépett fel: %s", + "relayhost_invalid": "A(z) %s elem érvénytelen a leképezésben.", + "release_send_failed": "Az üzenet felszabadítása sikertelen: %s", + "reset_f2b_regex": "A regex-szűrő időtúllépés miatt nem állt le. Próbálja újra, vagy várjon egy kicsit, és töltse újra az oldalt.", + "resource_invalid": "A(z) %s erőforrásnév érvénytelen", + "rl_timeframe": "Érvénytelen időkeret a lekérdezési korláthoz", + "rspamd_ui_pw_length": "A Rspamd UI jelszónak legalább 6 karakter hosszúnak kell lennie.", + "script_empty": "A szkript nem lehet üres", + "sender_acl_invalid": "A küldőhöz tartozó ACL-érték (%s) érvénytelen", + "set_acl_failed": "Az ACL beállítása meghiúsult", + "settings_map_invalid": "Érvénytelen beállítás-leképezési azonosító: %s", + "recovery_email_failed": "A helyreállítási email kiküldése sikertelen. Kérlek, lépj kapcsolatba az adminisztrátorral!", + "reset_token_limit_exceeded": "Túl sok visszaállítási kísérlet. Kérjük, várjon, mielőtt újra próbálkozna.", + "required_data_missing": "Hiányzik a(z) szükséges %s adat", + "tfa_removal_blocked": "A kétfaktoros hitelesítés nem távolítható el, mert elengedhetetlen a fiókod használatához." } } diff --git a/data/web/lang/lang.ja-jp.json b/data/web/lang/lang.ja-jp.json index 74c04248c..6dfa5d87b 100644 --- a/data/web/lang/lang.ja-jp.json +++ b/data/web/lang/lang.ja-jp.json @@ -1187,6 +1187,7 @@ "created_on": "作成日", "daily": "毎日", "day": "日", + "description": "説明", "delete_ays": "削除プロセスを確認してください。", "direct_aliases": "直接エイリアスアドレス", "direct_aliases_desc": "直接エイリアスアドレスは、スパムフィルターおよびTLSポリシー設定の影響を受けます。", @@ -1201,7 +1202,9 @@ "encryption": "暗号化", "excludes": "除外", "expire_in": "有効期限まで", + "expire_never": "有効期限なし", "fido2_webauthn": "FIDO2/WebAuthn", + "forever": "有効期限なし", "force_pw_update": "グループウェア関連サービスにアクセスするには、新しいパスワードを必ず設定する必要があります。", "from": "送信元", "generate": "生成", diff --git a/data/web/lang/lang.lv-lv.json b/data/web/lang/lang.lv-lv.json index 2b433b8e8..df1697282 100644 --- a/data/web/lang/lang.lv-lv.json +++ b/data/web/lang/lang.lv-lv.json @@ -13,7 +13,7 @@ "domain_relayhost": "Mainīt domēna relayhost", "eas_reset": "EAS ierīču atiestatīšana", "extend_sender_acl": "Ļauj paplašināt sūtītāja ACL ar ārējām adresēm", - "login_as": "Pieteikšanās kā pastkastes lietotājam", + "login_as": "Pieteikties kā pastkastes lietotājam", "mailbox_relayhost": "Pasta kastītes relayhost maiņa", "prohibited": "Aizliegts ar ACL", "protocol_access": "Protokola piekļuves maiņa", @@ -38,17 +38,17 @@ "alias_domain": "Aizstājdomēni", "alias_domain_info": "Tikai derīgi domēna vārdi (komatu atdalīti).", "automap": "Mēģiniet automatizēt mapes (\"Nosūtītie vienumi\", \"Nosūtītie\" => \"Nosūtītie\" etc.)", - "backup_mx_options": "Dublējuma MX iespējas", - "delete1": "Dzēst no avota, kad tas ir pabeigts", + "backup_mx_options": "Releja iespējas", + "delete1": "Izdzēst no avota pēc pabeigšanas", "delete2": "Dzēsiet ziņojumus galamērķī, kas nav avotā", - "delete2duplicates": "Dzēst dublikātus galamērķī", + "delete2duplicates": "Izdzēst atkārtojošos vienumus galamērķī", "description": "Apraksts", "domain": "Domēns", "domain_quota_m": "Kopējā domēna kvota (MiB)", "enc_method": "Šifrēšanas metode", "exclude": "Izslēgt objektus (regex)", "full_name": "Pilns vārds", - "goto_null": "Klusām dzēst pastu", + "goto_null": "Klusām atmest pastu", "hostname": "Saimniekdators", "kind": "Veids", "mailbox_quota_m": "Maks. kvota pastkastei (MiB)", @@ -65,8 +65,8 @@ "relay_all": "Pārsūtīt visus saņēmējus", "relay_all_info": "↪ Ja izvēlies nepārsūtīt visus saņēmējus, tad Tev būs nepieciešams pievienot (\"aklo\") pastkasti katram saņēmējam, kas būtu jāpārsūta.", "relay_domain": "Pārsūtīt šo domēnu", - "select": "Lūdzu izvēlaties...", - "select_domain": "Lūdzu sākumā izvēlaties domēnu", + "select": "Lūgums atlasīt…", + "select_domain": "Lūgums vispirms atlasīt domēnu", "sieve_desc": "Īss apraksts", "sieve_type": "Filtra tips", "skipcrossduplicates": "Izlaist dublētus ziņojumus pa mapēm (pirmais nāk, pirmais kalpo)", @@ -77,12 +77,13 @@ "target_domain": "Mērķa domēns", "username": "Lietotājvārds", "validate": "Apstiprināt", - "validation_success": "Apstiprināts veiksmīgi", + "validation_success": "Sekmīgi apstiprināts", "bcc_dest_format": "BCC galamērķim ir jābūt vienai derīgai e-pasta adresei.
    Ja ir nepieciešams nosūtīt kopiju vairākām adresēm, jāizveido aizstājvārds un jāizmanto tas šeit.", "domain_matches_hostname": "Domēns %s atbilst saimniekdatora nosaukumam", "disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)", "app_password": "Pievienot lietotnes paroli", - "app_passwd_protocols": "Atļautie lietotnes paroles protokoli" + "app_passwd_protocols": "Atļautie lietotnes paroles protokoli", + "goto_spam": "Apgūt kā mēstuli" }, "admin": { "access": "Pieeja", @@ -90,12 +91,12 @@ "activate_api": "Aktivizēt API", "active": "Aktīvs", "add": "Pievienot", - "add_domain_admin": "Pievienot domēna administratoru", + "add_domain_admin": "Pievienot domēna pārvaldītāju", "add_forwarding_host": "Pievienot pāradresācijas hostu", "add_relayhost": "Pievienot Relayhost", "add_row": "Pievienot rindu", - "admin": "Administrators", - "admin_details": "Labot administratora detaļas", + "admin": "Pārvaldītājs", + "admin_details": "Labot informāciju par pārvaldītāju", "admin_domains": "Domēna uzdevumi", "api_allow_from": "Atļaut API piekļuvi no šīm IP", "api_key": "API atslēga", @@ -113,16 +114,16 @@ "dkim_keys": "ARC/DKIM atslēgas", "dkim_private_key": "Privāta atslēga", "domain": "Domēns", - "domain_admins": "Domēna administratori", + "domain_admins": "Domēna pārvaldītāji", "edit": "Labot", - "empty": "Nav rezultātu", + "empty": "Nav iznākuma", "f2b_ban_time": "Aizlieguma laiks (s)", "f2b_max_attempts": "Maks. piegājieni", "f2b_netban_ipv4": "IPv4 apakštīkla izmērs, lai piemērotu aizliegumu uz (8-32)", "f2b_netban_ipv6": "IPv6 apakštīkla izmērs, lai piemērotu aizliegumu uz (8-128)", "f2b_parameters": "Fail2ban parametri", "f2b_retry_window": "Atkārtošanas logs (s) priekš maks. piegājiena", - "f2b_whitelist": "Baltā saraksta tīkls/hosts", + "f2b_whitelist": "Atļautie tīkli/resursdatori", "filter_table": "Filtru tabula", "forwarding_hosts": "Hostu pārsūtīšana", "forwarding_hosts_add_hint": "Var norādīt vai nu IPv4/IPv6 adreses, tīklu ar CIDR apzīmējumu, saimniekdatoru nosaukumus (kas tiks atrisināti IP adresēs) vai arī domēna vārdus (kas tiks atrisināti IP adresēs, vaicājot SPF ierakstus, vai, ja tādu nav, MX ierakstus).", @@ -152,7 +153,7 @@ "r_info": "Pelēkie/atspējotie vienumi spēkā esošo ierobežojumu sarakstā mailcow nav zināmi kā derīgi ierobežojumi, un tos nevar pārvietot. Nezināmi ierobežojumi jebkurā gadījumā parādīšanas secībā.
    Jaunus vienumus var pievienot inc/vars.local.inc.php, lai varētu tos pārslēgt.", "recipients": "Adresāts", "refresh": "Atsvaidzināt", - "regen_api_key": "Reģenerēt API atslēgu", + "regen_api_key": "Atkārtoti izveidot API atslēgu", "relay_from": "\"No:\" adrese", "relay_run": "Palaist testu", "relayhosts_hint": "Norādīt no sūtītāja atkarīgas piegādes, lai varētu tos atlasīt domēnu konfigurācijas uzvednē.
    \n Piegādes pakalpojums vienmēr ir \"smtp\", tādējādi tiks mēģināts TLS, kad piedāvāts. Iekļautais TLS (SMTPS) netiek atbalstīts. Tiek ņemts vērā lietotāja atsevišķais izejošā TLS nosacījuma iestatījums.
    \n Ietekmē atlasītos domēnus, tajā skaitā aizstājdomēnus.", @@ -181,7 +182,20 @@ "rspamd_com_settings": "Iestatījuma nosaukums tiks izveidots automātiski. Lūgums zemāk skatīt priekšiestatījumu piemērus. Vairāk informācijas ir Rspamd dokumentācijā", "reset_password_vars": "{{link}} Izveidotā paroles atiestatīšanas saite
    {{username}} Lietotāja, kurš pieprasīja paroles atiestatīšanu, pastkastes nosaukums
    {{username2}} Atkopšanas pastkastes nosaukums
    {{date}} Paroles atiestatīšanas pieprasījuma veikšanas datums
    {{token_lifetime}} Pilnvaras derīgums minūtēs
    {{hostname}} mailcow saimniekdatora nosaukums", "ui_header_announcement_help": "Paziņojums ir redzams visiem lietotājiem, kuri ir pieteikušies, un pieteikšanās ekrānā saskarnē.", - "login_time": "Pieteikšanās laiks" + "login_time": "Pieteikšanās laiks", + "iam_version": "Versija", + "quarantine_max_age": "Lielākais pieļaujamais vecums dienās
    Vērtībai jābūt vienādai ar vai lielākai par 1 dienu.", + "quarantine_max_score": "Atmest paziņojumu, ja e-pasta ziņojuma mēstuļu novērtējums ir augstāks par šo vērtību:
    Noklusējums ir 9999.0", + "options": "Iespējas", + "password_reset_settings": "Paroļu atkopes iestatījumi", + "password_settings": "Paroļu iestatījumi", + "add_admin": "Pievienot pārvaldītāju", + "admins": "Pārvaldītāji", + "admins_ldap": "LDAP pārvaldītāji", + "admin_quicklink": "Paslēpt ātro saiti uz pārvaldītāju pieteikšanās lapu", + "domain_admin": "Domēna pārvaldītājs", + "domainadmin_quicklink": "Paslēpt ātro saiti uz domēna pārvaldītāju pieteikšanās lapu", + "user_quicklink": "Paslēpt ātro saiti uz lietotāju pieteikšanās lapu" }, "danger": { "access_denied": "Piekļuve liegta, vai nepareizi dati", @@ -201,8 +215,8 @@ "goto_empty": "Aizstājādresei jāsatur vismaz viena derīga mērķa adrese", "goto_invalid": "Goto adrese nepareiza", "imagick_exception": "Kļūda: Imagick izņēmums, lasot attēlu", - "img_invalid": "Nevar apstiprināt attēla failu", - "img_tmp_missing": "Nevar apstiprināt attēla failu: pagaidu failu nav atrasts", + "img_invalid": "Nevar apstiprināt attēla datni", + "img_tmp_missing": "Nevar apstiprināt attēla datni: pagaidu datne nav atrasta", "invalid_mime_type": "Nederīgs mime tips", "is_alias": "%s jau ir zināma kā aizstājadrese", "is_alias_or_mailbox": "%s jau ir zināms kā aizstājvārds, pastkaste vai aizstājadrese, kas ir izvērsta no aizstājdomēna.", @@ -234,7 +248,13 @@ "username_invalid": "Lietotājvārds nevar tikt izmantots", "validity_missing": "Lūdzu piešķiriet derīguma termiņu", "domain_cannot_match_hostname": "Domēns nevar atbilst saimniekdatora nosaukumam", - "app_passwd_id_invalid": "Lietotnes paroles Id %s ir nederīgs" + "app_passwd_id_invalid": "Lietotnes paroles Id %s ir nederīgs", + "img_dimensions_exceeded": "Attēls pārsniedz lielāko pieļaujamo attēla lielumu", + "img_size_exceeded": "Attēls pārsniedz lielāko pieļaujamo datnes lielumu", + "version_invalid": "Versija %s ir nederīga", + "generic_server_error": "Atgadījās neparedzēta servera kļūda. Lūgums sazināties ar pārvaldītāju.", + "password_reset_na": "Paroļu atkope šobrīd nav pieejama. Lūgums sazināties ar pārvaldītāju.", + "recovery_email_failed": "Nevarēja nosūtīt atkopes e-pasta ziņojumu. Lūgums sazināties ar pārvaldītāju." }, "diagnostics": { "cname_from_a": "Vērtība, kas iegūta no A/AAAA ieraksta. Tas tiek atbalstīts tik ilgi, kamēr ieraksts norāda uz pareizo resursu.", @@ -250,13 +270,13 @@ "active": "Aktīvs", "alias": "Labot aizstājvārdu", "automap": "Mēģiniet automatizēt mapes (\"Nosūtītie vienumi\", \"Nosūtītie\" => \"Nosūtītie\" utt.)", - "backup_mx_options": "Dublēt MX iespējas", - "delete1": "Dzēst no avota, kad pabeigts", + "backup_mx_options": "Retranslācijas iespējams", + "delete1": "Izdzēst no avota pēc pabeigšanas", "delete2": "Dzēsiet ziņojumus galamērķī, kas nav avotā", - "delete2duplicates": "Dzēst dublikātus galamērķī", + "delete2duplicates": "Izdzēst atkārtojošos vienumus galamērķī", "description": "Apraksts", "domain": "Labot domēnu", - "domain_admin": "Labot domēna administratoru", + "domain_admin": "Labot domēna pārvaldītāju", "domain_quota": "Domēna kvota", "domains": "Domēni", "dont_check_sender_acl": "Atspējot sūtītāju pārbaudi domēnam %s (+ aizstājdomēni)", @@ -273,7 +293,7 @@ "max_aliases": "Lielākais aizstājvārdu skaits", "max_mailboxes": "Maks. iespējamās pastkastes", "max_quota": "Maks. kvota uz pastkasti (MiB)", - "maxage": "Lielākais ziņojumu, kuri tiks vaicāti attālajā serverī, vecums dienās
    (0 = neņemt vērā vecumu)", + "maxage": "Lielākais pieļaujamais ziņojumu, kuri tiks vaicāti attālajā serverī, vecums dienās
    (0 = neņemt vērā vecumu)", "maxbytespersecond": "Maks. baiti sekundē (0 ir vienāds ar neierobežotu skaitu)", "mins_interval": "Intervāls (min)", "multiple_bookings": "Vairāki rezervējumi", @@ -292,8 +312,8 @@ "sieve_type": "Filtra tips", "skipcrossduplicates": "Izlaist dublētus ziņojumus pa mapēm (pirmais nāk, pirmais kalpo)", "spam_alias": "Izveidot vai mainīt laika ierobežotas aizstājadreses", - "spam_policy": "Pievienot vai noņemt vienumus baltajā-/melnajā sarakstā", - "spam_score": "Iestatīt pielāgotu surogātpasta vērtējumu", + "spam_policy": "Pievienot vai noņemt vienumus atļautajā/liegumu sarakstā", + "spam_score": "Iestatīt pielāgotu mēstules vērtējumu", "subfolder2": "Sinhronizēt galamērķa apakšmapē
    (tukšs = neizmantot apakšmapi)", "syncjob": "Labot sinhronizācijas darbu", "target_address": "Mērķa adrese/s (atdalītas ar komatu)", @@ -316,17 +336,22 @@ "disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)", "app_passwd_protocols": "Atļautie lietotnes paroles protokoli", "allowed_protocols": "Atļautie protokoli tiešai lietotāja piekļuvei (neietekmē lietotnes paroles protokolus)", - "app_passwd": "Lietotnes parole" + "app_passwd": "Lietotnes parole", + "mta_sts_version": "Versija", + "mta_sts_version_info": "Norāda MTA-STS standarta versiju – pašreiz ir derīga tikai STSv1.", + "sender_acl_disabled": "Sūtītāja pārbaude ir atspējota", + "admin": "Labot pārvaldītāju" }, "footer": { "cancel": "Atcelt", - "confirm_delete": "Apstiprināt dzēšanu", - "delete_now": "Dzēst tagad", + "confirm_delete": "Apstiprināt izdzēšanu", + "delete_now": "Izdzēst tagad", "delete_these_items": "Lūgums apstiprināt izmaiņas šim objekta Id", "loading": "Lūgums uzgaidīt...", "restart_container": "Restartēt konteineri", "restart_container_info": "Svarīgi: nesteidzīga pārsāknēšana var aizņemt ilgāku laiku. Lūgums uzgaidīt, līdz tā tiek pabeigta.", - "restart_now": "Pārsāknēt tagad" + "restart_now": "Pārsāknēt tagad", + "hibp_nok": "Sakrīt. Šī, iespējams, ir bīstama parole." }, "header": { "administration": "Konfigurācija un informācija", @@ -348,7 +373,14 @@ "username": "Lietotājvārds", "fido2_webauthn": "FIDO/WebAuthn pieteikšanās", "mobileconfig_info": "Lūgums pieteikties kā pastkastes lietotājam, lai lejupielādētu pieprasīto Apple savienojuma profilu.", - "other_logins": "vai pieslēgties ar" + "other_logins": "vai pieteikties ar", + "forgot_password": "> Aizmirsta parole?", + "login_linkstext": "Nepareiza pieteikšanās?", + "login_domainadmintext": "Pieteikties kā domēna pārvaldītājam", + "login_admintext": "Pieteikties kā pārvaldītājam", + "login_user": "Lietotu pieteikšanās", + "login_dadmin": "Domēna pārvaldītāju pieteikšanās", + "login_admin": "Pārvaldītāju pieteikšanās" }, "mailbox": { "action": "Rīcība", @@ -383,13 +415,13 @@ "description": "Apraksts", "dkim_key_length": "DKIM atslēgas garums (bits)", "domain": "Domēns", - "domain_admins": "Domēna administratori", + "domain_admins": "Domēna pārvaldītāji", "domain_aliases": "Domēna aizstājvārdi", "domain_quota": "Kvota", "domain_quota_total": "Kopējais domēna ierobežojums", "domains": "Domēns", "edit": "Labot", - "empty": "Nav rezultātu", + "empty": "Nav iznākuma", "excludes": "Izslēdzot", "filter_table": "Filtra tabula", "filters": "Filtri", @@ -448,13 +480,15 @@ "add_alias_expand": "Izvērst aizstājvārdu pār aizstājdomēniem", "alias_domain_alias_hint": "Aizstājvārdi netiek automātiski piemēroti domēnu aizstājvārdiem. Aizstājadrese my-alias@domain nenosedz adresi my-alias@alias-domain (kur \"alias-domain\" ir iedomāts \"domain\" aizstājdomēns).
    Lūgums izmantot sieta atlasi, lai pārvirzītu pastu uz ārēju pastkasti (skatīt cilti \"Atlasīšana\" vai izmantot SOGo -> Pārsūtītājs). \"Izvērst aizstājvārdu pār aizstājdomēniem\" ir izmantojams, lai automātiski pievienotu trūkstošos aiztājvārdus.", "alias_domain_backupmx": "Aizstājdomēns ir neaktīvs retranslācijas domēnam", - "disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)" + "disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)", + "sieve_preset_1": "Atmest e-pasta vēstules ar iespējami bīstamiem datņu veidiem", + "syncjob_last_run_result": "Pēdējās izpildes iznākums" }, "quarantine": { "action": "Darbības", "atts": "Pielikumi", - "check_hash": "Meklēt faila hašu @ VT", - "empty": "Nav rezultātu", + "check_hash": "Meklēt datnes jaucējvērtību @ VT", + "empty": "Nav iznākuma", "qid": "Rspamd QID", "qitem": "Karantīnas vienumi", "quarantine": "Karantīna", @@ -463,7 +497,7 @@ "received": "Saņemtie", "recipients": "Adresāts", "release": "Atbrīvot", - "release_body": "Šim ziņojumam mēs esam pievienojuši jūsu ziņojumu kā eml failu.", + "release_body": "Mēs pievienojām Tavu ziņojumu kā .eml datni šim ziņojumam.", "release_subject": "Potenciāli kaitīgs karantīnas vienums %s", "remove": "Noņemt", "sender": "Sūtītājs (SMTP)", @@ -473,8 +507,14 @@ "text_plain_content": "Saturs (teksts/vienkāršs)", "toggle_all": "Pārslēgt visu", "disabled_by_config": "Pašreizējā sistēmas konfigurācija atspējo karantīnu. Lūgums iestatīt \"saglabāšanu katrai pastkastītei\" un \"lielākais pieļaujamais lielums\" karantīnas vienumiem.", - "qhandler_success": "Pieprasījums veiksmīgi nosūtīts sistēmai. Tagad var aizvērt logu.", - "qinfo": "Karantīnas sistēma datubāzē saglabās noraidīto pastu (sūtītājam netiks radīts iespaids par piegādātu pastu), kā arī pastu, kas tiek piegādāts kā kopija pastkastes mēstuļu mapē.\n
    \"Apgūt kā surogātpastu un izdzēst\" apgūs ziņojumu kā surogātpastu ar Bajesa teorēmu un aprēķinās arī nestriktas jaucējvērtības, lai nākotnē noraidītu līdzīgus ziņojumus.\n
    Lūgums apzināties, ka vairāku ziņojumu apgūšana var būt laikietilpīga atkarībā no sistēmas.
    Melnā saraksta vienumi karantīnā netiek iekļauti." + "qhandler_success": "Pieprasījums sekmīgi nosūtīts sistēmai. Logu tagad var aizvērt.", + "qinfo": "Karantīnas sistēma datubāzē saglabās noraidīto pastu (sūtītājam netiks radīts iespaids par piegādātu pastu), kā arī pastu, kas tiek piegādāts kā kopija pastkastes mēstuļu mapē.\n
    \"Apgūt kā surogātpastu un izdzēst\" apgūs ziņojumu kā surogātpastu ar Bajesa teorēmu un aprēķinās arī nestriktas jaucējvērtības, lai nākotnē noraidītu līdzīgus ziņojumus.\n
    Lūgums apzināties, ka vairāku ziņojumu apgūšana var būt laikietilpīga atkarībā no sistēmas.
    Lieguma saraksta vienumi karantīnā netiek iekļauti.", + "danger": "Bīstamība", + "notified": "Paziņots", + "refresh": "Atsvaidzināt", + "rspamd_result": "Rspamd iznākums", + "settings_info": "Lielākais pieļaujamais karantējamo vienumu daudzums: %s
    Lielākais pieļaujamais e-pasta lielums: %s MiB", + "spam_score": "Novērtējums" }, "queue": { "queue_manager": "Rindas pārvaldnieks", @@ -485,7 +525,7 @@ "imap_smtp_server_auth_info": "Lūgums izmantot pilnu e-pasta adresi un PLAIN autentificēšanās mehānismu.
    \nPieteikšanās dati tiks šifrēti ar servera puses obligātu šifrēšanu." }, "success": { - "admin_modified": "Izmaiņas administrātoram ir saglabātas", + "admin_modified": "Pārvaldītāja izmaiņas tika saglabātas", "alias_added": "Aizstājadrese %s (%d) tika pievienota", "alias_domain_removed": "Aizstājdomēns %s tika noņemts", "alias_modified": "Aizstājadreses izmaiņas %s tika saglabātas", @@ -496,17 +536,17 @@ "dkim_added": "DKIM atslēga saglabāta", "dkim_removed": "DKIM atslēga %s ir noņemta", "domain_added": "Pievienots domēns %s", - "domain_admin_added": "Domēna administrātors %s pievienots", - "domain_admin_modified": "Izmaiņas domēna administrātoram %s ir saglabātas", - "domain_admin_removed": "Domēna administrators %s tika noņemts", + "domain_admin_added": "Tika pievienots domēna pārvaldītājs %s", + "domain_admin_modified": "Domēna pārvaldītāja %s izmaiņas tika saglabātas", + "domain_admin_removed": "Tika noņemts domēna pārvaldītājs %s", "domain_modified": "Izmaiņas domēnam %s ir saglabātas", "domain_removed": "Domēns %s ir noņemts", "eas_reset": "ActiveSync ierīces priekš lietotāja %s tika atiestatītas", "f2b_modified": "Fail2ban parametru izmaiņas tika saglabātas", "forwarding_host_added": "Pāradresācijas hosts %s pievienotsd", "forwarding_host_removed": "Pāradresācijas hosts %s noņemts", - "item_deleted": "Vērtība %s veiksmīgi dzēsta", - "items_deleted": "Vērtība %s veiksmīgi dzēsta", + "item_deleted": "Vienums %s izdzēsts sekmīgi", + "items_deleted": "Vienums %s izdzēsts sekmīgi", "items_released": "Atlasītie vienumi tika izlaisti", "mailbox_added": "Pastkaste %s ir pievienota", "mailbox_modified": "Izmaiņas pastkastei %s ir saglabātas", @@ -519,34 +559,38 @@ "resource_modified": "Izmaiņas %s ir saglabātas", "resource_removed": "Resurs %s tika noņemts", "ui_texts": "Saglabāt UI izmaiņas tekstiem", - "upload_success": "Faila augšupielāde veiksmīga", + "upload_success": "Datne sekmīgi augšupielādēta", "verified_fido2_login": "Apliecināta FIDO2 pieteikšanās", "verified_webauthn_login": "Apliecināta WebAuthn pieteikšanās", "verified_totp_login": "Apliecināta TOTP pieteikšanās", "verified_yotp_login": "Apliecināta Yubico OTP pieteikšanās", "app_passwd_removed": "Noņemta lietotnes parole ar Id %s", - "app_passwd_added": "Pievienota jauna lietotnes parole" + "app_passwd_added": "Pievienota jauna lietotnes parole", + "f2b_banlist_refreshed": "Liegumu saraksta Id tika sekmīgi atsvaidzināts.", + "admin_added": "Tika pievienots pārvaldītājs %s", + "admin_removed": "Tika noņemts pārvaldītājs %s" }, "tfa": { "api_register": "%s izmanto Yubico Cloud API. Lūdzu iegūstiet API atslēgu priekš Jūsu atslēgashere", "confirm": "Apstiprināt", - "confirm_totp_token": "Lūdzu apstipriniet Jūsu izmaiņas ievadot uzģenerēto tekstu", + "confirm_totp_token": "Lūgums apstiprināt savas izmaiņas ar izveidotās tekstvienības ievadīšanu", "delete_tfa": "Atspējot TFA", - "disable_tfa": "Atspējot TFA līdz nākamajai veiksmīgajai pieteikšanās reizei", + "disable_tfa": "Atspējot TFA līdz nākamajai sekmīgajai pieteikšanās reizei", "enter_qr_code": "TOTP kods, ja Tava ierīce nevar nolasīt kvadrātkodus", "key_id": "Jūsu YubiKey identifikators", "key_id_totp": "Identifikators Jūsu atslēgai", "none": "Deaktivizēt", "scan_qr_code": "Lūdzu, skenējiet šo kodu ar savu autentifikācijas lietojumprogrammu vai ievadiet kodu manuāli.", - "select": "Lūdzu izvēlaties", + "select": "Lūgums atlasīt", "set_tfa": "Uzstādīt difi faktoru autentifik;acijas metodi", "tfa": "Divpakāpju pieteikšanās", "totp": "Uz laiku bāzēta vienreizēja parole (Google Autentifikātors utt.)", "webauthn": "WebAuthn autentifikācija", "waiting_usb_auth": "Gaida USB ierīci...

    Lūdzu, tagad nospiežiet pogu uz Jūsu WebAuthn USB ierīces.", - "waiting_usb_register": "Gaida USB ierīci...

    Lūdzu augšā ievadiet Jūsu paroli un apstipriniet WebAuthn reģistrāciju nospiežot pogu uz Jūsu WebAuthn USB ierīces.", + "waiting_usb_register": "Gaida USB ierīci...

    Lūgums augstāk ievadīt savu paroli un apstiprināt reģistrēšanos ar USB ierīces pogas nospiešanu.", "yubi_otp": "Yubico OTP autentifikators", - "authenticators": "Autentificētāji" + "authenticators": "Autentificētāji", + "u2f_deprecated_important": "Lūgums reģistrēt savu atslēgu pārvaldības lapā ar jauno WebAuthn veidu." }, "user": { "action": "Rīcība", @@ -589,7 +633,7 @@ "new_password_repeat": "Paroles apstiprinājums (atkārtoti)", "no_active_filter": "Nav pieejami aktīvi filtri", "no_record": "Nav ieraksta", - "password_now": "Pašreizējā parole (Apstiprināt izmaiņas)", + "password_now": "Pašreizējā parole (apstiprināt izmaiņas)", "remove": "Noņemt", "running": "Darbojas", "save_changes": "Saglabāt izmaiņas", @@ -599,11 +643,11 @@ "spam_aliases": "Pagaidu e-pasta aizstājvārdi", "spamfilter": "Mēstuļu filtrs", "spamfilter_behavior": "Reitings", - "spamfilter_bl": "Melnais saraksts", - "spamfilter_bl_desc": "No melnajā sarakstā iekļautajām e-pasta adresēm saņemtās vēstules vienmēr tiks atzīmētas kā mēstules un noraidītas. Noraidītais pasts netiks ievietots karantīnā. Var izmantot aizstājzīmes. Atlasīšana tiek pielietota tikai tiešiem aizstājvārdiem (aizstājvārdiem ar vienu mērķa pastkasti), izņemot visu tverošos aizstājvārdus un pašu pastkasti.", + "spamfilter_bl": "Liegumu saraksts", + "spamfilter_bl_desc": "No lieguma sarakstā iekļautajām e-pasta adresēm saņemtās vēstules vienmēr tiks atzīmētas kā mēstules un noraidītas. Noraidītais pasts netiks ievietots karantīnā. Var izmantot aizstājzīmes. Atlasīšana tiek pielietota tikai tiešiem aizstājvārdiem (aizstājvārdiem ar vienu mērķa pastkasti), izņemot visu tverošos aizstājvārdus un pašu pastkasti.", "spamfilter_default_score": "Noklusējuma vērtības", "spamfilter_green": "Zaļš: šī nav mēstule", - "spamfilter_hint": "Pirmā vērtība norāda uz zemu \"Spam vērtējumu\" vērtējumu, otra vērtība par \"Augstu spam vērtējumu\".", + "spamfilter_hint": "Pirmā vērtība norāda uz zemu \"mēstules novērtējumu\", otrā atspoguļo \"augstu mēstules novērtējumu\".", "spamfilter_red": "Sarkans: Šī vēstule noteikti ir spams un tiek nekavējoties noraidīta", "spamfilter_table_action": "Darbība", "spamfilter_table_add": "Pievienot vienību", @@ -611,8 +655,8 @@ "spamfilter_table_empty": "Nav datu ko parādīt", "spamfilter_table_remove": "noņemt", "spamfilter_table_rule": "Noteikums", - "spamfilter_wl": "Baltais saraksts", - "spamfilter_wl_desc": "No baltā saraksta e-pasta adresēm saņemtās vēstules nekad netiks atzīmētas kā mēstules. Var tikt izmantotas aizstājzīmes. Atlase tiek piemērota tikai tiešiem aizstājvārdiem (aizstājvārdiem ar vienu mērķa pastkasti), izņemot visu tverošos aizstājvārdus un pašu pastkasti.", + "spamfilter_wl": "Atļautais saraksts", + "spamfilter_wl_desc": "No atļautā saraksta e-pasta adresēm saņemtās vēstules nekad netiks atzīmētas kā mēstules. Var tikt izmantotas aizstājzīmes. Atlase tiek piemērota tikai tiešiem aizstājvārdiem (aizstājvārdiem ar vienu mērķa pastkasti), izņemot visu tverošos aizstājvārdus un pašu pastkasti.", "spamfilter_yellow": "Dzeltens: šī vēstule visticamāk ir spams un tiks pārvietota uz Junk mapi", "status": "Status", "sync_jobs": "Sinhronizācijas uzdevumi", @@ -644,15 +688,21 @@ "change_password_hint_app_passwords": "Kontā ir %d lietotņu paroles, kas netiks mainītas. Lai pārvaldītu tās, jādodas uz cilni \"Lietotņu paroles\".", "with_app_password": "ar lietotnes paroli", "apple_connection_profile_with_app_password": "Jauna lietotnes parole ir izveidota un pievienota profilam, lai ierīces iestatīšanas laikā nebūtu nepieciešams ievadīt paroli. Lūgums nekopīgot datni, jo tā nodrošina pilnu piekļuvi pastkastei.", - "tfa_info": "Divpakāpju autentificēšanās palīdz aizsargāt kontu.Ja tā ir iespējota, var būt nepieciešamas lietotņu paroles, lai pieteiktos lietotnēs vai pakalpojumos, kas nenodrošina divpakāpju autentificēšanos (piem., e-pasta klienti).", + "tfa_info": "Divpakāpju autentificēšanās palīdz aizsargāt kontu.Ja tā ir iespējota, ir nepieciešamas lietotņu paroles, lai pieteiktos lietotnēs vai pakalpojumos, kas nenodrošina divpakāpju autentificēšanos (piem., e-pasta klienti).", "app_passwds": "Lietotņu paroles", - "create_app_passwd": "Izveidot lietotnes paroli" + "create_app_passwd": "Izveidot lietotnes paroli", + "empty": "Nav iznākuma", + "quarantine_notification_info": "Tiklīdz paziņojums ir nosūtīts, vienumi tiks atzīmēti kā \"paziņoti\", un par šo vienumu vairs netiks sūtīti paziņojumi.", + "sender_acl_disabled": "Sūtītāja pārbaude ir atspējota", + "syncjob_last_run_result": "Pēdējās izpildes iznākums" }, "datatables": { "paginate": { "first": "Pirmā", "last": "Pēdējā" - } + }, + "emptyTable": "Tabulā nav datu", + "search": "Meklēt:" }, "debug": { "last_modified": "Pēdējoreiz mainīts", @@ -672,7 +722,8 @@ "warning": { "domain_added_sogo_failed": "Domēns pievienots, bet neizdevās pārsāknēt SOGO. Lūgums pārbaudīt servera žurnālus.", "dovecot_restart_failed": "Dovecot neizdevās pārsāknēties. Lūgums pārbaudīt žurnālus", - "is_not_primary_alias": "Izlaists aizstājvārds %s, kas nav galvenais" + "is_not_primary_alias": "Izlaists aizstājvārds %s, kas nav galvenais", + "no_active_admin": "Nevar deaktivēt pēdējo aktīvo pārvaldītāju" }, "oauth2": { "access_denied": "Lūgums pieteikties kā pastkastes īpašniekam, lai nodrošinātu piekļuvi ar OAuth2." diff --git a/data/web/lang/lang.nb-no.json b/data/web/lang/lang.nb-no.json index 3b267b9ea..bca608745 100644 --- a/data/web/lang/lang.nb-no.json +++ b/data/web/lang/lang.nb-no.json @@ -185,11 +185,12 @@ "protocol_access": "Endre protokolltilgang", "pushover": "Pushover", "quarantine": "Karantenehandlinger", - "quarantine_attachments": "Sett vedlegg i karantene", + "quarantine_attachments": "Se vedlegg i karantene", "quarantine_category": "Endre varslingskategori for karantene", "quarantine_notification": "Endre karantenevarslinger", "domain_desc": "Endre domenebeskrivelse", - "extend_sender_acl": "Tillat utvidelse av sender-ACL fra eksterne adresser" + "extend_sender_acl": "Tillat utvidelse av sender-ACL fra eksterne adresser", + "pw_reset": "Tillat endring av brukerpassord" }, "add": { "app_passwd_protocols": "Tillatte protokoller for app-passord", diff --git a/data/web/lang/lang.nl-nl.json b/data/web/lang/lang.nl-nl.json index 71ec97209..6ed9e322a 100644 --- a/data/web/lang/lang.nl-nl.json +++ b/data/web/lang/lang.nl-nl.json @@ -26,7 +26,9 @@ "syncjobs": "Sync jobs", "tls_policy": "Versleutelingsbeleid", "unlimited_quota": "Onbeperkte quota voor mailboxen", - "domain_desc": "Wijzig domeinbeschrijving" + "domain_desc": "Wijzig domeinbeschrijving", + "pw_reset": "Toegang om mailcow gebruikers wachtwoord te resetten", + "domain_relayhost": "Verander relayhost voor een domein" }, "add": { "activate_filter_warn": "Alle andere filters worden gedeactiveerd zolang deze geactiveerd is.", @@ -104,7 +106,11 @@ "validate": "Verifieer", "validation_success": "Succesvol geverifieerd", "tags": "Tags", - "bcc_dest_format": "BCC-bestemming moet één geldig e-mailadres zijn.
    Als u een kopie naar meerdere adressen wilt sturen, maak dan een alias aan en gebruik die hier." + "bcc_dest_format": "BCC-bestemming moet één geldig e-mailadres zijn.
    Als u een kopie naar meerdere adressen wilt sturen, maak dan een alias aan en gebruik die hier.", + "dry": "Synchronisatie simuleren", + "internal": "Intern", + "internal_info": "Interne aliassen zijn alleen toegankelijk vanuit het eigen (alias)domein.", + "sender_allowed": "Toestaan om te verzenden als deze alias" }, "admin": { "access": "Toegang", @@ -138,7 +144,7 @@ "arrival_time": "Aankomsttijd", "authed_user": "Geauthenticeerde gebruiker", "ays": "Weet je zeker dat je deze actie wilt uitvoeren?", - "ban_list_info": "Bekijk de lijst met verbannen IP-adressen hieronder: netwerk (resterende tijd) - [acties].
    Rode labels geven een permanente verbanning aan.
    Het kan enkele seconden duren voordat wijzigingen hieronder zichtbaar zijn.", + "ban_list_info": "Bekijk de lijst met verbannen IP-adressen hieronder: netwerk (resterende tijd) - [acties].
    IP-adressen die in de wachtrij staan om te worden gedeblokkeerd, worden binnen enkele seconden uit de actieve banlijst verwijderd.
    \n
    Rode labels geven actieve permanente blokkades door denylisting aan.", "change_logo": "Logo", "configuration": "Instellingen", "convert_html_to_text": "Converteer HTML naar plaintext", @@ -171,7 +177,7 @@ "excludes": "Exclusief", "f2b_ban_time": "Verbanningstijd (s)", "f2b_ban_time_increment": "Verbanningstijd wordt verhoogd met elk verbanning", - "f2b_blacklist": "Netwerken/hosts op de blacklist", + "f2b_blacklist": "Netwerken/hosts op de denylist", "f2b_filter": "Regex-filters", "f2b_list_info": "Een host of netwerk op de blacklist staat altijd boven eenzelfde op de whitelist. Het doorvoeren van wijzigingen kan enkele seconden in beslag nemen.", "f2b_max_attempts": "Maximaal aantal pogingen", @@ -206,7 +212,7 @@ "link": "Link", "loading": "Even geduld aub...", "logo_info": "De afbeelding zal worden geschaald naar een hoogte van 40px voor de navigatiebar, en naar een breedte van 250px voor de startpagina.", - "lookup_mx": "Match bestemming aan MX (gebruik .outlook.com om alle mail gericht aan MX *.outlook.com over deze hop te laten gaan)", + "lookup_mx": "Bestemming is een reguliere expressie die wordt gematcht met de MX-naam (.*\\.google\\.com om alle e-mail die gericht is aan een MX die eindigt op google.com via deze hop te routeren)", "main_name": "\"Mailcow\"", "merged_vars_hint": "Grijze rijen zijn samengevoegd van vars.(local.)inc.php en kunnen niet worden gewijzigd.", "message": "Bericht", @@ -275,7 +281,7 @@ "rspamd_com_settings": "Een beschrijving voor deze instelling zal automatisch worden gegenereerd, gebruik de onderstaande presets als voorbeeld. Raadpleeg de Rspamd-documentatie voor meer informatie.", "rspamd_global_filters": "Globale filters", "rspamd_global_filters_agree": "Ik ben me ervan bewust dat aanpassingen desastreuze gevolgen kunnen hebben", - "rspamd_global_filters_info": "Ieder globaal filter heeft zijn eigen functie, zie de namen.", + "rspamd_global_filters_info": "Globale filtermaps bevatten verschillende soorten globale deny- en allowlists.", "rspamd_global_filters_regex": "De velden kunnen uitsluitend regular expressions bevatten met het formaat \"/pattern/options\", bijvoorbeeld /.+@domain\\.tld/i.
    Ondanks dat alle invoer wordt gecontroleerd op fouten, is het toch mogelijk dat Rspamd onbruikbaar wordt als deze de invoer niet kan lezen.
    Als je problemen ervaart, herstart Rspamd dan om de filters opnieuw te laten lezen.
    Elementen op de blacklist zijn uitgesloten van de quarantaine.", "rspamd_settings_map": "Rspamd", "sal_level": "Moo-level", @@ -337,7 +343,71 @@ "admins_ldap": "LDAP administrators", "api_read_only": "Alleen-lezen toegang", "api_read_write": "Lees en schrijf toegang", - "login_time": "Login tijd" + "login_time": "Login tijd", + "admin_quicklink": "Snelle link naar de admin-loginpagina verbergen", + "allowed_methods": "Toegestane toegangsmethoden", + "app_hide": "Verberg voor login", + "copy_to_clipboard": "Tekst gekopieerd naar het klembord!", + "login_page": "Loginpagina", + "domainadmin_quicklink": "Snelle link naar de domeinbeheerder-loginpagina verbergen", + "f2b_manage_external": "Fail2Ban extern beheren", + "f2b_manage_external_info": "Fail2Ban zal nog steeds de banlijst bijhouden, maar zal niet actief regels instellen om verkeer te blokkeren. Gebruik de onderstaande gegenereerde banlijst om het verkeer extern te blokkeren.", + "filter": "Filter", + "force_sso_text": "Als een externe OIDC-provider is geconfigureerd, verbergt deze optie de standaard Mailcow-loginformulieren en wordt alleen de Single SignOn-knop weergegeven.", + "force_sso": "Mailcow-login uitschakelen en alleen Single SignOn tonen", + "iam": "Identiteitsprovider", + "iam_attribute_field": "Attribuutveld", + "iam_authorize_url": "Autorisatie endpoint", + "iam_auth_flow": "Autorisatie flow", + "iam_auth_flow_info": "Naast de Authorization Code Flow (Standard Flow in Keycloak), die wordt gebruikt voor Single SignOn-login, ondersteunt Mailcow ook een authenticatiestroom met directe inloggegevens. De Mailpassword Flow probeert de gebruikersgegevens te valideren door gebruik te maken van de Keycloak Admin REST API. Mailcow haalt het gehashte wachtwoord op uit het attribuut mailcow_password, dat in Keycloak is gekoppeld.", + "iam_client_id": "Client ID", + "iam_client_secret": "Client Secret", + "iam_client_scopes": "Clientscopes", + "iam_default_template": "Standaardsjabloon", + "iam_default_template_description": "Als er geen sjabloon aan een gebruiker is toegewezen, wordt het standaardsjabloon gebruikt voor het aanmaken van de mailbox, maar niet voor het bijwerken van de mailbox.", + "iam_description": "Configureer een externe provider voor authenticatie. De mailboxen van gebruikers worden automatisch aangemaakt bij hun eerste login, mits er een attribuutkoppeling is ingesteld.", + "iam_extra_permission": "Om de volgende instellingen te laten werken, heeft de mailcow-client in Keycloak een service account nodig en de toestemming view-users.", + "iam_host": "Hostname", + "iam_host_info": "Voer één of meerdere LDAP-hosts in, gescheiden door komma’s.", + "iam_import_users": "Importeer gebruikers", + "iam_login_provisioning": "Gebruikers automatisch aanmaken bij het inloggen", + "iam_mapping": "Attribuutkoppeling", + "iam_bindpass": "Bind-wachtwoord", + "iam_periodic_full_sync": "Periodieke volledige synchronisatie", + "iam_port": "Poort", + "iam_realm": "Realm", + "iam_redirect_url": "Redirect-URL", + "iam_rest_flow": "Mailwachtwoord-flow", + "iam_server_url": "Server Url", + "iam_sso": "Single SignOn", + "iam_sync_interval": "Synchronisatie-/importinterval (min)", + "iam_test_connection": "Verbinding testen", + "iam_token_url": "Token endpoint", + "iam_userinfo_url": "Gebruikersinfo endpoint", + "iam_username_field": "Gebruikersnaam veld", + "iam_use_ssl": "Gebruik SSL", + "iam_use_ssl_info": "Als SSL wordt ingeschakeld en de poort is ingesteld op 389, wordt deze automatisch aangepast naar 636.", + "iam_use_tls": "Gebruik StartTLS", + "iam_use_tls_info": "Als TLS wordt ingeschakeld, moet je de standaardpoort voor je LDAP-server (389) gebruiken. SSL-poorten kunnen niet worden gebruikt.", + "iam_version": "Versie", + "ignore_ssl_error": "Negeer SSL foutmeldingen", + "ip_check_opt_in": "Kies ervoor om de externe diensten ipv4.mailcow.email en ipv6.mailcow.email te gebruiken om externe IP-adressen te bepalen.", + "needs_restart": "vereist een herstart", + "no": "✕", + "password_reset_info": "Als er geen herstel-e-mailadres is opgegeven, kan deze functie niet worden gebruikt.", + "password_reset_settings": "Instellingen voor wachtwoordherstel", + "password_reset_tmpl_html": "HTML sjabloon", + "password_reset_tmpl_text": "Tekst sjabloon", + "password_settings": "Wachtwoordinstellingen", + "quicklink_text": "Toon of verberg snelle links naar andere inlogpagina’s onder het inlogformulier", + "queue_unban": "deblokkeren", + "reset_password_vars": "{{link}} De gegenereerde link om het wachtwoord te resetten
    {{username}} De mailboxnaam van de gebruiker die het wachtwoordherstel heeft aangevraagd
    {{username2}} De naam van de herstelmailbox
    {{date}} De datum waarop het verzoek tot wachtwoordherstel is gedaan
    {{token_lifetime}} De geldigheidsduur van de token in minuten
    {{hostname}} De mailcow-hostnaam", + "restore_template": "Leeg laten om het standaardsjabloon te herstellen.", + "task": "Taak", + "transport_test_rcpt_info": "• Gebruik null@hosted.mailcow.de om relaying naar een externe bestemming te testen.", + "user_link": "Gebruikerslink", + "user_quicklink": "Snelle link naar de gebruikers-loginpagina verbergen", + "yes": "✓" }, "danger": { "access_denied": "Toegang geweigerd of ongeldige gegevens", @@ -463,7 +533,27 @@ "demo_mode_enabled": "Demo modus is ingeschakeld", "template_exists": "Sjabloon %s bestaat al", "template_id_invalid": "Sjabloon ID %s ongeldig", - "template_name_invalid": "Sjabloon naam ongeldig" + "template_name_invalid": "Sjabloon naam ongeldig", + "tfa_removal_blocked": "Twee-factor-authenticatie kan niet worden verwijderd omdat dit vereist is voor uw account.", + "authsource_in_use": "De identiteitsprovider kan niet worden gewijzigd of verwijderd omdat deze momenteel door één of meerdere gebruikers wordt gebruikt.", + "cors_invalid_origin": "Ongeldige Allow-Origin opgegeven", + "extended_sender_acl_denied": "Ontbrekende ACL om externe afzenderadressen in te stellen", + "generic_server_error": "Er is een onverwachte serverfout opgetreden. Neem contact op met uw beheerder.", + "iam_test_connection": "Verbinding mislukt", + "img_dimensions_exceeded": "De afbeelding overschrijdt de maximale afbeeldingsgrootte", + "img_size_exceeded": "De afbeelding overschrijdt de maximale afbeeldingsgrootte", + "invalid_reset_token": "Ongeldige reset-token", + "max_age_invalid": "Maximale leeftijd %s is ongeldig", + "mode_invalid": "Mode %s is ongeldig", + "mx_invalid": "MX-record %s is ongeldig", + "password_reset_invalid_user": "Mailbox niet gevonden of er is geen herstel-e-mailadres ingesteld", + "password_reset_na": "Wachtwoordherstel is momenteel niet beschikbaar. Neem contact op met uw beheerder.", + "recovery_email_failed": "Kon geen herstel-e-mail verzenden. Neem contact op met uw beheerder.", + "required_data_missing": "Vereiste gegevens %s ontbreken", + "reset_token_limit_exceeded": "De limiet voor reset-tokens is overschreden. Probeer het later opnieuw.", + "to_invalid": "Ontvanger mag niet leeg zijn", + "webauthn_username_failed": "De geselecteerde authenticator behoort tot een ander account", + "version_invalid": "Versie %s is ongeldig" }, "debug": { "chart_this_server": "Grafiek (deze server)", @@ -520,7 +610,7 @@ "alias": "Wijzig alias", "allow_from_smtp": "Sta enkel de volgende IP-adressen toe voor SMTP", "allow_from_smtp_info": "Laat leeg om alle afzenders toe te staan.
    IPv4/IPv6-adressen en netwerken.", - "allowed_protocols": "Toegestane protocollen", + "allowed_protocols": "Toegestane protocollen voor directe gebruikerstoegang (heeft geen invloed op protocollen voor app-wachtwoorden).", "app_name": "Naam van app", "app_passwd": "Appwachtwoord", "automap": "Probeer mappen automatisch te koppelen (\"Verzonden items\", \"Verzonden\" => \"Verzonden\" etc.)", @@ -605,7 +695,7 @@ "sogo_visible_info": "Wanneer verborgen zal een alias niet worden weergegeven als een selecteerbaar verzendadres. Deze optie beïnvloedt uitsluitend objecten die kunnen worden weergegeven in SOGo (gedeelde of niet-gedeelde aliasadressen die naar minstens één mailbox verwijzen).", "spam_alias": "Maak een nieuw tijdelijk alias aan, of pas deze aan", "spam_filter": "Spamfilter", - "spam_policy": "Voeg items toe of verwijder items van de white- of blacklist", + "spam_policy": "Voeg items toe of verwijder items van de deny- en allowlists", "spam_score": "Stel een aangepaste spamscore in", "subfolder2": "Synchroniseer in submap op bestemming
    (leeg = gebruik geen submappen)", "syncjob": "Wijzig sync job", @@ -624,7 +714,47 @@ "acl": "ACL (Toestemming)", "domain_footer": "Domeinbreede footer", "domain_footer_html": "HTML footer", - "mailbox_relayhost_info": "Wordt alleen toegepast op de mailbox en directe aliassen, maar heft een domein relayhost op." + "mailbox_relayhost_info": "Wordt alleen toegepast op de mailbox en directe aliassen, maar heft een domein relayhost op.", + "custom_attributes": "Aangepaste attributen", + "domain_footer_info_vars": { + "auth_user": "{= auth_user =} - Geauthenticeerde gebruikersnaam opgegeven door een MTA", + "from_user": "{= from_user =} - Afzendergedeelte van het e-mailadres, bijvoorbeeld bij \"moo@mailcow.tld\" wordt \"moo\" teruggegeven", + "from_name": "{= from_name =} - Naam van de afzender, bijvoorbeeld bij \"Mailcow \" wordt \"Mailcow\" teruggegeven", + "from_addr": "{= from_addr =} - Adres van de afzender", + "from_domain": "{= from_domain =} - Domein van de afzender", + "custom": "{= foo =} - Als de mailbox het aangepaste attribuut \"foo\" met de waarde \"bar\" heeft, wordt \"bar\" teruggegeven." + }, + "domain_footer_plain": "PLAIN-voettekst", + "domain_footer_skip_replies": "Voettekst negeren bij antwoord-e-mails", + "footer_exclude": "Uitsluiten van voettekst", + "internal": "Intern", + "internal_info": "Interne aliassen zijn alleen toegankelijk vanuit het eigen (alias)domein.", + "sender_allowed": "Toestaan om als deze alias te verzenden", + "sender_allowed_info": "Als dit is uitgeschakeld, kan deze alias alleen e-mail ontvangen. Gebruik de afzender-ACL om dit te overschrijven en specifieke mailboxen toestemming te geven om te verzenden.", + "lookup_mx": "Bestemming is een reguliere expressie die wordt gematcht met de MX-naam (.*.google.com om alle e-mail die gericht is aan een MX die eindigt op google.com via deze hop te routeren)", + "mailbox_rename": "Hernoem mailbox", + "mailbox_rename_agree": "Ik heb een backup gemaakt.", + "mailbox_rename_warning": "BELANGRIJK! Maak een back-up voordat u de mailbox hernoemt.", + "mailbox_rename_alias": "Alias automatisch aanmaken.", + "mailbox_rename_title": "Nieuwe naam van de lokale mailbox", + "mta_sts": "MTA-STS", + "mta_sts_info": "MTA-STS is een standaard die afdwingt dat e-mailbezorging tussen mailservers TLS met geldige certificaten gebruikt.
    \nDeze wordt gebruikt wanneer DANE niet mogelijk is vanwege ontbrekende of niet-ondersteunde DNSSEC.
    \nOpmerking: Als het ontvangende domein DANE met DNSSEC ondersteunt, heeft DANE altijd de voorkeur – MTA-STS fungeert alleen als een fallback.", + "mta_sts_version": "Versie", + "mta_sts_version_info": "Definieert de versie van de MTA-STS-standaard – momenteel is alleen STSv1 geldig.", + "mta_sts_mode": "Modus", + "mta_sts_mode_info": "Er zijn drie modus om uit te kiezen:
    • testing – het beleid wordt alleen gemonitord, overtredingen hebben geen impact.
    • enforce – het beleid wordt strikt afgedwongen, verbindingen zonder geldige TLS worden geweigerd.
    • none – het beleid wordt gepubliceerd maar niet toegepast.
    ", + "mta_sts_max_age": "Maximale leeftijd", + "mta_sts_max_age_info": "Tijd in seconden dat ontvangende mailservers dit beleid mogen cachen voordat het opnieuw wordt opgehaald.", + "mta_sts_mx": "MX server", + "mta_sts_mx_info": "Staat verzending alleen toe naar expliciet vermelde mailserver-hostnamen; de verzendende MTA controleert of de DNS MX-hostnaam overeenkomt met de beleidslijst en staat levering alleen toe met een geldig TLS-certificaat (beschermt tegen MITM).", + "mta_sts_mx_notice": "Meerdere MX-servers kunnen worden opgegeven (gescheiden door komma’s).", + "none_inherit": "Geen / Erven", + "password_recovery_email": "E-mail voor wachtwoordherstel", + "pushover": "Pushover", + "quota_warning_bcc": "Quotum waarschuwing BCC", + "quota_warning_bcc_info": "Waarschuwingen zullen in afzonderlijke exemplaren naar de volgende ontvangers worden gestuurd. Het onderwerp zal worden gevolgd door de bijbehorende gebruikersnaam tussen haakjes, bijvoorbeeld: Contingentwaarschuwing (user@example.com).", + "sogo_access": "Verleen directe logintoegang tot SOGo", + "sogo_access_info": "Na het inloggen wordt de gebruiker automatisch doorgestuurd naar SOGo" }, "footer": { "cancel": "Annuleren", @@ -637,18 +767,21 @@ "restart_container": "Herstart container", "restart_container_info": "Belangrijk: Een herstart kan enige tijd in beslag nemen, wacht aub totdat dit proces voltooid is.
    Deze pagina zal zichzelf verversen zodra het proces voltooid is.", "restart_now": "Nu herstarten", - "restarting_container": "Container wordt herstart, even geduld aub..." + "restarting_container": "Container wordt herstart, even geduld aub...", + "hibp_check": "Controleer tegen haveibeenpwned.com", + "nothing_selected": "Niets geselecteerd" }, "header": { "administration": "Configuratie & details", "apps": "Apps", - "debug": "Systeeminformatie", + "debug": "Informatie", "email": "E-Mail", "mailcow_config": "Beheer", "quarantine": "Quarantaine", "restart_netfilter": "Herstart netfilter", "restart_sogo": "Herstart SOGo", - "user_settings": "Gebruikersinstellingen" + "user_settings": "Gebruikersinstellingen", + "mailcow_system": "Systeem" }, "info": { "awaiting_tfa_confirmation": "In afwachting van tweefactorauthenticatie...", @@ -662,7 +795,22 @@ "mobileconfig_info": "Log in als mailboxgebruiker om het Apple-verbindingsprofiel te downloaden.", "other_logins": "of aanmelden met", "password": "Wachtwoord", - "username": "Gebruikersnaam" + "username": "Gebruikersnaam", + "back_to_mailcow": "Terug naar mailcow", + "forgot_password": "> Wachtwoord vergeten?", + "invalid_pass_reset_token": "De reset-wachtwoordtoken is ongeldig of verlopen.
    Vraag een nieuwe link voor het resetten van het wachtwoord aan.", + "login_linkstext": "Niet de juiste login?", + "login_usertext": "Log in als gebruiker", + "login_domainadmintext": "Login in als domeinbeheerder", + "login_admintext": "Log in als administrator", + "login_user": "Gebruikerslogin", + "login_dadmin": "Domeinbeheerder login", + "login_admin": "Administrator login", + "new_password": "Nieuw wachtwoord", + "new_password_confirm": "Bevestig nieuw wachtwoord", + "reset_password": "Herstel wachtwoord", + "request_reset_password": "Verzoek wachtwoord wijziging", + "email": "E-mailadres" }, "mailbox": { "action": "Handeling", @@ -782,7 +930,7 @@ "sieve_preset_5": "Autoreply (vakantie)", "sieve_preset_6": "Weiger mail met antwoord", "sieve_preset_7": "Forward en behoud/verwijder", - "sieve_preset_8": "Verwijder mail verstuurd naar een aliasadres van de afzender", + "sieve_preset_8": "E-mail van een specifieke afzender doorsturen, als gelezen markeren en in een submap plaatsen", "sieve_preset_header": "Zie de onderstaande voorbeelden. Raadpleeg Wikipedia voor meer informatie.", "sogo_visible": "Alias tonen in SOGo", "sogo_visible_n": "Verberg alias in SOGo", @@ -809,7 +957,37 @@ "toggle_all": "Selecteer alles", "username": "Gebruikersnaam", "waiting": "Wachten", - "weekly": "Wekelijks" + "weekly": "Wekelijks", + "add_alias_expand": "Alias uitbreiden over alias-domeinen", + "add_template": "Sjabloon toevoegen", + "all_domains": "Alle domeinen", + "catch_all": "Catch-All", + "created_on": "Aangemaakt op", + "domain_templates": "Domein sjablonen", + "domain_quota_total": "Totale domein-opslagruimte", + "goto_ham": "Leren als Ham", + "goto_spam": "Leren als spam", + "iam": "Identiteitsprovider", + "internal": "Intern", + "last_pw_change": "Laatste wachtwoordwijziging", + "mailbox_templates": "Mailbox sjablonen", + "no": "✕", + "open_logs": "Open logs", + "recipient": "Ontvanger", + "relay_unknown": "Onbekende mailboxen relayen", + "sender": "Afzender", + "syncjob_check_log": "Controleer log", + "syncjob_last_run_result": "Laatste uitvoeringsresultaat", + "syncjob_EX_OK": "Succes", + "syncjob_EXIT_CONNECTION_FAILURE": "Verbindingsprobleem", + "syncjob_EXIT_TLS_FAILURE": "Probleem met versleutelde verbinding", + "syncjob_EXIT_AUTHENTICATION_FAILURE": "Authenticatieprobleem", + "syncjob_EXIT_OVERQUOTA": "Doel mailbox is over quota", + "syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Kan geen verbinding maken met de externe server", + "syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Verkeerde gebruikersnaam of wachtwoord", + "templates": "Sjablonen", + "template": "Sjabloon", + "yes": "✓" }, "oauth2": { "access_denied": "Log in als een mailboxgebruiker om toegang via OAuth te verlenen", @@ -842,7 +1020,7 @@ "notified": "Verwittigd", "qhandler_success": "Aanvraag succesvol verzonden naar het systeem. Je kunt nu het venster sluiten.", "qid": "Rspamd QID", - "qinfo": "Het quarantainesysteem slaat een kopie van zowel geweigerde mail (voor de afzender zal het lijken alsof de mail niet afgeleverd is), als mail die afgeleverd is de spamfolder, op in de database.\r\n
    \"Markeer als spam en verwijder\" traint het systeem om soortgelijke mails in de toekomst opnieuw als spam te markeren.\r\n
    Wanneer er meerdere berichten tegelijkertijd worden behandeld kan het mogelijk enige tijd duren.
    Elementen op de blacklist zijn uitgesloten van de quarantaine.", + "qinfo": "Het quarantainesysteem slaat een kopie van zowel geweigerde mail (voor de afzender zal het lijken alsof de mail niet afgeleverd is), als mail die afgeleverd is de spamfolder, op in de database.\n
    \"Markeer als spam en verwijder\" traint het systeem om soortgelijke mails in de toekomst opnieuw als spam te markeren.\n
    Wanneer er meerdere berichten tegelijkertijd worden behandeld kan het mogelijk enige tijd duren.
    Elementen op de denylist zijn uitgesloten van de quarantaine.", "qitem": "Quarantaine-item", "quarantine": "Quarantaine", "quick_actions": "Handelingen", @@ -874,7 +1052,19 @@ "toggle_all": "Selecteer alles" }, "queue": { - "queue_manager": "Queue manager" + "queue_manager": "Queue manager", + "delete": "Verwijder alles", + "flush": "Wachtrij leegmaken", + "info": "De mailwachtrij bevat alle e-mails die wachten op aflevering. Als een e-mail lange tijd in de mailwachtrij blijft staan, wordt deze automatisch door het systeem verwijderd.
    De foutmelding van de betreffende e-mail geeft informatie over waarom de e-mail niet kon worden afgeleverd.", + "legend": "Functies voor acties in de mailwachtrij:", + "ays": "Bevestig dat u alle items uit de huidige wachtrij wilt verwijderen.", + "deliver_mail": "Afleveren", + "deliver_mail_legend": "Probeert de geselecteerde e-mails opnieuw te bezorgen.", + "hold_mail": "Vasthouden", + "hold_mail_legend": "Houdt de geselecteerde e-mails vast. (Voorkomt verdere afleverpogingen)", + "show_message": "Bericht weergeven", + "unban": "Wachtrij deblokkeren", + "unhold_mail": "Vrijgeven" }, "start": { "help": "Toon/verberg hulppaneel", @@ -994,7 +1184,8 @@ "start_fido2_validation": "Start FIDO2-validatie", "fido2_auth": "Aanmelden met FIDO2", "fido2_success": "Apparaat succesvol geregistreerd", - "fido2_validation_failed": "Validatie mislukt" + "fido2_validation_failed": "Validatie mislukt", + "set_fido2_touchid": "Registreer Touch ID op Apple M1" }, "user": { "action": "Handeling", @@ -1161,6 +1352,7 @@ "loadingRecords": "Laden...", "processing": "Wachten alstublieft..", "search": "Zoeken:", - "zeroRecords": "Geen overeenkomsten gevonden" + "zeroRecords": "Geen overeenkomsten gevonden", + "infoFiltered": "(gefilterd uit _MAX_ totale vermeldingen)" } } diff --git a/data/web/lang/lang.pl-pl.json b/data/web/lang/lang.pl-pl.json index 02c892c64..de3557c03 100644 --- a/data/web/lang/lang.pl-pl.json +++ b/data/web/lang/lang.pl-pl.json @@ -1,50 +1,117 @@ { "acl": { - "sogo_profile_reset": "Usuń profil SOGo (webmail)", - "syncjobs": "Polecenie synchronizacji", - "alias_domains": "Dodaj aliasy domen", - "delimiter_action": "Akcja oparta na separatorze" + "sogo_profile_reset": "Zresetuj profil SOGo", + "syncjobs": "Zadania doryczące synchronizacji kont", + "alias_domains": "Dodaj domeny aliasowe", + "delimiter_action": "Akcja oparta na separatorze", + "app_passwds": "Zarządzaj hasłami do aplikacji", + "bcc_maps": "Mapy BCC", + "domain_desc": "Zmień opis dotyczący domeny", + "domain_relayhost": "Zmień serwer przekazujący dla tej domeny", + "eas_reset": "Resetuj urządzenia EAS", + "extend_sender_acl": "Zezwalaj na rozszerzenie listy kontroli dostępu nadawców(ACL) o adresy zewnętrzne", + "filters": "Dostępne filtry", + "login_as": "Zaloguj się jako użytkownik poczty", + "mailbox_relayhost": "Zmień serwer pocztowy(relayhost) dla skrzynki pocztowej", + "prohibited": "Zakazane przez liste kontroli dostępu(ACL)", + "protocol_access": "Zmień dostęp do protokołów", + "pushover": "Pushover(powiadomienia push w czasie rzeczywistym)", + "pw_reset": "Zezwalaj na resetowanie hasła użytkownika mailcow", + "quarantine": "Akcje kwarantanny", + "quarantine_attachments": "Załączniki do kwarantanny", + "quarantine_category": "Zmień kategorię dotyczącą powiadomień o kwarantannie", + "quarantine_notification": "Zmień powiadomienia o kwarantannie", + "ratelimit": "Ograniczenie liczby zapytań", + "recipient_maps": "Mapy odbiorców", + "smtp_ip_access": "Zmiana dozwolonych hostów SMTP", + "sogo_access": "Zezwól na zarządzanie dostępem SOGo", + "spam_alias": "Tymczasowe aliasy", + "spam_policy": "Zablokowane adresy/Dozwolone adresy", + "spam_score": "Wskaźnik spam", + "tls_policy": "Polityka TLS", + "unlimited_quota": "Nieograniczony limit dla skrzynek pocztowych" }, "add": { "active": "Aktywny", "add": "Dodaj", - "alias_address": "Alias/y:", + "alias_address": "Alias/y", "alias_address_info": "Pełny/e adres/y email lub @example.com, aby przejąć wszystkie wiadomości dla domeny (oddzielone przecinkami). tylko domeny mailcow.", "alias_domain": "Alias domeny", "alias_domain_info": "Tylko prawidłowe nazwy domen (oddzielone przecinkami).", - "backup_mx_options": "Opcje Backup MX:", + "backup_mx_options": "Opcje Backup MX", "delete1": "Usuń ze źródła po zakończeniu", "delete2duplicates": "Usuń duplikaty w miejscu docelowym", - "description": "Opis:", + "description": "Opis", "domain": "Domena", - "domain_quota_m": "Łączny limit domeny (MiB):", + "domain_quota_m": "Łączny limit domeny (MiB)", "enc_method": "Metoda szyfrowania", "exclude": "Wyklucz obiekty (regex)", - "full_name": "Pełna nazwa:", + "full_name": "Pełna nazwa", "hostname": "Nazwa hosta", "kind": "Rodzaj", - "mailbox_quota_m": "Maks. wielkość skrzynki (MiB):", - "mailbox_username": "Nazwa użytkownika (lewa strona adresu email):", - "max_aliases": "Maks. liczba aliasów:", - "max_mailboxes": "Maks. liczba skrzynek:", + "mailbox_quota_m": "Maks. wielkość skrzynki (MiB)", + "mailbox_username": "Nazwa użytkownika (lewa strona adresu email)", + "max_aliases": "Maks. liczba aliasów", + "max_mailboxes": "Maks. liczba skrzynek", "mins_interval": "Zakres pobierania (minuty)", "multiple_bookings": "Wielokrotne rejestracje", - "password": "Hasło:", - "password_repeat": "Potwierdź hasło(powtórz):", + "password": "Hasło", + "password_repeat": "Potwierdź hasło(powtórz)", "port": "Port", - "post_domain_add": "Po dodaniu nowej domeny będziesz musiał ponownie uruchomić kontener serwisowy SOGo!", - "quota_mb": "Limit wielkości (MiB):", + "post_domain_add": "Kontener SOGo, \"sogo-mailcow\", musi zostać ponownie uruchomiony po dodaniu nowej domeny!

    Dodatkowo należy przejrzeć konfigurację DNS domeny. Po zatwierdzeniu konfiguracji DNS uruchom ponownie \"acme-mailcow\", aby automatycznie wygenerować certyfikaty dla nowej domeny (autoconfig.<domain>, autodiscover.<domain>).
    Ten krok jest opcjonalny i będzie ponownie wykonywany co 24 godziny.", + "quota_mb": "Limit wielkości (MiB)", "relay_all": "Przekaż wszystkim odbiorcom", - "relay_all_info": "Jeśli decydujesz się nie przekazywać wszystkim odbiorcom, musisz dodać (\"ślepą\")skrzynkę dla każdego poszczególnego odbiorcy, któremu należy przekazać.", + "relay_all_info": "↪Jeśli wybierzesz not, aby przekazać wszystkich odbiorców, musisz dodać (\"ślepą\") skrzynkę pocztową dla każdego pojedynczego odbiorcy, która powinna być przekazywana.", "relay_domain": "Domena przekaźnikowa", "select": "Proszę wybrać...", "select_domain": "Proszę najpierw wybrać domenę", "syncjob": "Dodaj polecenie synchronizacji", "syncjob_hint": "Pamiętaj, że hasła należy zapisywać w zwykłym tekście!", - "target_address": "Adresy Idź do:", + "target_address": "Adresy Idź do", "target_address_info": " Pełny/e adres/y email (oddzielone przecinkami).", - "target_domain": "Domena docelowa:", - "username": "Nazwa użytkownika" + "target_domain": "Domena docelowa", + "username": "Nazwa użytkownika", + "activate_filter_warn": "Wszystkie pozostałe filtry zostaną wyłączone, gdy opcja active zostanie zaznaczona.", + "add_domain_only": "Dodaj wyłącznie domene", + "add_domain_restart": "Dodaj domenę i uruchom ponownie SOGo", + "app_name": "Nazwa aplikacji", + "app_password": "Dodaj hasło do aplikacji", + "app_passwd_protocols": "Dozwolone protokoły dla hasła aplikacji", + "automap": "Spróbuj automatycznie mapować foldery (\"Wysłane elementy\", \"Wysłane\" => \"Wysłane\" itp.)", + "bcc_dest_format": "Miejscem docelowym BCC musi być jeden prawidłowy adres e-mail.
    Jeśli chcesz wysłać kopię do wielu adresów, utwórz alias i użyj go tutaj.", + "comment_info": "Prywatny komentarz nie jest widoczny dla użytkownika, podczas gdy publiczny komentarz jest wyświetlany jako podpowiedź, które pojawia się, gdy użytkownik najedzie myszką nad elementem", + "custom_params": "Niestandardowe parametry", + "custom_params_hint": "Właściwa: --param=xy, błędna: --param xy", + "delete2": "Usuń wiadomości w miejscu docelowym, które nie znajdują się w źródle", + "destination": "Miejsce docelowe", + "disable_login": "Nie pozwalaj na logowanie(poczta przychodząca jest nadal akceptowana)", + "domain_matches_hostname": "Domena pasuje do nazwy hosta", + "dry": "Symulacja synchronizacji", + "gal": "Globalna lista adresów", + "gal_info": "GAL zawiera wszystkie obiekty domeny i nie może być edytowany przez żadnego użytkownika. Wolne/zajęte logi w SOGo bedą nidostępne, jeśli są wyłączone! Uruchom ponownie SOGo, aby zastosować zmiany.", + "generate": "generuj", + "goto_ham": "Ucz się jako ham", + "goto_null": "Odrzucaj pocztę \"po cichu\"", + "goto_spam": "Ucz się jako spam", + "inactive": "Nieaktywny", + "internal": "Wewnętrzny", + "internal_info": "Aliasy wewnętrzne są dostępne tylko z domeny własnej lub domeny aliasów.", + "mailbox_quota_def": "Domyślny przydział skrzynki pocztowej", + "nexthop": "Następny hop", + "private_comment": "Prywatny komentarz", + "public_comment": "Komentarz publiczny", + "relay_transport_info": "
    Info
    Możesz transports maps dla niestandardowego miejsca docelowego dla tej domeny. Jeśli nie jest ustawiony, zostanie aktywowany lookup MX.", + "relay_unknown_only": "Przekaż tylko nieistniejące skrzynki pocztowe. Istniejące skrzynki pocztowe będą dostarczane lokalnie.", + "relayhost_wrapped_tls_info": "Proszę nie używać portów owiniętych TLS (głównie używanych na porcie 465).
    \nUżyj dowolnego portu niezawiniętego i uruchom STARTTLS. Polityka TLS w celu egzekwowania TLS może być utworzona w \"mapie zasad TLS\".", + "sieve_desc": "Krótki opis", + "sieve_type": "Typ filtra", + "skipcrossduplicates": "Pomiń duplikaty wiadomości w folderach (na zasadzie kolejności zgłoszeń)", + "subscribeall": "Subskrybuj wszystkie foldery", + "tags": "Tagi", + "timeout1": "Limit czasu połączenia z hostem zdalnym", + "timeout2": "Limit czasu połączenia dla lokalnego hosta", + "validate": "Zatwierdź", + "validation_success": "Zatwierdzone z powodzeniem" }, "admin": { "access": "Dostęp", @@ -72,7 +139,7 @@ "f2b_max_attempts": "Max. ilość prób", "f2b_parameters": "Parametry Fail2ban", "f2b_retry_window": "Spróbuj ponownie (s) dla max. ilości prób", - "f2b_whitelist": "Biała lista sieci/hosty", + "f2b_whitelist": "Dozwolone sieci/hosty", "filter_table": "Tabela filtru", "forwarding_hosts": "Hosty przekazujące", "forwarding_hosts_add_hint": "Możesz albo wyszczególnić adresy IPv4/IPv6, sieci w notacji CIDR, nazwy hostów (które zostaną rozłożone na adresy IP), albo nazwy domen (które zostaną rozłożone na adresy IP poprzez sprawdzanie rekordów SPF lub, w razie ich braku, rekordów MX).", @@ -100,30 +167,274 @@ "spamfilter": "Filtr spamu", "time": "Czas", "unchanged_if_empty": "W przypadku braku zmian, nie wypełniaj", - "username": "Nazwa użytkownika" + "username": "Nazwa użytkownika", + "activate_api": "Aktywuj API", + "activate_send": "Aktywuj przycisk wysyłania", + "active_rspamd_settings_map": "Aktywne ustawienia mapy", + "add_admin": "Dodaj administratora", + "add_relayhost": "Dodaj transport zależny od nadawcy", + "add_relayhost_hint": "Miej na uwadze, że dane uwierzytelniające, jeśli takie istnieją, będą przechowywane w postaci zwykłego tekstu.", + "add_row": "Dodaj wiersz", + "add_settings_rule": "Dodaj regułę ustawień", + "add_transport": "Dodaj transport", + "add_transports_hint": "Należy pamiętać, że dane uwierzytelniające, jeśli takie istnieją, będą przechowywane jako zwykły tekst.", + "additional_rows": " Dodano kolejne rzędy", + "admins": "Administratorzy", + "admins_ldap": "Administratorzy LDAP", + "admin_quicklink": "Ukryj szybki link do strony logowania administratora", + "advanced_settings": "Ustawienia zaawansowane", + "allowed_methods": "Dostęp-kontrola-zezwolenie-metody", + "allowed_origins": "Dostęp-Kontrola-Zezwolenia-Pochodzenie", + "api_allow_from": "Zezwalaj na dostęp API z tych notacji sieciowych IP/CIDR", + "api_info": "API jest w trakcie prac. Dokumentację można znaleźć pod adresem /api", + "api_key": "klucz API", + "api_read_only": "Dostęp tylko do odczytu", + "api_read_write": "Dostęp do odczytu i zapisu", + "api_skip_ip_check": "Pomiń sprawdzenie IP dla API", + "app_hide": "Ukryj dla logowania", + "app_links": "Linki aplikacji", + "app_name": "Nazwa aplikacji", + "apps_name": "Nazwa \"aplikacji mailcow\"", + "arrival_time": "Czas serwera", + "authed_user": "Autoryzowany użytkownik", + "ays": "Jesteś pewien, że chcesz kontynuować?", + "ban_list_info": "Zobacz listę zakazanych adresów IP poniżej: network (pozostały czas zakazu) - [działania].
    IP kolejkowane do odbanowania zostaną usunięte z listy aktywnych zakazów w ciągu kilku sekund.
    Czerwone etykiety wskazują aktywny stałe zakazy poprzez odmowę wpisu.", + "change_logo": "Zmień logo", + "logo_normal_label": "Normalna", + "logo_dark_label": "Odwrócony dla trybu ciemnego", + "convert_html_to_text": "Konwertuj HTML na zwykły tekst", + "copy_to_clipboard": "Tekst skopiowany do schowka!", + "cors_settings": "Ustawienia CORS", + "credentials_transport_warning": "Ostrzeżenie: Dodanie nowego wpisu mapy transportu spowoduje aktualizację danych uwierzytelniających dla wszystkich wpisów z pasującą kolumną hop.", + "customer_id": "Identyfikator klienta", + "customize": "Dostosuj", + "login_page": "Strona logowania", + "destination": "Miejsce docelowe", + "dkim_domains_selector": "Selektor", + "dkim_domains_wo_keys": "Wybierz domeny z brakującymi kluczami", + "dkim_from": "Od", + "dkim_from_title": "Domena źródłowa do kopiowania danych z", + "dkim_overwrite_key": "Nadpisanie istniejącego klucza DKIM", + "dkim_to": "Do", + "dkim_to_title": "Domeny docelowe - zostaną nadpisane", + "domain_admin": "Administrator domeny", + "domainadmin_quicklink": "Ukryj szybki link do strony logowania administratora domeny", + "domain_s": "Domena/y", + "duplicate": "Duplikat", + "duplicate_dkim": "Duplikat rekordu DKIM", + "excludes": "Wyklucza tych odbiorców", + "f2b_ban_time_increment": "Czas zakazu jest zwiększany z każdym zakazem", + "f2b_blacklist": "Lista odrzuconych sieci/hostów", + "f2b_filter": "Filtry Regex", + "f2b_list_info": "Odrzucony host lub sieć zawsze będzie przewyższać jednostkę zezwalającą.Zastosowanie aktualizacji listy zajmie kilka sekund.", + "f2b_manage_external": "Zarządzaj Fail2Ban zewnętrznie", + "f2b_manage_external_info": "Fail2ban nadal będzie utrzymywać listę banów, ale nie będzie aktywnie ustalać zasad blokowania ruchu. Użyj wygenerowane listy banów poniżej, aby zewnętrznie zablokować ruch.", + "f2b_max_ban_time": "Max. czas bana (s)", + "f2b_netban_ipv4": "Rozmiar podsieci IPv4 do zastosowania zakazu (8-32)", + "f2b_netban_ipv6": "Rozmiar podsieci IPv6 do zastosowania zakazu (8-128)", + "f2b_regex_info": "Logi brane pod uwagę: SOGo, Postfix, Dovecot, PHP-FPM.", + "filter": "Filtr", + "force_sso_text": "Jeśli skonfigurowany jest zewnętrzny dostawca OIDC, ta opcja ukrywa domyślne formularze logowania mailcow i pokazuje tylko przycisk logowania pojedynczego", + "force_sso": "Wyłącz logowanie mailcow i pokaż tylko pojedyncze logowanie", + "from": "Od", + "generate": "Generuj", + "guid": "GUID - unikalny identyfikator instancji", + "guid_and_license": "GUID & licencja", + "hash_remove_info": "Usunięcie hasha z limitem współczynnika (jeśli nadal istnieje) spowoduje całkowite zresetowanie jego licznika.
    Każdy hash jest oznaczony indywidualnym kolorem.", + "help_text": "Zastąp tekst pomocy poniżej maski logowania (dozwolone HTML)", + "html": "HTML", + "iam": "Dostawca tożsamości", + "iam_attribute_field": "Pole atrybutów", + "iam_authorize_url": "Punkt końcowy autoryzacji", + "iam_auth_flow": "Przepływ uwierzytelniania", + "iam_auth_flow_info": "Oprócz przepływu kodu autoryzacyjnego (Standardowy przepływ w Keycloak), który służy do logowania jednokrotnego logowania, mailcow obsługuje również przepływ uwierzytelniania z bezpośrednimi poświadczeniami. Mailpassword Flow próbuje zweryfikować dane uwierzytelniające użytkownika za pomocą Keycloak Admin REST API. mailcow pobiera hashowane hasło z atrybutu mailcow_password, który jest mapowany w Keycloak.", + "iam_basedn": "Baza DN", + "iam_client_id": "ID klienta", + "iam_client_secret": "Sekret klienta", + "iam_client_scopes": "Zakresy klientów", + "iam_default_template": "Domyślny szablon", + "iam_default_template_description": "Jeśli użytkownikowi nie zostanie przypisany żaden szablon, domyślny szablon zostanie użyty do utworzenia skrzynki pocztowej, ale nie do aktualizacji skrzynki pocztowej.", + "iam_description": "Skonfiguruj zewnętrznego dostawce uwierzytelniania
    Skrzynki pocztowe użytkownika zostaną automatycznie utworzone przy pierwszym logowaniu, pod warunkiem, że zostało ustawione mapowanie atrybutów.", + "iam_extra_permission": "Aby następujące ustawienia działały, klient mailcow w Keycloak potrzebuje konta serwisowego oraz uprawnień do podgląd-użytkowników.", + "iam_host": "Host", + "iam_host_info": "Wprowadź jeden lub więcej hostów LDAP, oddzielonych przecinkami.", + "iam_import_users": "Zaimportuj użytkowników", + "iam_login_provisioning": "Automatyczne tworzenie użytkowników przy logowaniu", + "iam_mapping": "Mapowanie atrybutów", + "iam_bindpass": "Powiąż hasło", + "iam_periodic_full_sync": "Okresowa pełna synchronizacja", + "iam_port": "Port", + "iam_realm": "Realm", + "iam_redirect_url": "Przekierowanie Url", + "iam_rest_flow": "Procedura uwierzytelniania hasła pocztowego", + "iam_server_url": "Adres URL serwera", + "iam_sso": "Pojedyncze logowanie", + "iam_sync_interval": "Interwał synchronizacji / importu (min)", + "iam_test_connection": "Test połączenia", + "iam_token_url": "Tokenowy punkt końcowy", + "iam_userinfo_url": "Endpoint informacji o użytkowniku", + "iam_username_field": "Pole nazwy użytkownika", + "iam_binddn": "Powiąź DN", + "iam_use_ssl": "Używaj SSL", + "iam_use_ssl_info": "Jeśli włączysz SSL, a port zostanie ustawiony na 389, zostanie automatycznie nadpisany, aby użyć 636.", + "iam_use_tls": "Używaj StartTLS", + "iam_use_tls_info": "Jeśli włączono TLS, należy użyć domyślnego portu dla serwera LDAP (389). Nie można używać portów SSL.", + "iam_version": "Wersja", + "ignore_ssl_error": "Ignoruj błędy SSL", + "in_use_by": "W użyciu przez", + "include_exclude": "Uwzględnij/Nie uwzględniaj", + "include_exclude_info": "Domyślnie - bez zaznaczenia - wszystkie skrzynki pocztowe są adresowane", + "includes": "Uwzględnij tych odbiorców", + "ip_check": "Sprawdź IP", + "ip_check_disabled": "Sprawdzenie IP jest wyłączone. Możesz go włączyć w obszarze
    System > Konfiguracja > Opcje > Dostosuj", + "ip_check_opt_in": "Opt-In korzystania z usług stron trzecich ipv4.mailcow.email i ipv6.mailcow.email w celu rozwiązania zewnętrznych adresów IP.", + "is_mx_based": "Bazuje na MX", + "last_applied": "ostatnio zastosowany", + "license_info": "Licencja nie jest wymagana, ale pomaga w dalszym rozwoju.
    Zarejestruj swój GUID tutaj lub kup wsparcie dla instalacji mailcow.", + "link": "Link", + "login_time": "Cześć logowania", + "logo_info": "Twój obraz zostanie przeskalowany do wysokości 40px dla górnego paska nawigacyjnego i max. szerokości 250px dla strony startowej. Zalecana jest skalowalna grafika.", + "recipients": "Odbiorcy", + "send": "Wyślij", + "sender": "Nadawca", + "lookup_mx": "Destynacja jest regularnym wyrażeniem, które pasuje do nazwy MX (.*\\\\.google\\.com, aby przekierować całą pocztę skierowaną do MX kończącego się na google.com w tym hopie)", + "main_name": "Nazwa \"mailcow UI\"", + "merged_vars_hint": "Wyszarzone wiersze zostały połączone z vars. (local.) inc.php i nie można ich modyfikować.", + "message_size": "Rozmiar wiadomości", + "nexthop": "Następny hop", + "needs_restart": "potrzebny restart", + "no": "✕", + "no_active_bans": "Brak aktywnych banów", + "no_new_rows": "Brak dostępnych kolejnych wierszy", + "oauth2_apps": "Aplikacje OAuth2", + "oauth2_add_client": "Dodaj klienta OAuth2", + "oauth2_client_id": "ID klienta", + "oauth2_client_secret": "Sekret klienta", + "oauth2_info": "Implementacja OAuth2 obsługuje typ grantu \"Kod autoryzacji\" i wydaje tokeny odświeżania.
    \nSerwer automatycznie wydaje również nowe tokeny odświeżania, po użyciu tokena odświeżania.

    \n• Domyślnym zakresem jest profil. Tylko użytkownicy skrzynek pocztowych mogą być uwierzytelniani w OAuth2. Jeśli parametr zakresu zostanie pominięty, wraca do profil.
    \n\nParametr state musi zostać wysłany przez klienta w ramach żądania autoryzacji.

    >\nŚcieżki zapytań do API OAuth2:
    \n
      \nPunkt końcowy autoryzacji\n/oauth/authorize
    • Token endpoint: /oauth/token
    • Resource page: /oauth/profile
    • \n
    \nRegeneracja tajemnicy klienta nie spowoduje wygaśnięcia istniejących kodów autoryzacyjnych, ale nie odnowi ich tokena.

    \nOdwołanie tokenów klienta spowoduje natychmiastowe zakończenie wszystkich aktywnych sesji. Wszyscy klienci muszą się ponownie uwierzytelnić.", + "oauth2_redirect_uri": "Przekieruj URI", + "oauth2_renew_secret": "Wygeneruj sekret nowego klienta", + "oauth2_revoke_tokens": "Unieważnij wszystkie tokeny klienta", + "optional": "Opcjonalny", + "options": "Opcje", + "password_length": "Długość hasła", + "password_policy": "Polityka haseł", + "password_policy_chars": "Musi zawierać co najmniej jeden znak alfabetyczny", + "password_policy_length": "Minimalna długość hasła to %d", + "password_policy_lowerupper": "Musi zawierać małe i duże litery", + "password_policy_numbers": "Musi zawierać co najmniej jeden numer", + "password_policy_special_chars": "Musi zawierać znaki specjalne", + "password_reset_info": "Jeśli nie jest udostępniony e-mail zapasowy, ta funkcja nie może być używana.", + "password_reset_settings": "Ustawienia odzyskiwania haseł", + "password_reset_tmpl_html": "Szablon HTML", + "password_reset_tmpl_text": "Szablon tekstu", + "password_settings": "Ustawienia hasła", + "quarantine_bcc": "Wyślij kopię wszystkich powiadomień (BCC) do tego odbiorcy:
    Pozostaw pustą aby wyłączyć. Niepodpisana, niezaznaczona poczta. Powinna być dostarczana tylko wewnętrznie.", + "quarantine_exclude_domains": "Wyklucz domeny i domeny aliasowe", + "quarantine_max_age": "Maksymalny wiek w dniach
    Wartość musi być równa lub większa niż 1 dzień.", + "quarantine_max_score": "Odrzuć powiadomienie, jeśli wskaźnik spamu wiadomości jest wyższy niż ta wartość:
    Domyślne do 9999.0", + "quarantine_max_size": "Maksymalny rozmiar w MiB (większe elementy są odrzucane):
    0 oznacza, że nie oznacza nieograniczony.", + "quarantine_notification_html": "Szablon wiadomości e-mail z powiadomieniem:
    Pozostaw puste, aby przywrócić szablon domyślny.", + "quarantine_notification_sender": "Powiadomienie nadawcy wiadomości e-mail", + "quarantine_notification_subject": "Powiadomienie E-mail Temat", + "quarantine_redirect": "Przekieruj wszystkie powiadomienia do tego odbiorcy:
    Pozostaw pusty aby wyłączyć. Niepodpisana, niezaznaczona poczta. Powinny być dostarczane tylko wewnętrznie.", + "quarantine_release_format": "Format zwolnionych przedmiotów", + "quarantine_release_format_att": "Jako załącznik", + "quarantine_release_format_raw": "Niezmodyfikowany oryginał", + "quarantine_retention_size": "Retencje na skrzynkę pocztową:
    0 oznacza nieaktywne.", + "quicklink_text": "Pokaż lub ukryj szybkie linki do innych stron logowania pod formularzem logowania", + "quota_notification_html": "Szablon wiadomości e-mail z powiadomieniem:
    Pozostaw puste, aby przywrócić szablon domyślny.", + "quota_notification_sender": "Powiadomienie nadawcy wiadomości e-mail", + "quota_notification_subject": "Temat powiadomienia e-mail", + "quota_notifications": "Powiadomienia o limitach", + "quota_notifications_info": "Powiadomienia o limktach są wysyłane do użytkowników raz przy przekroczaniu 80% i raz przy przekraczaniu 95% zużycia.", + "quota_notifications_vars": "{{percent}} równa się aktualnemu limitowi użytkownika
    {{username}} jest nazwą skrzynki pocztowej", + "queue_unban": "Zdejmij bana", + "rate_name": "Nazwa oceny", + "regen_api_key": "Regeneruj klucz API", + "regex_maps": "Mapy Regex", + "relay_from": "\"Od:\" adres", + "relay_rcpt": "\"Do:\"Adres", + "relay_run": "Uruchom test", + "relayhosts": "Transporty zależne od nadawcy", + "relayhosts_hint": "Zdefiniuj transporty zależne od nadawcy, aby móc wybrać je w oknie dialogowym konfiguracji domeny.
    \nUsługa transportu jest zawsze \"smtp:\" i dlatego uruchomj TLS, gdy będzie oferowana. Owinięty TLS (SMTPS) nie jest obsługiwany. Pod uwagę brane jest indywidualne ustawienie polityki wychodzącej TLS użytkownika.
    \nDotyczy wybranych domen, w tym domen aliasowych.", + "remove_row": "Usuń wiersz", + "reset_default": "Zresetuj do ustawień domyślnych", + "reset_limit": "Usuń hash", + "reset_password_vars": "{{link}} Wygenerowany link do resetu hasła
    {{username}} Nazwa skrzynki pocztowej użytkownika, który poprosił o zresetowanie hasła
    {{username2}} Nazwa skrzynki pocztowej do odzyskiwania
    {{date}} Data złożenia żądania resetowania hasła
    {{token_lifetime}}Żywotność tokena w minutach
    {{hostname}} Nazwa hosta mailcow", + "restore_template": "Pozostaw puste, aby przywrócić szablon domyślny.", + "routing": "Routowanie", + "rsetting_add_rule": "Dodaj regułę", + "rsetting_content": "Zawartość reguły", + "rsetting_desc": "Krótki opis", + "rsetting_no_selection": "Proszę zaznaczyć regułę", + "rsetting_none": "Brak dostępnych reguł", + "rsettings_insert_preset": "Wstaw przykładowy preset", + "rsettings_preset_1": "Wyłącz wszystkie z wyjątkiem DKIM i limitu stawek dla uwierzytelnionych użytkowników", + "rsettings_preset_2": "Administratorzy poczty pozwalają na spam", + "rsettings_preset_3": "Zezwalaj tylko określonym nadawcom na skrzynkę pocztową (tj. używaj tylko jako wewnętrznej skrzynki pocztowej)", + "rsettings_preset_4": "Wyłącz Rspamd dla domeny", + "rspamd_com_settings": "Nazwa ustawienia zostanie automatycznie wygenerowana, zobacz przykładowe ustawienia wstępne poniżej. Aby uzyskać więcej informacji, zobacz Rspamd docs", + "rspamd_global_filters": "Globalne mapy filtrów", + "rspamd_global_filters_agree": "Będę ostrożny!", + "rspamd_global_filters_info": "Globalne mapy filtrów zawierają różne rodzaje globalnych list dozwolonych i zablokowanych adresów.", + "rspamd_global_filters_regex": "Ich imiona wyjaśniają ich cel. Cała zawartość musi zawierać ważne wyrażenie regularne w formacie \"/pattern/options\" (e.g. /.+@domain\\.tld/i).
    \nChociaż podstawowe kontrole są wykonywane na każdej linii regex, funkcja Rspamd może zostać złamana, jeśli nie odczyta poprawnie składni.
    >\nRspamd po zmianie spróbuje odczytać zawartość mapy. Jeśli wystąpią problemy, restart Rspamd, aby wymusić ponowne ładowanie mapy.
    Elementy z listy odrzuconych są wyłączone z kwarantanny.", + "rspamd_settings_map": "Mapa ustawień Rspamd", + "sal_level": "Poziom Moo", + "service": "Usługa", + "service_id": "Id usługi", + "subject": "Temat", + "success": "Sukces", + "sys_mails": "Maile systemowe", + "task": "Zadanie", + "text": "Tekst", + "title": "Tytuł", + "title_name": "Tytuł strony internetowej \"mailcow UI\"", + "to_top": "Powrót na górę", + "transport_dest_format": "Regex lub syntax: example.org, .example.org, *, box@example.org(wiele wartości można rozdzielić przecinkami)", + "transport_maps": "Mapy transportu", + "transport_test_rcpt_info": "• Użyj null@hosted.mailcow.de aby przetestować przekazywanie do nieznanego miejsca docelowego.", + "transports_hint": "• Wpis mapy transportowej overrules mapa transportowa zależna od nadawcy.
    \nNajlepiej stosować transporty oparte na MX •.
    \nUstawienia zasad Outbound TLS dotyczące użytkownika są ignorowane i mogą być egzekwowane tylko przez wpisy mapy zasad TLS\n• Usługa transportu dla zdefiniowanych transportów jest zawsze \"smtp:\" i dlatego uruchomi TLS, gdy będzie oferowana. Wrapped TLS (SMTPS) nie jest obsługiwany.
    \nAdresy pasujące do \"/localhost$/\" będą zawsze transportowane przez \"lokalne:\", dlatego miejsce docelowe \"*\" nie będzie miało zastosowania do tych adresów.
    \n• Aby określić poświadczenia dla przykładowego następnego hopa \"[host]:25\", Postfix zawsze kolejkuje zapytania o \"host\" przed wyszukaniem \"[host]:25\". Takie zachowanie uniemożliwia jednoczesne użycie \"host\" i \"[host]:25.", + "ui_footer": "Stopka (dozwolone HTML)", + "ui_header_announcement": "Ogłoszenia", + "ui_header_announcement_active": "Ustaw ogłoszenie jako aktywne", + "ui_header_announcement_content": "Tekst (dozwolony HTML)", + "ui_header_announcement_help": "Ogłoszenie jest widoczne dla wszystkich zalogowanych użytkowników oraz na ekranie logowania interfejsu użytkownika.", + "ui_header_announcement_select": "Wybierz typ ogłoszenia", + "ui_header_announcement_type": "Typ", + "ui_header_announcement_type_danger": "Bardzo ważne", + "ui_header_announcement_type_info": "Info", + "ui_header_announcement_type_warning": "Ważne", + "ui_texts": "Etykiety i teksty UI", + "unban_pending": "oczekuje na odblokowanie", + "upload": "Prześlij", + "user_link": "Link użytkownika", + "user_quicklink": "Ukryj Quicklink do strony logowania użytkownika", + "validate_license_now": "Sprawdź identyfikator GUID względem serwera licencji", + "verify": "Zweryfikuj", + "yes": "✓" }, "danger": { "access_denied": "Odmowa dostępu lub nieprawidłowe dane w formularzu", "alias_domain_invalid": "Alias domeny nieprawidłowy", "alias_empty": "Alias nie może być pusty", "alias_goto_identical": "Alias i Idź do nie mogą być identyczne", - "alias_invalid": "Alias nieprawidłowy", + "alias_invalid": "Adres aliasu jest nieprawidłowy", "aliasd_targetd_identical": "Alias domeny nie może być identyczny z domeną docelową", "aliases_in_use": "Maks. liczba aliasów musi być większa od lub równa %d", "description_invalid": "Nieprawidłowy opis źródła", "dkim_domain_or_sel_invalid": "Nieprawidłowa domena lub selektor DKIM", - "domain_exists": "Domena %s już istnieje", - "domain_invalid": "Błędna nazwa domeny", + "domain_exists": "Domena już istnieje", + "domain_invalid": "Nazwa domeny jest pusta lub nieprawidłowa", "domain_not_empty": "Nie można usunąć niepustej domeny", - "domain_not_found": "Nie znaleziono domeny %s", - "domain_quota_m_in_use": "Limit domeny %s MiB", - "goto_empty": "Adres Idź do nie może być pusty", + "domain_not_found": "Nie znaleziono domeny", + "domain_quota_m_in_use": "Limit domeny musi być większy lub równy do MiB", + "goto_empty": "Adres alias musi zawierać co najmniej jeden prawidłowy adres goto", "goto_invalid": "Adres Idź do jest nieprawidłowy", - "is_alias": "%s został już podany jako alias", - "is_alias_or_mailbox": "%s podano wcześniej jako alias lub skrzynkę", - "is_spam_alias": "%s podano wcześniej jako alias dla spam", - "last_key": "Nie można usunąć ostatniego klucza", - "login_failed": "Niepowodzenie logowania", + "is_alias": "został już podany jako alias", + "is_alias_or_mailbox": "jest już znany jako alias, skrzynka pocztowa lub adres aliasu rozwinięty z domeny aliasu.", + "is_spam_alias": "jest już znany jako tymczasowy adres aliasu (adres aliasu antyspamowego)", + "last_key": "Nie można usunąć ostatniego klucza, zamiast tego należy wyłączyć TFA.", + "login_failed": "Logowanie nie powiodło się", "mailbox_invalid": "Nieprawidłowa nazwa skrzynki", "mailbox_quota_exceeded": "Wielkość przekracza limit domeny (maks. %d MiB)", "mailbox_quota_exceeds_domain_quota": "Maksymalna wielkość przekracza limit domeny", @@ -145,8 +456,107 @@ "sender_acl_invalid": "ACL Nadawcy jest nieprawidłowy", "target_domain_invalid": "Domena Idź do jest nieprawidłowa", "targetd_not_found": "Nie znaleziono domeny docelowej", - "username_invalid": "Nie można użyć nazwy użytkownika", - "validity_missing": "Proszę wyznaczyć termin ważności" + "username_invalid": "Nie można użyć tej nazwy użytkownika", + "validity_missing": "Proszę wyznaczyć termin ważności", + "to_invalid": "Pole odbiorca nie może być puste", + "app_name_empty": "Nazwa aplikacji nie może być pusta", + "app_passwd_id_invalid": "Identyfikator hasła aplikacji jest niepoprawny", + "authsource_in_use": "Nie można zmienić ani usunąć dostawcy tożsamości, ponieważ jest używany przez co najmniej jednego użytkownika.", + "bcc_empty": "Miejsce docelowe BCC nie może być puste", + "bcc_exists": "Mapa BCC istnieje już dla tego typu", + "bcc_must_be_email": "Miejsce docelowe BCC nie jest prawidłowym adresem e-mail", + "comment_too_long": "Komentarz zbyt długi, maksymalnie 160 dozwolonych znaków", + "cors_invalid_method": "Podano nieprawidłową metodę Allow-Method", + "cors_invalid_origin": "Podano nieprawidłową wartość Allow-Origin", + "defquota_empty": "Domyślny limit skrzynki pocztowej nie może wynosić 0.", + "demo_mode_enabled": "Tryb demo jest włączony", + "dkim_domain_or_sel_exists": "Klucz DKIM już istnieje i nie zostanie nadpisany", + "domain_cannot_match_hostname": "Domena nie może być taka sama jak nazwa hosta", + "extended_sender_acl_denied": "brak ACL do ustawiania adresów nadawcy zewnętrznego", + "extra_acl_invalid": "Adres nadawcy zewnętrznego jest nieprawidłowy", + "extra_acl_invalid_domain": "Nadawca zewnętrzny używa nieprawidłowej domeny", + "fido2_verification_failed": "Weryfikacja FIDO2 nie powiodła się", + "file_open_error": "Nie można otworzyć pliku do zapisu", + "filter_type": "Niewłaściwy typ filtra", + "from_invalid": "Nadawca nie może być pusty", + "generic_server_error": "Wystąpił nieoczekiwany błąd serwera. Skontaktuj się z administratorem.", + "global_filter_write_error": "Nie można zapisać pliku filtra", + "global_map_invalid": "Id mapy globalnej nieprawidłowe", + "global_map_write_error": "Nie można zapisać globalnej mapy ID", + "ham_learn_error": "Błąd uczenia ham", + "iam_test_connection": "Połączenie nie powiodło się", + "imagick_exception": "Błąd: wyjątek Imagick podczas odczytu obrazu", + "img_dimensions_exceeded": "Obraz przekracza maksymalny rozmiar obrazu", + "img_invalid": "Nie można zweryfikować pliku obrazu", + "img_size_exceeded": "Obraz przekracza maksymalny rozmiar pliku", + "img_tmp_missing": "Nie można zweryfikować pliku obrazu: Nie znaleziono pliku tymczasowego", + "invalid_bcc_map_type": "Nieprawidłowy typ mapy BCC", + "invalid_destination": "Format docelowy jest nieprawidłowy", + "invalid_filter_type": "Nieprawidłowy typ filtra", + "invalid_host": "Nieprawidłowy określony host", + "invalid_mime_type": "Niepoprawny typ mime", + "invalid_nexthop": "Następny format hop jest nieprawidłowy", + "invalid_nexthop_authenticated": "Następny hop już istnieje z innymi danymi logowania, zaktualizuj najpierw istniejące dane uwierzytelniające dla tego hopa.", + "invalid_recipient_map_new": "Określony nieprawidłowy nowy odbiorca", + "invalid_recipient_map_old": "Określony nieprawidłowy pierwotny odbiorca", + "invalid_reset_token": "Nieprawidłowy token resetu", + "ip_list_empty": "Lista dozwolonych adresów IP nie może być pusta", + "mailbox_defquota_exceeds_mailbox_maxquota": "Domyślny limit skrzynki pocztowej przekracza maksymalny dozwolony limit", + "malformed_username": "Nieprawidłowy format nazwy użytkownika", + "map_content_empty": "Zawartość mapy nie może być pusta", + "max_age_invalid": "Nieprawidłowa wartość maksymalnego wieku: %s", + "mode_invalid": "Tryb %s jest nieprawidłowy", + "mx_invalid": "Rekord MX %s jest nieprawidłowy", + "mysql_error": "Błąd MySQL: %s", + "network_host_invalid": "Nieprawidłowa sieć lub host: %s", + "next_hop_interferes": "%s powoduje konflikt z nexthopem %s", + "next_hop_interferes_any": "Istniejący next hop koliduje z %s.”", + "nginx_reload_failed": "Nie udało się przeładować Nginx: %s", + "no_user_defined": "Brak zdefiniowanego użytkownika", + "password_reset_invalid_user": "Skrzynka pocztowa nie została znaleziona lub nie ustawiono adresu e-mail do odzyskiwania", + "password_reset_na": "Odzyskiwanie hasła jest obecnie niedostępne. Skontaktuj się ze swoim administratorem.", + "private_key_error": "Błąd klucza prywatnego: %s", + "pushover_credentials_missing": "Brak tokena i/lub klucza Pushover", + "pushover_key": "Klucz Pushover ma niewłaściwy format", + "pushover_token": "Token Pushover ma zły format", + "recipient_map_entry_exists": "Istnieje wpis mapy odbiorcy \"%s", + "recovery_email_failed": "Nie można wysłać e-maila odzyskiwania. Skontaktuj się ze swoim administratorem.", + "redis_error": "Błąd Redis: %s", + "relayhost_invalid": "Wpis mapy %s jest niepoprawny", + "release_send_failed": "Nie udało się zwolnić wiadomości: %s", + "required_data_missing": "Brakuje wymaganych danych %s", + "reset_f2b_regex": "Filtr Regex nie mógł zostać zresetowany na czas, spróbuj ponownie lub poczekaj jeszcze kilka sekund i przeładuj stronę.", + "reset_token_limit_exceeded": "Limit tokenów Reset został przekroczony. Spróbuj ponownie później.", + "rl_timeframe": "Nieprawidłowo ustawiony przedział czasu limitu", + "rspamd_ui_pw_length": "Hasło do Rspamd UI powinno mieć co najmniej 6 znaków długości", + "script_empty": "Skrypt nie może być pusty", + "set_acl_failed": "Nie udało się ustawić ACL", + "settings_map_invalid": "Ustawienia id mapy %s są nieprawidłowe", + "sieve_error": "Błąd podczas analizy skryptu Sieve: %s", + "spam_learn_error": "Błąd uczenia spamu: %s", + "subject_empty": "Temat nie może być pusty", + "targetd_relay_domain": "Domena docelowa %s jest skonfigurowana jako domena przekazująca relay", + "template_exists": "Szablon %s już istnieje", + "template_id_invalid": "Identyfikator szablonu %s niepoprawny", + "template_name_invalid": "Nazwa szablonu niepoprawna", + "temp_error": "Tymczasowy błąd", + "text_empty": "Pole tekstowe nie może być puste", + "tfa_token_invalid": "Niepoprawny token TFA", + "tls_policy_map_dest_invalid": "Podano błędne lub nieobsługiwane miejsce docelowe dla tej polityki", + "tls_policy_map_entry_exists": "Istnieje mapa polityki TLS \"%s", + "tls_policy_map_parameter_invalid": "Parametr polityki jest nieprawidłowy", + "totp_verification_failed": "Weryfikacja TOTP nie powiodła się", + "transport_dest_exists": "Miejsce docelowe transportu „%s” już istnieje", + "webauthn_verification_failed": "Weryfikacja WebAuthn nie powiodła się: %s", + "webauthn_authenticator_failed": "Wybrany autoryzator nie został odnaleziony", + "webauthn_publickey_failed": "Nie przechowywano klucza publicznego dla wybranego uwierzytelniacza", + "webauthn_username_failed": "Wybrany autoryzator należy do innego konta", + "unknown": "Wystąpił nieznany błąd", + "unknown_tfa_method": "Nieznana metodą TFA", + "unlimited_quota_acl": "Ustawienie nieograniczonego limitu przestrzeni jest zabronione przez reguły ACL", + "value_missing": "Proszę o podanie wszystkich wartości", + "version_invalid": "Wersja %s jest niepoprawna", + "yotp_verification_failed": "Weryfikacja OTP Yubico nie powiodła się: %s" }, "edit": { "active": "Aktywny", @@ -179,7 +589,7 @@ "previous": "Poprzednia strona", "quota_mb": "Limit wielkośći (MiB)", "relay_all": "Przekaż wszystkim odbiorcom", - "relay_all_info": "Jeśli decydujesz się nie przekazywać wszystkim odbiorcom, musisz dodać (\"ślepą\")skrzynkę dla każdego poszczególnego odbiorcy, któremu należy przekazać.", + "relay_all_info": "↪ Jeśli zdecydujesz się nie przekazywać wszystkim odbiorcom, musisz dodać (\"ślepą\")skrzynkę dla każdego poszczególnego odbiorcy, któremu należy przekazać.", "relay_domain": "Domena przekaźnikowa", "remove": "Usuń", "resource": "Zasób", @@ -191,15 +601,127 @@ "target_domain": "Domena docelowa", "title": "Edytuj obiekt", "unchanged_if_empty": "Jeżli bez zmian, nie wypełniaj", - "username": "Nazwa użytkownika" + "username": "Nazwa użytkownika", + "delete_ays": "Proszę o potwierdzenie procesu usuwania.", + "acl": "ACL (Pozwolenie)", + "admin": "Edytyj administratora", + "advanced_settings": "Ustawienia zaawansowane", + "allow_from_smtp": "Zezwalaj tylko tym adresom IP na używanie SMTP", + "allow_from_smtp_info": "Pozostaw puste, aby zezwolić na wszystkich nadawców.
    adresy IPv4/IPv6 i sieci.", + "allowed_protocols": "Dozwolone protokoły dla bezpośredniego dostępu użytkownika (nie wpływa na protokoły haseł aplikacji)", + "app_name": "Nazwa aplikacji", + "app_passwd": "Hasło do aplikacji", + "app_passwd_protocols": "Dozwolone protokoły dla hasła aplikacji", + "automap": "Spróbuj automatycznie mapować foldery (np. „Sent items”, „Sent” ⇒ „Sent” itp.)", + "bcc_dest_format": "Miejscem docelowym BCC musi być jeden prawidłowy adres e-mail.
    Jeśli chcesz wysłać kopię na wiele adresów, utwórz alias i użyj go tutaj.", + "client_id": "Id klienta", + "client_secret": "Tajny klucz klienta(sekret)", + "comment_info": "Komentarz prywatny nie jest widoczny dla użytkownika, natomiast komentarz publiczny jest wyświetlany jako podpowiedź po najechaniu kursorem w widoku użytkownika", + "created_on": "Stworzony na", + "custom_attributes": "Niestandardowe atrybuty", + "delete2": "Usuń wiadomości na koncie docelowym, które nie występują na koncie źródłowym", + "disable_login": "Zablokuj logowanie (przychodząca poczta nadal będzie przyjmowana)", + "domain_footer": "Stopka dla całej domeny", + "domain_footer_html": "Stopka HTML", + "domain_footer_info": "Stopki dla całej domeny są dodawane do wszystkich wychodzących wiadomości e-mail powiązanych z adresami w tej domenie.
    W stopce można użyć następujących zmiennych:", + "domain_footer_info_vars": { + "auth_user": "{= auth_user =} - Uwierzytelniona nazwa użytkownika określona przez MTA", + "from_user": "{= from_user =} – część użytkownika (local-part) adresu nadawcy; np. dla „moo@mailcow.tld\n” zwróci „moo”", + "from_name": "{= from_name =} – nazwa nadawcy (From name) z nagłówka wiadomości; np. dla \"Mailcow <moo@mailcow.tld>\" zwróci \"Mailcow\"", + "from_addr": "{= from_addr =} – pełny adres nadawcy (z części envelope)", + "from_domain": "{= from_domain =} – część domenowa adresu nadawcy (z envelope)", + "custom": "{= foo =} - jeśli skrzynka pocztowa ma niestandardowy atrybut „foo” o wartości „bar”, zwraca „bar”" + }, + "domain_footer_plain": "Stopka w formacie tekstowym(PLAIN)", + "domain_footer_skip_replies": "Nie dodawaj stopki do odpowiedzi na e-maile", + "extended_sender_acl": "Adresy nadawców zewnętrznych", + "extended_sender_acl_info": "Klucz DKIM dla domeny powinien zostać zaimportowany, jeśli jest dostępny.\nPamiętaj, aby dodać ten serwer do odpowiadającego rekordu SPF typu TXT.\nKiedy domena lub domena aliasu zostanie dodana do tego serwera i pokrywa się z zewnętrznym adresem, zewnętrzny adres zostanie usunięty.", + "force_pw_update": "Wymuszenie aktualizacji hasła przy następnym logowaniu", + "force_pw_update_info": "Ten użytkownik będzie mógł logować się wyłącznie tutaj %s. Hasła aplikacyjne pozostają aktywne.", + "footer_exclude": "Wyklucz ze stopki", + "gal": "Globalna lista adresowa", + "gal_info": "GAL zawiera wszystkie obiekty domeny i nie może być edytowana przez żadnego użytkownika. Informacje o dostępności (free/busy) w SOGo są niedostępne, jeśli funkcja jest wyłączona!Uruchom ponownie SOGo, aby zastosować zmiany.", + "generate": "generuj", + "grant_types": "Rodzaje grantów(Typy przyznawania dostępu)", + "internal": "Wewnętrzny", + "internal_info": "Aliasów wewnętrznych można używać tylko w obrębie własnej domeny lub domen aliasów.", + "last_modified": "Ostatnio modyfikowany", + "lookup_mx": "Destination to wyrażenie regularne dopasowujące nazwę serwera MX (np. .*.google.com — aby kierować całą pocztę wysyłaną do MX kończących się na google.com przez ten hop)", + "pushover_title": "Tytuł powiadomienia", + "pushover_sound": "Dźwięk powiadomienia", + "pushover_vars": "Gdy nie zdefiniowano żadnego filtra nadawcy, brane będą pod uwagę wszystkie wiadomości. Filtry oparte na wyrażeniach regularnych (regex) oraz dokładne dopasowania nadawców można definiować indywidualnie — będą one przetwarzane kolejno, niezależnie od siebie. Dostępne zmienne do użycia w treści i tytule (należy pamiętać o zasadach ochrony danych osobowych).", + "pushover_verify": "Zweryfikuj dane logowania", + "quota_warning_bcc": "Ukryta kopia ostrzeżenia o przekroczeniu limitu", + "quota_warning_bcc_info": "Ostrzeżenia będą wysyłane jako osobne kopie do poniższych odbiorców. Temat wiadomości zostanie rozszerzony o nazwę użytkownika w nawiasach, na przykład: Ostrzeżenie o limicie (user@example.com\n).", + "ratelimit": "Limit wysyłania", + "redirect_uri": "Adres przekierowania / adres zwrotny (Redirect/Callback URL)", + "relay_transport_info": "
    Informacja
    **Możesz zdefiniować mapy transportu (transport maps) dla niestandardowego miejsca docelowego tej domeny.** Jeśli nie zostanie to ustawione, zostanie wykonane wyszukiwanie", + "relay_unknown_only": "Przekazuj (relay) tylko nieistniejące skrzynki pocztowe. Istniejące skrzynki będą dostarczane lokalnie.", + "relayhost": "Transporty przypisane do nadawcy", + "scope": "Zakres", + "sender_acl_disabled": "Sprawdzanie nadawcy jest wyłączone", + "sender_acl_info": "Jeżeli użytkownik skrzynki pocztowej A ma pozwolenie na wysyłkę jako użytkownik skrzynki B, adres nadawcy nie jest automatycznie wyświetlany jako opcja w polu „Od” w SOGo.
    \nUżytkownik skrzynki B musi utworzyć delegację w SOGo, aby użytkownik A mógł wybrać ich adres jako nadawcę. Aby zdelegować skrzynkę w SOGo, użyj menu (trzy kropki) po prawej stronie nazwy skrzynki w lewym górnym rogu, będąc w widoku poczty. To zachowanie nie dotyczy adresów aliasów.", + "sieve_desc": "Krótki opis", + "sieve_type": "Typ filtra", + "skipcrossduplicates": "Pomijaj duplikaty wiadomości w różnych folderach (pierwsza napotkana wiadomość zostaje zachowana).", + "sogo_access": "Bezpośrednie przekazywanie do SOGo", + "sogo_access_info": "Po zalogowaniu użytkownik jest automatycznie przekierowywany do SOGo.", + "sogo_visible": "Alias jest widoczny w SOGO", + "sogo_visible_info": "Ta opcja dotyczy tylko obiektów, które mogą być wyświetlane w SOGo (aliasy współdzielone lub nie współdzielone, wskazujące przynajmniej na jedną lokalną skrzynkę).\nJeśli obiekt zostanie ukryty, alias nie będzie dostępny jako wybieralny nadawca w SOGo.", + "spam_alias": "Tworzenie lub zmiana ograniczonych czasowo adresów aliasów", + "spam_filter": "Filtr spam", + "spam_policy": "Dodaj lub usuń elementy z listy dozwolonych / zablokowanych", + "spam_score": "Ustaw własny poziom punktacji spamu", + "timeout1": "Limit czasu połączenia z serwerem zdalnym", + "timeout2": "Limit czasu połączenia z serwerem lokalnym", + "validate_save": "Zatwierdź i zapisz", + "pushover_info": "Ustawienia powiadomień push będą miały zastosowanie do wszystkich czystych (niespamowych) wiadomości dostarczanych do %s w tym aliasów (współdzielonych, niewspółdzielonych, oznaczonych).", + "mailbox_quota_def": "Domyślny limit skrzynki pocztowej", + "mailbox_relayhost_info": "Dotyczy wyłącznie skrzynki pocztowej i bezpośrednich aliasów, nadpisuje ustawienie serwera pośredniczącego (relayhost) dla domeny.", + "maxbytespersecond": "Max. Ilość bajtów na sekundę
    (0 = unlimited)", + "mailbox_rename": "Zmień nazwę skrzynki pocztowej", + "mailbox_rename_agree": "Stworzyłem kopię zapasową.", + "mailbox_rename_warning": "WAŻNE! Utwórz kopię zapasową przed zmianą nazwy skrzynki pocztowej.", + "mailbox_rename_alias": "Tworzenie aliasów automatycznie", + "mailbox_rename_title": "Nowa nazwa lokalnej skrzynki pocztowej", + "mbox_rl_info": "Ten limit szybkości dotyczy nazwy logowania SASL i odpowiada dowolnemu adresowi „from” używanemu przez zalogowanego użytkownika. Limit szybkości dla skrzynki pocztowej nadpisuje limit szybkości dla całej domeny.", + "nexthop": "Następny hop", + "private_comment": "Prywatny komentarz", + "public_comment": "Komentarz publiczny", + "mta_sts": "Konfiguruj MTA-STS", + "mta_sts_info": "MTA-STS to standard wymuszający dostarczanie poczty elektronicznej pomiędzy serwerami pocztowymi z użyciem TLS oraz ważnych certyfikatów.\n\nJest stosowany wtedy, gdy użycie DANE nie jest możliwe z powodu braku lub nieobsługiwanego DNSSEC.\n\n
    Uwaga: Jeżeli domena odbiorcza obsługuje DANE z DNSSEC, DANE jest zawsze preferowane — MTA-STS działa wyłącznie jako mechanizm zapasowy.", + "mta_sts_version": "Wersja.", + "mta_sts_version_info": "Określa wersję standardu MTA-STS — obecnie jedyną prawidłową wartością jest STSv1..", + "mta_sts_mode": "Tryb.", + "mta_sts_mode_info": "Dostępne są trzy tryby do wyboru:\n
    • testing – polityka jest wyłącznie monitorowana, a naruszenia nie mają wpływu na dostarczanie poczty.
    • enforce – polityka jest ściśle egzekwowana; połączenia bez ważnego TLS są odrzucane.
    • none – polityka jest publikowana, lecz nie jest stosowana.
    .", + "mta_sts_max_age": "Maksymalny czas obowiązywania.", + "mta_sts_max_age_info": "Czas (w sekundach) przechowywania polityki w cache przez serwery odbierające..", + "mta_sts_mx": "serwer MX.", + "mta_sts_mx_info": "Umożliwia wysyłanie poczty wyłącznie do jawnie wymienionych nazw hostów serwerów pocztowych; wysyłający MTA sprawdza, czy nazwa hosta MX w DNS odpowiada liście z polityki, i zezwala na dostarczenie tylko przy użyciu ważnego certyfikatu TLS (ochrona przed atakami MITM)..", + "mta_sts_mx_notice": "Dopuszcza się podanie wielu serwerów MX, rozdzielonych przecinkami..", + "none_inherit": "Brak /Dziedzicz", + "password_recovery_email": "Email do odzyskiwania hasła", + "pushover": "Pushover", + "pushover_evaluate_x_prio": "Eskaluj wiadomości o wysokim priorytecie [X-Priority: 1]", + "pushover_only_x_prio": "Uwzględniaj wyłącznie wiadomości o wysokim priorytecie [X-Priority: 1]", + "pushover_sender_array": "Uwzględniaj wyłącznie następujące adresy e-mail nadawców (oddzielone przecinkami)", + "pushover_sender_regex": "Bierz pod uwagę następujący regex nadawcy", + "pushover_text": "Tekst powiadomienia" }, "footer": { "cancel": "Anuluj", "confirm_delete": "Potwierdź usunięcie", "delete_now": "Usuń teraz", - "delete_these_items": "Czy jesteś pewien, że chcesz usunąć następujące elementy?", + "delete_these_items": "Proszę potwierdzić zmiany w poniższym identyfikatorze obiektu", "loading": "Proszę czekać...", - "restart_now": "Uruchom ponownie teraz" + "restart_now": "Uruchom ponownie teraz", + "hibp_check": "Sprawdź w stosunku do haveibeenpwned.com", + "hibp_nok": "Dopasowano! To potencjalnie niebezpieczne hasło!", + "hibp_ok": "Nie znaleziono żadnego dopasowania.", + "nothing_selected": "Nic wybranego", + "restart_container": "Zresetuj kontener", + "restart_container_info": "Ważne: Łagodne ponowne uruchomienie może zająć trochę czasu — proszę poczekać na jego zakończenie.", + "restarting_container": "Ponowne uruchomienie kontenera, może to zająć trochę czasu" }, "header": { "administration": "Administrowanie", @@ -207,16 +729,40 @@ "mailcow_config": "Konfiguracja", "quarantine": "Kwarantanna", "restart_sogo": "Uruchom ponownie SOGo", - "user_settings": "Ustawienia użytkownika" + "user_settings": "Ustawienia użytkownika", + "apps": "Aplikacje", + "debug": "Informacja", + "mailcow_system": "System", + "restart_netfilter": "Uruchom ponownie netfilter" }, "info": { - "no_action": "Żadne działanie nie ma zastosowania" + "no_action": "Żadne działanie nie ma zastosowania", + "awaiting_tfa_confirmation": "Oczekiwanie na potwierdzenie TFA", + "session_expires": "Twoja sesja wygaśnie za około 15 sekund" }, "login": { "delayed": "Logowanie zostało opóźnione o %s sekund.", "login": "Zaloguj się", "password": "Hasło", - "username": "Nazwa użytkownika" + "username": "Nazwa użytkownika", + "forgot_password": "Zapomniałeś hasła?", + "login_linkstext": "Login nieprawidłowy?", + "login_usertext": "Zaloguj się jako użytkownik", + "login_domainadmintext": "Zaloguj się jako administrator domeny", + "login_admintext": "Zaloguj się jako admin", + "other_logins": "lub zaloguj za pomocą", + "email": "Adres e-mail", + "back_to_mailcow": "Wróć do mailcow", + "fido2_webauthn": "Logowanie FIDO2/WebAuthn", + "invalid_pass_reset_token": "Token resetowania hasła jest nieprawidłowy lub wygasł.
    Proszę poprosić o nowy link do resetowania hasła.", + "login_user": "Logowanie użytkownika", + "login_dadmin": "Logowanie administratora domeny", + "login_admin": "Login Administratora", + "mobileconfig_info": "Zaloguj się jako użytkownik skrzynki pocztowej, aby pobrać żądany profil połączenia Apple.", + "new_password": "Nowe hasło", + "new_password_confirm": "Potwierdź nowe hasło", + "reset_password": "Zresetuj hasło", + "request_reset_password": "Poproś o zmianę hasła" }, "mailbox": { "action": "Działanie", @@ -276,7 +822,55 @@ "tls_enforce_out": "Uruchom TLS wychodzące", "toggle_all": "Zaznacz wszystkie", "username": "Nazwa użytkownika", - "weekly": "Co tydzień" + "weekly": "Co tydzień", + "add_alias_expand": "Rozszerz alias na domeny alias", + "add_bcc_entry": "Dodaj mapę BCC", + "add_filter": "Dodaj filtr", + "recipient_map_old_info": "Mapa odbiorcy (pierwotne miejsce docelowe) musi być prawidłowym adresem e-mail lub nazwą domeny.", + "recipient_maps": "Mapy odbiorców", + "relay_unknown": "Przekazuj nieznane skrzynki pocztowe", + "running": "Uruchomione", + "sender": "Nadawca", + "set_postfilter": "Oznacz jako postfilter", + "set_prefilter": "Oznacz jako prefilter", + "sieve_info": "Możesz przechowywać wiele filtrów dla każdego użytkownika, jednak w danym momencie aktywny może być tylko jeden prefilter i jeden postfilter
    \nKażdy filtr będzie przetwarzany w opisanej kolejności. Ani błędny skrypt, ani polecenie „keep;” nie zatrzymają przetwarzania kolejnych skryptów.\nZmiany w globalnych skryptach Sieve spowodują ponowne uruchomienie usługi Dovecot.

    \nGlobalny prefiltr Sieve • Prefilter • Skrypty użytkownika • Postfilter • Globalny postfilter Sieve", + "sieve_preset_1": "Odrzuć pocztę z prawdopodobnymi niebezpiecznymi typami plików", + "sieve_preset_2": "Zawsze zaznaczaj e-mail konkretnego nadawcy jako odczytane", + "sieve_preset_3": "Odrzuć po cichu, zatrzymaj cały proces sieve", + "sieve_preset_4": "Plik do INBOX, pomiń dalszy proces przez filtry sieve", + "sieve_preset_5": "Automatyczna odpowiedź (urlopowa)", + "sieve_preset_6": "Odrzuć pocztę z odpowiedzią", + "sieve_preset_7": "Przekierowanie wiadomości z możliwością zachowania lub usunięcia kopii", + "sieve_preset_8": "Przekieruj wiadomość e-mail od określonego nadawcy, oznacz jako odczytaną i posortuj do podfoldera", + "sieve_preset_header": "Zbacz przykładowe ustawienia poniżej. Więcej szczegółów znajdziesz w Wikipedii.", + "sogo_visible": "Alias jest widoczny w SOGo", + "sogo_visible_n": "Ukryj alias w SOGo", + "sogo_visible_y": "Pokaż alias w SOGo", + "stats": "Statystyki", + "status": "Status", + "syncjob_check_log": "Sprawdź log", + "syncjob_last_run_result": "Wynik ostatniego uruchomienia", + "syncjob_EX_OK": "Sukces", + "syncjob_EXIT_CONNECTION_FAILURE": "Problem z połączeniem", + "syncjob_EXIT_TLS_FAILURE": "Problem z szyfrowanym połączeniem", + "syncjob_EXIT_AUTHENTICATION_FAILURE": "Problem uwierzytelniania", + "syncjob_EXIT_OVERQUOTA": "Docelowa skrzynka pocztowa przekroczyła limit pojemności", + "syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nie można połączyć się ze zdalnym serwerem", + "syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Niewłaściwa nazwa użytkownika lub hasło", + "table_size": "Rozmiar tabeli", + "table_size_show_n": "Pokaż %s elementy", + "templates": "Szablony", + "template": "Szablon", + "tls_map_dest": "Miejsce docelowe", + "tls_map_dest_info": "Przykłady: example.org, .example.org, [mail.example.org]:25", + "tls_map_parameters": "Parametry", + "add_recipient_map_entry": "Dodaj mapę odbiorców", + "add_template": "Dodaj szablon", + "add_tls_policy_map": "Dodaj mapę polityk TLS", + "address_rewriting": "Przepisywanie adresów", + "alias_domain_alias_hint": "Aliasy nie są automatycznie stosowane do aliasów domen. Adres aliasu my-alias@domain nie obejmuje adresu my-alias@alias-domain (gdzie „alias-domain” jest przykładową domeną aliasową dla „domain”).\n
    Aby przekierować pocztę do zewnętrznej skrzynki, użyj filtra Sieve (zob. kartę „Filtry” lub SOGo → Przekazywanie). Skorzystaj z opcji „Rozszerz alias na domeny aliasowe”, aby automatycznie dodać brakujące aliasy.", + "alias_domain_backupmx": "Domena aliasowa nieaktywna dla domeny przekaźnikowej", + "all_domains": "Wszystkie domeny" }, "quarantine": { "action": "Działanie", @@ -284,10 +878,40 @@ "quarantine": "Kwarantanna", "quick_actions": "Szybkie działania", "remove": "Usuń", - "toggle_all": "Zaznacz wszystkie" + "toggle_all": "Zaznacz wszystkie", + "confirm_delete": "Potwierdź usunięcie tego elementu.", + "learn_spam_delete": "Zapamiętaj jako spam i usuwaj w przyszłości", + "quick_delete_link": "Otwórz szybki link do usuwania", + "refresh": "Odśwież", + "rejected": "Odrzucony", + "release": "Zwolnij", + "release_body": "Dołączyliśmy Twoją wiadomość jako plik eml do tej wiadomości.", + "release_subject": "Potencjalnie szkodliwa pozycja kwarantanny %s", + "show_item": "Pokaż element", + "spam": "Spam", + "spam_score": "Wskaźnik", + "subj": "Temat", + "table_size": "Rozmiar tabeli", + "table_size_show_n": "Pokaż %s elementy", + "text_from_html_content": "Zawartość (przekonwertowany HTML)", + "text_plain_content": "Zawartość (tekst zwykły)", + "type": "Typ" }, "queue": { - "queue_manager": "Queue Manager" + "queue_manager": "Menedżer kolejki", + "delete": "Usuń wszystko", + "ays": "Potwierdź, że chcesz usunąć wszystkie elementy z bieżącej kolejki.", + "flush": "Opróżnij kolejkę wiadomości", + "info": "Kolejka poczty zawiera wszystkie wiadomości e-mail oczekujące na dostarczenie.\nJeśli wiadomość e-mail pozostaje w kolejce przez dłuższy czas, system automatycznie ją usuwa.
    \nKomunikat błędu dla danej wiadomości zawiera informacje o przyczynie, dla której nie mogła zostać dostarczona.", + "legend": "Funkcje zarządzania kolejką poczty:", + "deliver_mail": "Dostarcz", + "deliver_mail_legend": "Próby ponownego dostarczenia wybranych wiadomości.", + "hold_mail": "Wstrzymane", + "hold_mail_legend": "Wstrzymuję wybrane maile. (Zapobiega dalszym próbom dostarczenia)", + "show_message": "Pokaż wiadomość", + "unban": "Usuń zablokowanie w kolejce", + "unhold_mail": "Zwolnij wiadomość", + "unhold_mail_legend": "Zwalnia wybrane wiadomości do dostarczenia (wymaga wcześniejszego wstrzymania)." }, "start": { "help": "Pokaż/Ukryj panel pomocy", @@ -298,7 +922,7 @@ "alias_added": "Alias/y został/y dodany/e", "alias_domain_removed": "Usunięto alias domeny %s", "alias_modified": "Zapisano zmiany w aliasie/ach %s", - "alias_removed": "Usunięto alias %s ", + "alias_removed": "Usunięto alias %s", "aliasd_added": "Dodano alias domeny %s", "aliasd_modified": "Zapisano zmiany w aliasie domeny %s", "dkim_added": "Klucz DKIM został zapisany", @@ -313,24 +937,85 @@ "f2b_modified": "Zmiany w Fail2ban zostały zapisane", "forwarding_host_added": "Dodano hosta przekazującego %s", "forwarding_host_removed": "Usunięto hosta przekazującego %s", - "item_deleted": "", - "items_deleted": "Item %s successfully deleted", + "item_deleted": "Element %s skutecznie usunięte", + "items_deleted": "Elementy %s skutecznie usunięte", "mailbox_added": "Dodano skrzynkę %s", "mailbox_modified": "Zapisano zmiany w skrzynce %s", "mailbox_removed": "Usunięto skrzynkę %s", "object_modified": "Zapisano zmiany w obiekcie %s", "resource_added": "Dodano śródło %s", "resource_modified": "Zapisano zmiany w skrzynce %s", - "resource_removed": "Usunięto zasób %s" + "resource_removed": "Usunięto zasób %s", + "template_removed": "Szablon o identyfikatorze %s został usunięty.", + "tls_policy_map_entry_deleted": "Mapa polityki TLS o identyfikatorze %s została usunięta", + "tls_policy_map_entry_saved": "Wpis mapy polityki TLS \"%s\" został zapisany", + "ui_texts": "Zapisane zmiany w tekstach UI", + "upload_success": "Plik przesłany pomyślnie", + "verified_fido2_login": "Zweryfikowany login FIDO2", + "verified_totp_login": "Zweryfikowany login TOTP", + "verified_webauthn_login": "Zweryfikowany login WebAuthn", + "verified_yotp_login": "Zweryfikowany login Yubico OTP", + "acl_saved": "ACL dla obiektu %s zapisany", + "admin_added": "Administrator %s został dodany", + "admin_api_modified": "Zmiany w API zostały zapisane", + "admin_removed": "Administrator %s został usunięty", + "app_links": "Zapisane zmiany w linkach aplikacji", + "app_passwd_added": "Dodano nowe hasło aplikacji", + "app_passwd_removed": "Usunięto ID hasła aplikacji %s", + "bcc_deleted": "Wpisy map BCC usunięte: %s", + "bcc_edited": "Wpis mapy BCC %s edytowany", + "bcc_saved": "Wpis mapy BCC zapisany", + "cors_headers_edited": "Ustawienia CORS zostały zapisane", + "custom_login_modified": "Dostosowanie logowania zostało pomyślnie zapisane", + "db_init_complete": "Inicjalizacja bazy danych zakończona", + "delete_filter": "Filtry %s ID usunięte", + "delete_filters": "Usunięte filtry: %s", + "deleted_syncjob": "Usunięte syncjob ID %s", + "deleted_syncjobs": "Usunięte syncjobs: %s”.", + "domain_add_dkim_available": "Klucz DKIM już istniał", + "dkim_duplicated": "Klucz DKIM dla domeny %s został skopiowany do %s", + "domain_footer_modified": "Zmiany w stopce domeny %s zostały zapisane", + "dovecot_restart_success": "Dovecot został pomyślnie zrestartowany", + "f2b_banlist_refreshed": "ID listy banów został pomyślnie odświeżony.", + "global_filter_written": "Filtr został pomyślnie zapisany do pliku", + "hash_deleted": "Hash usunięty", + "iam_test_connection": "Połączenie powiodło się", + "ip_check_opt_in_modified": "Sprawdzenie adresu IP zostało pomyślnie zapisane", + "item_released": "Pozycja %s zwolniona", + "items_released": "Wybrane elementy zostały zwolnione", + "learned_ham": "Pomyślnie nauczono ID %s jako ham", + "license_modified": "Zmiany w licencji zostały zapisane", + "logged_in_as": "Zalogowany jako %s", + "mailbox_renamed": "Nazwa skrzynki pocztowej została zmieniona z %s na %s", + "nginx_reloaded": "Nginx został przeładowany", + "password_policy_saved": "Polityka haseł została pomyślnie zapisana", + "password_changed_success": "Hasło zostało pomyślnie zmienione", + "pushover_settings_edited": "Ustawienia Pushover pomyślnie ustawione, proszę zweryfikować dane uwierzytelniające.", + "qlearn_spam": "Identyfikator wiadomości %s został nauczony jako spam i usunięty", + "queue_command_success": "Polecenie kolejki zostało pomyślnie wykonane", + "recipient_map_entry_deleted": "Id mapy odbiorcy %s został usunięty", + "recipient_map_entry_saved": "Wpis mapy odbiorcy \"%s\" został zapisany", + "recovery_email_sent": "E-mail do odzyskiwania wysłany do %s", + "relayhost_added": "Wpis mapy %s został dodany", + "relayhost_removed": "Wpis mapy %s został usunięty", + "reset_main_logo": "Reset do domyślnego logo", + "rl_saved": "Limit szybkości dla obiektu %s został zapisany", + "rspamd_ui_pw_set": "Hasło do Rspamd UI pomyślnie ustawione", + "saved_settings": "Zapisane ustawienia", + "settings_map_added": "Dodano ustawienia (wpis mapy)", + "settings_map_removed": "Usunięte ustawienia mapy ID %s", + "sogo_profile_reset": "Profil SOGo dla użytkownika/ów został zresetowany", + "template_added": "Dodano szablon", + "template_modified": "Zmiany w szablonie/ach zostały zapisane" }, "tfa": { - "api_register": "%s używa Yubico Cloud API. Proszę pobrać klucz API dla Twojego klucza here", + "api_register": "%s używa Yubico Cloud API. Proszę pobrać klucz API dla Twojego klucza tutaj", "confirm": "Potwierdź", "confirm_totp_token": "Potwierdź zmiany przez wprowadzenie wygenerowanego tokenu", "delete_tfa": "Wyłącz TFA", "disable_tfa": "Wyłącz TFA do kolejnego udanego logowania", "enter_qr_code": "Twój kod TOTP, jeśli Twoje urządzenie nie skanuje kodów QR.", - "key_id": "Identyfikator dla Twojego YubiKey", + "key_id": "Identyfikator dla twojego urządzenia", "key_id_totp": "Identyfikator dla Twojego klucza", "none": "Deaktywuj", "scan_qr_code": "Zeskanuj następujący kod aplikacją uwierzytelniającą lub wprowadź kod ręcznie.", @@ -340,8 +1025,16 @@ "totp": "Time-based OTP (Google Authenticator itd.)", "webauthn": "Uwierzytelnianie WebAuthn", "waiting_usb_auth": "Czekam na urządzenie USB...

    Wciśnij teraz przycisk na urządzeniu WebAuthn USB.", - "waiting_usb_register": " Czekam na urządzenie USB...

    Wprowadź swoje hasło powyżej i potwierdź rejestrację WebAuthn przez naciśnięcie przycisku na urządzeniu WebAuthn USB.", - "yubi_otp": "Uwierzytelnianie Yubico OTP" + "waiting_usb_register": " Czekam na urządzenie USB...

    Wprowadź swoje hasło powyżej i potwierdź rejestrację przez naciśnięcie przycisku na urządzeniu USB.", + "yubi_otp": "Uwierzytelnianie Yubico OTP", + "authenticators": "Uwierzytelniacze", + "error_code": "Kod błędu", + "init_webauthn": "Inicjalizacja, proszę czekać...", + "reload_retry": "- (przeładuj przeglądarkę, jeśli błąd nadal występuje)", + "start_webauthn_validation": "Rozpocznij walidację", + "tfa_token_invalid": "Token TFA nieprawidłowy", + "u2f_deprecated": "Wygląda na to, że Twój klucz został zarejestrowany przy użyciu przestarzałej metody U2F. Dezaktywujemy dla Ciebie uwierzytelnianie dwuskładnikowe i usuniemy Twój klucz.", + "u2f_deprecated_important": "Zarejestruj swój klucz w panelu administracyjnym za pomocą nowej metody WebAuthn." }, "user": { "action": "Działanie", @@ -371,7 +1064,7 @@ "edit": "Edytuj", "encryption": "Szyfrowanie", "excludes": "Wyłączenia", - "force_pw_update": "Musisz zmienić hasło, aby używać webmaila.", + "force_pw_update": "Musisz ustawić nowe hasło, aby mieć dostęp do usług groupware.", "hour": "Godzina", "hourly": "Co godzinę", "hours": "Godziny", @@ -379,7 +1072,7 @@ "interval": "Zakres", "is_catch_all": "Funkcja catch-all dla domen/y", "last_run": "Ostatnie uruchomienie", - "mailbox_details": " Szczegóły skrzynki", + "mailbox_details": "Szczegóły", "messages": "wiadomości", "never": "Nigdy", "new_password": "Nowe hasło", @@ -391,17 +1084,17 @@ "remove": "Usuń", "save_changes": "Zapisz zmiany", "shared_aliases": "Aliasy współdzielone", - "shared_aliases_desc": "Na aliasy współdzielone nie wpływają filtry spamu i ustawienia TLS.", - "show_sieve_filters": "Twój filtr sieve", - "sogo_profile_reset": "Usuń profil SOGo (webmail)", - "sogo_profile_reset_help": "To usunie ustawienia SOGo bezpowrotnie.", - "sogo_profile_reset_now": "Usuń profil teraz", - "spam_aliases": "Tymczasowy alias email", + "shared_aliases_desc": "Współdzielone aliasy nie są objęte ustawieniami specyficznymi dla użytkownika, takimi jak filtr antyspamowy czy polityka szyfrowania.\nOdpowiadające im filtry antyspamowe mogą być tworzone wyłącznie przez administratora — jako polityki obowiązujące dla całej domeny.", + "show_sieve_filters": "Pokaż filtr sieve aktywnego użytkownika", + "sogo_profile_reset": "Zresetuj profil SOGo", + "sogo_profile_reset_help": "Spowoduje to usunięcie profilu użytkownika SOGo oraz bezpowrotne usunięcie wszystkich danych kontaktów i kalendarza.", + "sogo_profile_reset_now": "Zresetuj profil teraz", + "spam_aliases": "Tymczasowe aliasy email", "spamfilter": "Filtr spamu", - "spamfilter_behavior": "Rating", + "spamfilter_behavior": "Ocena", "spamfilter_bl": "Czarna lista", - "spamfilter_bl_desc": "Adresy email z czarnej listy zawsze klasyfikuj jako spam i odrzucaj. Można użyć wildcards.", - "spamfilter_default_score": "Wartości domyślne:", + "spamfilter_bl_desc": "Adresy e-mail znajdujące się na liście zablokowanych (denylist) są zawszeklasyfikowane jako spam i odrzucane.\nOdrzucone wiadomości nie sąkopiowane do kwarantanny.\nMożna używać symboli wieloznacznych (wildcardów).\nFiltr jest stosowany wyłącznie do bezpośrednich aliasów (aliasów kierujących do jednej skrzynki pocztowej), z wyłączeniem aliasów typu „catch-all” oraz samej skrzynki.", + "spamfilter_default_score": "Wartości domyślne", "spamfilter_green": "Zielony: ta wiadomość nie jest spamem", "spamfilter_hint": "Pierwsza wartość oznacza \"niską punktację spam\", druga wartość oznacza \"wysoką punktację spam\".", "spamfilter_red": "Czerwony: ta wiadomość jest spamem i zostanie odrzucona przez serwer", @@ -412,13 +1105,13 @@ "spamfilter_table_remove": "Usuń", "spamfilter_table_rule": "Zasada", "spamfilter_wl": "Biała lista", - "spamfilter_wl_desc": "Adresy email z białej listy nigdy nie klasyfikuj jako spam. Można użyć wildcards.", + "spamfilter_wl_desc": "Adresy e-mail znajdujące się na liście dozwolonych (allowlist) są zaprogramowane tak, aby nigdy nie były klasyfikowane jako spam. Można używać symboli wieloznacznych (wildcardów).Filtr jest stosowany wyłącznie do bezpośrednich aliasów (aliasów wskazujących na jedną skrzynkę pocztową), z wyłączeniem aliasów typu „catch-all” oraz samej skrzynki pocztowej", "spamfilter_yellow": "Żółty: ta wiadomość może być spamem, zostanie oznaczona jako spam i przeniesiona do folderu spam", - "sync_jobs": "Polecenie synchronizacji", + "sync_jobs": "Zadania synchronizacji", "tag_handling": "Ustaw obsługę znaczników pocztowych", "tag_help_example": "Przykład adresu email z etykietą: ja+Facebook@example.org", "tag_help_explain": "W podfolderze: tworzy nowy podfolder z nazwą taką jak etykieta, który zostanie umieszczony pod Skrzynką odbiorczą (\"Skrzynka odbiorcza/Facebook\").
    \r\nW temacie: nazwy etykiet zostaną dodane na początku tematów wiadomości, np.: \"[Facebook] Moje wiadomości\".", - "tag_in_none": "Nic nie robić", + "tag_in_none": "Nie wykonuj żadnej akcji", "tag_in_subfolder": "W podfolderze", "tag_in_subject": "W temacie", "tls_enforce_in": "Uruchom TLS przychodzące", @@ -429,6 +1122,199 @@ "username": "Nazwa użytkownika", "week": "Tydzień", "weekly": "Co tydzień", - "weeks": "Tygodnie" + "weeks": "Tygodnie", + "q_add_header": "Spam", + "advanced_settings": "Ustawienia zaawansowane", + "app_hint": "Hasła aplikacji są alternatywnymi hasłami dla logowania IMAP, SMTP, CalDAV, CardDAV i EAS. Nazwa użytkownika pozostaje niezmieniona. Webmail SOGo nie jest dostępny za pośrednictwem haseł aplikacji.", + "allowed_protocols": "Dozwolone protokoły", + "app_name": "Nazwa aplikacji", + "app_passwds": "Hasła do aplikacji", + "apple_connection_profile": "Profil połączenia Apple", + "apple_connection_profile_complete": "Ten profil połączenia obejmuje parametry IMAP i SMTP, a także ścieżki CalDAV (kalendarze) i CardDAV (kontakty) dla urządzenia Apple.", + "apple_connection_profile_mailonly": "Ten profil połączenia zawiera parametry konfiguracji IMAP i SMTP dla urządzenia Apple.", + "apple_connection_profile_with_app_password": "Nowe hasło aplikacji jest generowane i dodawane do profilu, dzięki czemu nie trzeba wprowadzać hasła podczas konfigurowania urządzenia. Proszę nie udostępniaj tego pliku, ponieważ zapewnia on pełny dostęp do skrzynki pocztowej.", + "attribute": "Atrybut", + "authentication": "Uwierzytelnianie", + "change_password_hint_app_passwords": "Twoje konto ma %d hasła aplikacji, które nie zostaną zmienione. Aby nimi zarządzać, przejdź do zakładki Hasła aplikacji.", + "clear_recent_successful_connections": "Wyczyść udane połączenia", + "create_app_passwd": "Stwórz hasło do aplikacji", + "created_on": "Stworzony na", + "delete_ays": "Proszę o potwierdzenie procesu usuwania.", + "direct_protocol_access": "Ten użytkownik skrzynki pocztowej ma bezpośredni, zewnętrzny dostęp do następujących protokołów i aplikacji. To ustawienie jest kontrolowane przez administratora. Można tworzyć hasła aplikacji, aby przyznać dostęp do poszczególnych protokołów i aplikacji.Przycisk „Webmail” umożliwia jednokrotne logowanie (SSO) do SOGo i jest zawsze dostępny.", + "email": "Email", + "email_and_dav": "E-maile, kalendarze i kontakty", + "empty": "Brak wyników", + "expire_in": "wygasa w", + "fido2_webauthn": "FIDO2/WebAuthn (standard uwierzytelniania)", + "from": "od", + "generate": "generuj", + "last_mail_login": "Ostatni login na skrzynkę pocztową", + "last_pw_change": "Ostatnia zmiana hasła", + "last_ui_login": "Ostatni login UI", + "loading": "Ładowanie...", + "login_history": "Historia logowania", + "mailbox": "Skrzynka pocztowa", + "mailbox_general": "Ogólne ustawienia skrzynki", + "mailbox_settings": "Ustawienia", + "month": "miesiąc", + "months": "miesiące", + "no_last_login": "Brak ostatnich danych logowania do interfejsu użytkownika", + "open_logs": "Otwórz logi użytkownika", + "open_webmail_sso": "­Webmail", + "overview": "Przegląd", + "password": "Hasło", + "password_repeat": "Hasło (powtórz)", + "password_reset_info": "Jeśli nie ma wiadomości e-mail do odzyskiwania hasła, ta funkcja nie może być używana.", + "protocols": "Protokoły", + "pushover_evaluate_x_prio": "Eskaluj wiadomości o wysokim priorytecie [X-Priority: 1]", + "pushover_info": "Ustawienia powiadomień push będą stosowane do wszystkich czystych (niebędących spamem) wiadomości dostarczonych do %s, w tym aliasów (współdzielonych, niewspółdzielonych i oznaczonych).", + "pushover_only_x_prio": "Uwzględniaj tylko wiadomości o wysokim priorytecie [X-Priority: 1]", + "pushover_sender_array": "Uwzględnij następujące adresy e-mail nadawców (oddzielone przecinkami)", + "pushover_sender_regex": "Dopasuj nadawców według następującego regexu", + "pushover_text": "Tekst powiadomienia", + "pushover_title": "Tytuł powiadomienia", + "pushover_sound": "Dźwięk powiadomienia", + "pushover_vars": "Jeśli nie zdefiniowano filtra nadawcy, wszystkie wiadomości będą brane pod uwagę.
    Filtry regex oraz dokładne sprawdzanie nadawców można definiować indywidualnie – są one przetwarzane kolejno i nie zależą od siebie.
    Dostępne zmienne dla treści i tytułu (prosimy pamiętać o zasadach ochrony danych osobowych).", + "pushover_verify": "Zweryfikuj dane logowania", + "pw_recovery_email": "E-mail do odzyskiwania hasła", + "q_all": "Wszystkie kategorie", + "q_reject": "Odrzucono", + "quarantine_category": "Kategoria powiadomień o kwarantannie", + "quarantine_category_info": "Kategoria powiadomień „Odrzucone” obejmuje wiadomości, które zostały odrzucone, natomiast „Folder spam” powiadamia użytkownika o wiadomościach umieszczonych w folderze spam.", + "quarantine_notification_info": "Po wysłaniu powiadomienia elementy zostaną oznaczone jako „powiadomione” i żadne kolejne powiadomienia nie zostaną wysłane dla danego elementu.", + "recent_successful_connections": "Zarejestrowano udane połączenia", + "running": "Uruchomiony", + "save": "Zapisz zmiany", + "sender_acl_disabled": "Sprawdzenie nadawcy jest wyłączone", + "spam_score_reset": "Przywróć domyślne ustawienia serwera", + "status": "Status", + "syncjob_check_log": "Sprawdź log", + "syncjob_last_run_result": "Wynik ostatniego uruchomienia", + "syncjob_EX_OK": "Sukces", + "syncjob_EXIT_CONNECTION_FAILURE": "Problem z połączeniem", + "syncjob_EXIT_TLS_FAILURE": "Problem z szyfrowanym połączeniem", + "syncjob_EXIT_AUTHENTICATION_FAILURE": "Problem uwierzytelniania", + "syncjob_EXIT_OVERQUOTA": "Docelowa skrzynka pocztowa przekroczyła limit pojemności", + "syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Nie można połączyć się ze zdalnym serwerem", + "syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Niewłaściwa nazwa użytkownika lub hasło", + "text": "Tekst", + "tfa_info": "Uwierzytelnianie dwuskładnikowe pomaga chronić Twoje konto. Jeśli je włączysz, będziesz potrzebować haseł aplikacji, aby logować się do programów lub usług, które nie obsługują uwierzytelniania dwuskładnikowego (np. klientów poczty).", + "title": "Tytuł", + "value": "Wartość", + "verify": "Zweryfikuj", + "waiting": "Oczekuje", + "with_app_password": "z hasłem aplikacji", + "year": "rok", + "years": "lata", + "spam_aliases_info": "Alias antyspamowy to tymczasowy adres e-mail, który może być używany do ochrony właściwych adresów pocztowych.
    Opcjonalnie można ustawić czas wygaśnięcia, po którym alias zostanie automatycznie dezaktywowany, co pozwala skutecznie pozbyć się nadużywanych lub ujawnionych adresów." + }, + "warning": { + "session_ua": "Nieprawidłowy token formularza: Błąd walidacji User-Agent", + "cannot_delete_self": "Nie można usunąć zalogowanego użytkownika", + "domain_added_sogo_failed": "Dodano domenę, ale nie udało się ponownie uruchomić SOGo, sprawdź logi serwera.", + "dovecot_restart_failed": "Nie udało się ponownie uruchomić Dovecota, sprawdź logi", + "fuzzy_learn_error": "Błąd uczenia fuzzy hash: %s", + "hash_not_found": "Hash nie został odnaleziony lub został już usunięty", + "ip_invalid": "Pominięto nieprawidłowe IP: %s", + "is_not_primary_alias": "Pominięto alias niebędący głównym: %s", + "no_active_admin": "Nie można dezaktywować ostatniego aktywnego administratora", + "quota_exceeded_scope": "Przekroczono limit pojemności domeny: w tym zakresie domeny można tworzyć tylko skrzynki o nieograniczonej pojemności.", + "session_token": "Nieprawidłowy token formularza: niedopasowanie tokenów" + }, + "datatables": { + "collapse_all": "Zwiń wszystko", + "decimal": ".", + "emptyTable": "Brak danych w tabeli", + "expand_all": "Rozszerz wszystko", + "info": "Wyświetlanie od _START_ do _END_ z _TOTAL_ wpisów", + "infoEmpty": "Wyświetlanie od 0 do 0 z 0 wpisów", + "infoFiltered": "(filtrowane z _MAX_ suma wpisów)", + "thousands": ",", + "lengthMenu": "Pokaż wpisy _MENU_", + "loadingRecords": "Ładowanie...", + "processing": "Proszę czekać...", + "search": "Szukaj:", + "zeroRecords": "Nie znaleziono pasujących rekordów", + "paginate": { + "first": "Pierwszy", + "last": "Ostatni", + "next": "Następny", + "previous": "Poprzedni" + }, + "aria": { + "sortAscending": "Aktywuj, aby posortować kolumnę rosnąco", + "sortDescending": "Aktywuj, aby posortować kolumnę malejąco" + } + }, + "debug": { + "architecture": "Architektura", + "chart_this_server": "Wykres (ten serwer)", + "containers_info": "Informacje o kontenerze", + "container_running": "uruchomiony", + "container_disabled": "Kontener zatrzymany lub wyłączony", + "container_stopped": "Zatrzymany", + "cores": "rdzenie", + "current_time": "Czas systemowy", + "disk_usage": "Użycie dysku", + "docs": "Dokumentacja", + "error_show_ip": "Nie można ustalić publicznych adresów IP", + "external_logs": "Logi zewnętrzne", + "history_all_servers": "Historia (wszystkie serwery)", + "in_memory_logs": "Logi w pamięci", + "last_modified": "Ostatnia modyfikacja", + "log_info": "

    mailcow logi w pamięci są gromadzone na listach Redis i przycinane do wartości LOG_LINES (%d) co minutę, aby ograniczyć nadmierne obciążenie systemu.

    Logi w pamięci nie są przeznaczone do trwałego przechowywania. Wszystkie aplikacje, które zapisują logi w pamięci, wysyłają je również do daemon Dockera, a więc do domyślnego sterownika logowania.

    Ten typ logów należy wykorzystywać do debugowania drobnych problemów z kontenerami.

    Logi zewnętrzne są zbierane za pośrednictwem API danej aplikacji.

    Logi statyczne to głównie dzienniki aktywności, które nie są zapisywane przez Dockerd, ale powinny być trwałe (z wyjątkiem logów API).

    ", + "login_time": "Czas", + "logs": "Logi", + "memory": "Pamięć", + "online_users": "Użytkownik online", + "restart_container": "Restart", + "service": "Usługa", + "show_ip": "Pokaż publiczne IP", + "size": "Rozmiar", + "started_at": "Zaczęło się od", + "started_on": "Zaczęło się od", + "static_logs": "Logi statyczne", + "success": "Sukces", + "system_containers": "System i kontenery", + "timezone": "Strefa czasowa", + "uptime": "Czas pracy", + "update_available": "Dostępna jest aktualizacja", + "no_update_available": "System jest w najnowszej wersji", + "update_failed": "Nie można było sprawdzić aktualizacji", + "username": "Nazwa użytkownika", + "wip": "Obecnie praca w toku" + }, + "diagnostics": { + "cname_from_a": "Wartość pochodzi z rekordu A/AAAA. Obsługiwane, o ile rekord wskazuje na prawidłowy zasób.", + "dns_records": "Rekordy DNS", + "dns_records_24hours": "Pamiętaj, że zmiany wprowadzone w DNS mogą zająć nawet do 24 godzin, zanim ich aktualny stan zostanie poprawnie odzwierciedlony na tej stronie.\nTa sekcja ma na celu ułatwienie Ci konfiguracji rekordów DNS oraz sprawdzenie, czy wszystkie rekordy zostały prawidłowo zapisane w DNS.", + "dns_records_data": "Poprawne dane", + "dns_records_docs": "Proszę skonsultuj również dokumnetację.", + "dns_records_name": "Nazwa", + "dns_records_status": "Aktualny stan", + "dns_records_type": "Typ", + "optional": "Ten rekord jest opcjonalny." + }, + "fido2": { + "confirm": "Potwierdź", + "fido2_auth": "Logowanie za pomocą FIDO2", + "fido2_success": "Urządzenie pomyślnie zarejestrowane", + "fido2_validation_failed": "Walidacja nie powiodła się", + "fn": "Przyjazna nazwa FIDO", + "known_ids": "Znane Id", + "none": "Wyłączony", + "register_status": "Status rejestracji", + "rename": "Zmień nazwę", + "set_fido2": "Zarejestruj urządzenie FIDO2", + "set_fido2_touchid": "Zarejestruj Touch ID w Apple M1", + "set_fn": "Ustaw przyjazną nazwę w FIDO", + "start_fido2_validation": "Rozpocznij walidację FIDO2" + }, + "ratelimit": { + "disabled": "Wyłączone", + "second": "wiadomości / sekundę", + "minute": "wiadomości / minutę", + "hour": "wiadomości / godzinę", + "day": "wiadomości/ dzień" } } diff --git a/data/web/lang/lang.pt-br.json b/data/web/lang/lang.pt-br.json index f9c08ef02..db3c8cac2 100644 --- a/data/web/lang/lang.pt-br.json +++ b/data/web/lang/lang.pt-br.json @@ -1,7 +1,7 @@ { "acl": { - "alias_domains": "Adicionar domínios alias", - "app_passwds": "Gerenciar senhas de aplicativos", + "alias_domains": "Adicionar alias de domínios", + "app_passwds": "Gerenciar senhas de app", "bcc_maps": "Mapas BCC", "delimiter_action": "Ação delimitadora", "domain_desc": "Alterar descrição do domínio", @@ -9,7 +9,7 @@ "eas_reset": "Redefinir dispositivos EAS", "extend_sender_acl": "Permitir estender a ACL do remetente por endereços externos", "filters": "Filtros", - "login_as": "Faça login como usuário da mailbox", + "login_as": "Fazer login como usuário da mailbox", "mailbox_relayhost": "Alterar relayhost para uma mailbox", "prohibited": "Proibido pela ACL", "protocol_access": "Alterar o acesso ao protocolo", @@ -38,7 +38,7 @@ "add_domain_only": "Adicionar somente domínio", "add_domain_restart": "Adicionar domínio e reiniciar o SoGo", "alias_address": "Endereço (s) de alias", - "alias_address_info": "Endereço/s de e-mail completo ou @example .com, para capturar todas as mensagens de um domínio (separadas por vírgula). somente domínios mailcow.", + "alias_address_info": "Endereço(s) de e-mail completo(s) ou @example.com, para capturar todas as mensagens de um domínio (separadas por vírgula). Apenas domínios do Mailcow.", "alias_domain": "Domínio de alias", "alias_domain_info": "Somente nomes de domínio válidos (separados por vírgula).", "app_name": "Nome do aplicativo", @@ -109,7 +109,10 @@ "username": "Nome de usuário", "validate": "Validar", "validation_success": "Validado com sucesso", - "dry": "Simular sincronização" + "dry": "Simular sincronização", + "internal": "Interno", + "internal_info": "Aliases internos são acessíveis apenas a partir do próprio domínio ou alias de domínio.", + "sender_allowed": "Permitir enviar como este alias" }, "admin": { "access": "Acesso", @@ -149,13 +152,13 @@ "arrival_time": "Hora de chegada (hora do servidor)", "authed_user": "Usuário autoritário", "ays": "Tem certeza de que deseja continuar?", - "ban_list_info": "Veja uma lista de IPs banidos abaixo: rede (tempo restante de banimento) - [ações].
    Os IPs na fila para serem desbanidos serão removidos da lista de banimentos ativos em alguns segundos.
    Rótulos vermelhos indicam proibições permanentes ativas na lista negra.", + "ban_list_info": "Veja abaixo a lista de IPs banidos: rede (tempo restante do banimento) - [ações].
    IPs na fila para serem desbanidos serão removidos da lista de banimentos ativos em alguns segundos.
    Rótulos vermelhos indicam banimentos permanentes ativos na lista de bloqueio.", "change_logo": "Alterar logotipo", "logo_normal_label": "Normal", "logo_dark_label": "Invertido para o modo escuro", "configuration": "Configuração", "convert_html_to_text": "Converter HTML em texto sem formatação", - "copy_to_clipboard": "Text copied to clipboard!", + "copy_to_clipboard": "Copiado para a área de transferência!", "cors_settings": "Configurações do CORS", "credentials_transport_warning": "Aviso: Adicionar uma nova entrada no mapa de transporte atualizará as credenciais de todas as entradas com uma coluna correspondente do próximo salto.", "customer_id": "ID do cliente", @@ -186,9 +189,9 @@ "excludes": "Exclui esses destinatários", "f2b_ban_time": "Tempo (s) de proibição", "f2b_ban_time_increment": "O tempo de banimento é incrementado com cada banimento", - "f2b_blacklist": "Redes/hosts na lista negra", + "f2b_blacklist": "Redes/hosts na lista de bloqueio", "f2b_filter": "Filtros Regex", - "f2b_list_info": "Um host ou rede na lista negra sempre superará uma entidade na lista branca. As atualizações da lista levarão alguns segundos para serem aplicadas.", + "f2b_list_info": "Um host ou rede na lista de bloqueio sempre terá prioridade sobre uma entidade na lista de permissões. As atualizações das listas levarão alguns segundos para serem aplicadas.", "f2b_manage_external": "Gerenciar Fail2Ban externamente", "f2b_manage_external_info": "O Fail2ban ainda manterá a lista de banimentos, mas não definirá ativamente regras para bloquear o tráfego. Use a lista de banimento gerada abaixo para bloquear externamente o tráfego.", "f2b_max_attempts": "Máximo de tentativas", @@ -198,7 +201,7 @@ "f2b_parameters": "Parâmetros do Fail2ban", "f2b_regex_info": "Registros considerados: SoGo, Postfix, Dovecot, PHP-FPM.", "f2b_retry_window": "Repita a (s) janela (s) para o máximo de tentativas", - "f2b_whitelist": "Redes/hosts incluídos na lista branca", + "f2b_whitelist": "Redes/hosts na lista de permissões", "filter_table": "Tabela de filtros", "forwarding_hosts": "Anfitriões de encaminhamento", "forwarding_hosts_add_hint": "Você pode especificar endereços IPv4/IPv6, redes em notação CIDR, nomes de host (que serão resolvidos para endereços IP) ou nomes de domínio (que serão resolvidos para endereços IP consultando registros SPF ou, na ausência deles, registros MX).", @@ -220,7 +223,7 @@ "includes": "Inclua esses destinatários", "ip_check": "Verificação de IP", "ip_check_disabled": "A verificação de IP está desativada. Você pode ativá-lo em
    Sistema > Configuração > Opções > Personalizar", - "ip_check_opt_in": "Opte por usar o serviço de terceiros ipv4.mailcow.email. e ipv6.mailcow.email para resolver endereços IP externos.", + "ip_check_opt_in": "Opte por usar o serviço de terceiros ipv4.mailcow.email e ipv6.mailcow.email para resolver endereços IP externos.", "is_mx_based": "Baseado em MX", "last_applied": "Aplicado pela última vez", "license_info": "Uma licença não é necessária, mas ajuda no desenvolvimento.
    Registre seu GUID aqui ou comprar suporte para sua instalação de mailcow.", @@ -242,7 +245,7 @@ "oauth2_add_client": "Adicionar cliente OAuth2", "oauth2_client_id": "ID do cliente", "oauth2_client_secret": "Segredo do cliente", - "oauth2_info": "A implementação OAuth2 suporta o tipo de concessão \"Código de Autorização\" e emite tokens de atualização.
    \nO servidor também emite automaticamente novos tokens de atualização, depois que um token de atualização foi usado.

    \n• O escopo padrão é perfil. Somente usuários com caixa de e-mail podem ser autenticados contra o OAuth2. Se o parâmetro de escopo for omitido, ele voltará para perfil.
    \nCaminhos para solicitações OAuth2 API:
    \n
      \n
    • Endpoint de autorização: /oauth/authorize
    • \n
    • Endpoint token: /oauth/token
    • \n
    • Página de recursos: /oauth/profile
    • \n
    \nRegenerar o segredo do cliente não expirará os códigos de autorização existentes, mas eles não renovarão seu token.

    \nA revogação dos tokens do cliente causará o término imediato de todas as sessões ativas. Todos os clientes precisam se autenticar novamente.", + "oauth2_info": "A implementação do OAuth2 suporta o tipo de concessão \"Código de Autorização\" e emite tokens de atualização.
    \nO servidor também emite automaticamente novos tokens de atualização após um token de atualização ter sido usado.
    \nO escopo padrão é profile. Somente usuários de caixa de correio podem ser autenticados com o OAuth2. Se o parâmetro de escopo for omitido, o padrão será profile.
    \nO parâmetro state deve ser enviado pelo cliente como parte da solicitação de autorização.

    \nCaminhos para solicitações à API OAuth2:
    \n\n
      \n
    • Ponto de extremidade de autorização: /oauth/authorize
    • \n
    • Endpoint do token: /oauth/token
    • \n
    • Página de recursos: /oauth/profile
    • \n
    \n\nRegenerar o segredo do cliente não expirará os códigos de autorização existentes, mas impedirá a renovação do token.

    \nA revogação dos tokens de cliente causará o encerramento imediato de todas as sessões ativas. Todos os clientes precisarão se autenticar novamente.", "oauth2_redirect_uri": "URI de redirecionamento", "oauth2_renew_secret": "Gere um novo segredo de cliente", "oauth2_revoke_tokens": "Revogar todos os tokens do cliente", @@ -318,8 +321,8 @@ "rspamd_com_settings": "Um nome de configuração será gerado automaticamente, veja os exemplos de predefinições abaixo. Para obter mais detalhes, consulte a documentação do Rspamd", "rspamd_global_filters": "Mapas de filtro globais", "rspamd_global_filters_agree": "Eu vou ter cuidado!", - "rspamd_global_filters_info": "Os mapas de filtros globais contêm diferentes tipos de listas negras e brancas globais.", - "rspamd_global_filters_regex": "Seus nomes explicam seu propósito. Todo o conteúdo deve conter uma expressão regular válida no formato “/padrão/opções” (por exemplo, /. + @domain\\ .tld/i).
    \r\n Embora verificações rudimentares estejam sendo executadas em cada linha de regex, a funcionalidade do Rspamd pode ser interrompida se não conseguir ler a sintaxe corretamente.
    \r\n O Rspamd tentará ler o conteúdo do mapa quando alterado. Se você tiver problemas, reinicie o Rspamd para forçar o recarregamento do mapa.
    Os elementos da lista negra são excluídos da quarentena.", + "rspamd_global_filters_info": "Os mapas de filtros globais contêm diferentes tipos de listas globais de bloqueio e de permissões.", + "rspamd_global_filters_regex": "Os nomes explicam sua finalidade. Todo o conteúdo deve conter uma expressão regular válida no formato \"/padrão/opções\" (por exemplo, /.[+@domain.tld](mailto:+@domain.tld)/i).
    \nEmbora verificações básicas sejam executadas em cada linha de regex, a funcionalidade do Rspamd pode ser comprometida se ele não conseguir ler a sintaxe corretamente.
    \nO Rspamd tentará ler o conteúdo do mapa quando ele for alterado. Se você tiver problemas, reinicie o Rspamd para forçar o recarregamento do mapa.
    Elementos na lista de bloqueio são excluídos da quarentena.", "rspamd_settings_map": "Mapa de configurações do Rspamd", "sal_level": "Nível de humor", "save": "Salvar alterações", @@ -364,7 +367,52 @@ "iam_client_secret": "Senha de cliente", "iam_auth_flow": "Fluxo de autenticação", "iam_client_scopes": "Escopo do cliente", - "iam_default_template": "Template Padrão" + "iam_default_template": "Template Padrão", + "admin_quicklink": "Ocultar link rápido para página de login do administrador", + "app_hide": "Ocultar para login", + "login_page": "Página de login", + "domainadmin_quicklink": "Ocultar link rápido para página de login do administrador de domínio", + "filter": "Filtro", + "force_sso_text": "Se um provedor OIDC externo for configurado, esta opção oculta os formulários de login padrão do mailcow e mostra apenas o botão de single sign-on", + "force_sso": "Desabilitar login do mailcow e mostrar apenas single sign-on", + "iam": "Provedor de identidade", + "iam_attribute_field": "Campo de atributo", + "iam_authorize_url": "Endpoint de autorização", + "iam_auth_flow_info": "Além do fluxo de código de autorização (fluxo padrão no Keycloak), que é usado para login de single sign-on, o mailcow também suporta fluxo de autenticação com credenciais diretas. O fluxo Mailpassword tenta validar as credenciais do usuário usando a API REST do administrador do Keycloak. O mailcow recupera a senha hash do atributo mailcow_password, que é mapeado no Keycloak.", + "iam_basedn": "DN base", + "iam_default_template_description": "Se nenhum template for atribuído a um usuário, o template padrão será usado para criar a caixa de correio, mas não para atualizar a caixa de correio.", + "iam_description": "Configure um provedor externo para autenticação
    As caixas de correio dos usuários serão criadas automaticamente no primeiro login, desde que um mapeamento de atributos tenha sido definido.", + "iam_extra_permission": "Para que as configurações a seguir funcionem, o cliente mailcow no Keycloak precisa de uma conta de serviço e a permissão para visualizar usuários.", + "iam_host": "Host", + "iam_host_info": "Digite um ou mais hosts LDAP, separados por vírgulas.", + "iam_import_users": "Importar usuários", + "iam_login_provisioning": "Criar usuários automaticamente no login", + "iam_mapping": "Mapeamento de atributos", + "iam_bindpass": "Senha de vinculação", + "iam_periodic_full_sync": "Sincronização completa periódica", + "iam_port": "Porta", + "iam_realm": "Realm", + "iam_redirect_url": "URL de redirecionamento", + "iam_rest_flow": "Fluxo Mailpassword", + "iam_server_url": "URL do servidor", + "iam_sso": "Single sign-on", + "iam_sync_interval": "Intervalo de sincronização/importação (min)", + "iam_test_connection": "Testar conexão", + "iam_token_url": "Endpoint de token", + "iam_userinfo_url": "Endpoint de informações do usuário", + "iam_username_field": "Campo de nome de usuário", + "iam_binddn": "DN de vinculação", + "iam_use_ssl": "Usar SSL", + "iam_use_ssl_info": "Se habilitar SSL e a porta estiver definida como 389, ela será automaticamente substituída para usar 636.", + "iam_use_tls": "Usar StartTLS", + "iam_use_tls_info": "Se habilitar TLS, você deve usar a porta padrão para seu servidor LDAP (389). Portas SSL não podem ser usadas.", + "iam_version": "Versão", + "ignore_ssl_error": "Ignorar erros SSL", + "needs_restart": "precisa reiniciar", + "quicklink_text": "Mostrar ou ocultar links rápidos para outras páginas de login abaixo do formulário de login", + "task": "Tarefa", + "user_link": "Link do usuário", + "user_quicklink": "Ocultar link rápido para página de login do usuário" }, "danger": { "access_denied": "Acesso negado ou dados de formulário inválidos", @@ -501,7 +549,17 @@ "username_invalid": "O nome de usuário %s não pode ser usado", "validity_missing": "Por favor, atribua um período de validade", "value_missing": "Forneça todos os valores", - "yotp_verification_failed": "Falha na verificação do Yubico OTP: %s" + "yotp_verification_failed": "Falha na verificação do Yubico OTP: %s", + "authsource_in_use": "O provedor de identidade não pode ser alterado ou excluído pois está sendo usado por um ou mais usuários.", + "generic_server_error": "Ocorreu um erro inesperado no servidor. Entre em contato com seu administrador.", + "iam_test_connection": "Falha na conexão", + "max_age_invalid": "Idade máxima %s é inválida", + "mode_invalid": "Modo %s é inválido", + "mx_invalid": "Registro MX %s é inválido", + "required_data_missing": "Dados obrigatórios %s estão ausentes", + "version_invalid": "Versão %s é inválida", + "tfa_removal_blocked": "A autenticação de dois fatores não pode ser removida, pois é obrigatória para a sua conta.", + "quarantine_category_invalid": "A categoria da quarentena deve ser uma das seguintes: add_header, reject, all" }, "datatables": { "collapse_all": "Recolher tudo", @@ -672,7 +730,7 @@ "pushover_verify": "Verifique as credenciais", "quota_mb": "Cota (MiB)", "quota_warning_bcc": "Aviso de cota BCC", - "quota_warning_bcc_info": "Os avisos serão enviados em cópias separadas para os seguintes destinatários. O assunto será sufixado pelo nome de usuário correspondente entre colchetes, por exemplo: Aviso de cota (user@example.com).", + "quota_warning_bcc_info": "Os avisos serão enviados em cópias separadas para os seguintes destinatários. O assunto será precedido pelo nome de usuário correspondente entre colchetes, por exemplo: Aviso de cota (user@example.com).", "ratelimit": "Limite de taxa", "redirect_uri": "URL de redirecionamento/retorno de chamada", "relay_all": "Retransmita todos os destinatários", @@ -691,24 +749,44 @@ "sieve_desc": "Breve descrição", "sieve_type": "Tipo de filtro", "skipcrossduplicates": "Ignore mensagens duplicadas entre pastas (primeiro a chegar, primeiro a ser servido)", - "sogo_access": "Encaminhamento direto para o SOGoo", - "sogo_access_info": "Depois de fazer login, o usuário é automaticamente redirecionado para o SOGo.", + "sogo_access": "Encaminhamento direto para o SOGo", + "sogo_access_info": "Após o login, o usuário é automaticamente redirecionado para o SOGo.", "sogo_visible": "O alias é visível no SoGo", "sogo_visible_info": "Essa opção afeta somente objetos, que podem ser exibidos no SoGo (endereços de alias compartilhados ou não compartilhados apontando para pelo menos uma mailbox local). Se estiver oculto, um alias não aparecerá como remetente selecionável no SoGo.", "spam_alias": "Crie ou altere endereços de alias com limite de tempo", "spam_filter": "Filtro de spam", - "spam_policy": "Adicionar ou remover itens da lista branca/negra", + "spam_policy": "Adicionar ou remover itens da lista de permissões/bloqueio", "spam_score": "Defina uma pontuação de spam personalizada", "subfolder2": "Sincronizar na subpasta no destino
    (vazio = não usar subpasta)", "syncjob": "Editar tarefa de sincronização", - "target_address": "Ir para endereço/es (separados por vírgula)", + "target_address": "Ir para o(s) endereço(s) (separados por vírgula)", "target_domain": "Domínio de destino", "timeout1": "Tempo limite para conexão com o host remoto", "timeout2": "Tempo limite para conexão com o host local", "title": "Editar objeto", "unchanged_if_empty": "Se inalterado, deixe em branco", "username": "Nome de usuário", - "validate_save": "Valide e salve" + "validate_save": "Validar e salvar", + "internal": "Interno", + "internal_info": "Aliases internos são acessíveis apenas a partir do próprio domínio ou domínios alias.", + "mailbox_rename": "Renomear caixa de correio", + "mailbox_rename_agree": "Eu criei um backup.", + "mailbox_rename_warning": "IMPORTANTE! Crie um backup antes de renomear a caixa de correio.", + "mailbox_rename_alias": "Criar alias automaticamente", + "mailbox_rename_title": "Novo nome da caixa de correio local", + "mta_sts": "MTA-STS", + "mta_sts_info": "MTA-STS é um padrão que força a entrega de email entre servidores de email para usar TLS com certificados válidos.
    É usado quando DANE não é possível devido ao DNSSEC ausente ou não suportado.
    Nota: Se o domínio de recepção suporta DANE com DNSSEC, DANE é sempre preferido – MTA-STS atua apenas como fallback.", + "mta_sts_version": "Versão", + "mta_sts_version_info": "Define a versão do padrão MTA-STS – atualmente apenas STSv1 é válido.", + "mta_sts_mode": "Modo", + "mta_sts_mode_info": "Há três modos para escolher:
    • testing – política é apenas monitorada, violações não têm impacto.
    • enforce – política é rigorosamente aplicada, conexões sem TLS válido são rejeitadas.
    • none – política é publicada mas não aplicada.
    ", + "mta_sts_max_age": "Idade máxima", + "mta_sts_max_age_info": "Tempo em segundos que servidores de email de recepção podem armazenar esta política em cache até buscar novamente.", + "mta_sts_mx": "Servidor MX", + "mta_sts_mx_info": "Permite envio apenas para nomes de host de servidor de email explicitamente listados; o MTA de envio verifica se o nome do host DNS MX corresponde à lista de políticas e permite entrega apenas com certificado TLS válido (protege contra MITM).", + "mta_sts_mx_notice": "Múltiplos servidores MX podem ser especificados (separados por vírgulas).", + "sender_allowed": "Permitir enviar como este alias", + "sender_allowed_info": "Se desativado, este alias poderá apenas receber e-mails. Use a ACL de remetente para substituir essa configuração e conceder a caixas de correio específicas permissão para enviar." }, "fido2": { "confirm": "Confirme", @@ -729,7 +807,7 @@ "cancel": "Cancelar", "confirm_delete": "Confirme a exclusão", "delete_now": "Excluir agora", - "delete_these_items": "Confirme suas alterações no seguinte ID de objeto", + "delete_these_items": "Por favor, confirme as alterações feitas no seguinte ID de objeto.", "hibp_check": "Verifique em haveibeenpwned.com", "hibp_nok": "Combinado! Essa é uma senha potencialmente perigosa!", "hibp_ok": "Nenhuma combinação encontrada.", @@ -771,7 +849,15 @@ "password": "Senha", "reset_password": "Recuperar a senha", "request_reset_password": "Solicitar troca de senha", - "username": "Nome de usuário" + "username": "Nome de usuário", + "login_linkstext": "Login incorreto?", + "login_usertext": "Entrar como usuário", + "login_domainadmintext": "Entrar como administrador de domínio", + "login_admintext": "Entrar como administrador", + "login_user": "Login de usuário", + "login_dadmin": "Login como administrador de domínio", + "login_admin": "Login como administrador", + "email": "Endereço de email" }, "mailbox": { "action": "Ação", @@ -889,7 +975,7 @@ "recipient_map_new": "Novo destinatário", "recipient_map_new_info": "O destino do mapa do destinatário deve ser um endereço de e-mail válido ou um nome de domínio.", "recipient_map_old": "Destinatário original", - "recipient_map_old_info": "O destino original do mapa de um destinatário deve ser um endereço de e-mail válido ou um nome de domínio.", + "recipient_map_old_info": "O destino original do mapa de destinatário deve ser um endereço de e-mail válido ou um nome de domínio.", "recipient_maps": "Mapas de destinatários", "relay_all": "Retransmita todos os destinatários", "relay_unknown": "Retransmitir mailboxes desconhecidas", @@ -946,7 +1032,9 @@ "username": "Nome de usuário", "waiting": "Esperando", "weekly": "Semanalmente", - "yes": "✓" + "yes": "✓", + "iam": "Provedor de Identidade", + "internal": "Interno" }, "oauth2": { "access_denied": "Faça login como proprietário da mailbox para conceder acesso via OAuth2.", @@ -961,8 +1049,8 @@ "action": "Ação", "atts": "Anexos", "check_hash": "Arquivo de pesquisa hash @ VT", - "confirm": "Confirme", - "confirm_delete": "Confirme a exclusão desse elemento.", + "confirm": "Confirmar", + "confirm_delete": "Confirmar exclusão desse elemento.", "danger": "Perigo", "deliver_inbox": "Entregar na caixa de entrada", "disabled_by_config": "A configuração atual do sistema desativa a funcionalidade de quarentena. Defina “retenções por mailbox” e um “tamanho máximo” para os elementos de quarentena.", @@ -978,7 +1066,7 @@ "notified": "Notificado", "qhandler_success": "Solicitação enviada com sucesso para o sistema. Agora você pode fechar a janela.", "qid": "Respand AID", - "qinfo": "O sistema de quarentena salvará as mensagens rejeitadas no banco de dados (o remetente não terá a impressão de uma mensagem entregue), bem como as mensagens, que são entregues como cópia na pasta Lixo eletrônico de uma mailbox.\n
    “Aprenda como spam e exclua” aprenderá uma mensagem como spam por meio do Teorema de Bayes e também calculará hashes difusos para negar mensagens semelhantes no futuro.\n
    Esteja ciente de que aprender várias mensagens pode ser demorado, dependendo do seu sistema.
    Os elementos da lista negra são excluídos da quarentena.", + "qinfo": "O sistema de quarentena irá salvar no banco de dados os e-mails rejeitados (o remetente não terá a impressão de que o e-mail foi entregue), bem como os e-mails que são entregues como cópia na pasta de spam de uma mailbox.\n
    “Aprender como spam e excluir” irá classificar a mensagem como spam por meio do Teorema de Bayes e também calcular hashes fuzzy para bloquear mensagens semelhantes no futuro.\n
    Esteja ciente de que o aprendizado de múltiplas mensagens pode, dependendo do seu sistema, levar tempo.
    Elementos na lista de bloqueio são excluídos da quarentena.", "qitem": "Item de quarentena", "quarantine": "Quarentena", "quick_actions": "Ações", @@ -1123,12 +1211,15 @@ "verified_fido2_login": "Login FIDO2 verificado", "verified_totp_login": "Login TOTP verificado", "verified_webauthn_login": "Login verificado do WebAuthn", - "verified_yotp_login": "Login OTP verificado do Yubico" + "verified_yotp_login": "Login OTP verificado do Yubico", + "custom_login_modified": "Personalização de login foi salva com sucesso", + "iam_test_connection": "Conexão bem-sucedida", + "mailbox_renamed": "Caixa de correio foi renomeada de %s para %s" }, "tfa": { "authenticators": "Autenticadores", "api_register": "%s usa a API Yubico Cloud. Obtenha uma chave de API para sua chave aqui", - "confirm": "Confirme", + "confirm": "Confirmar", "confirm_totp_token": "Confirme suas alterações inserindo o token gerado", "delete_tfa": "Desativar o TFA", "disable_tfa": "Desative o TFA até o próximo login bem-sucedido", @@ -1141,7 +1232,7 @@ "reload_retry": "- (recarregue o navegador se o erro persistir)", "scan_qr_code": "Escaneie o código a seguir com seu aplicativo autenticador ou insira o código manualmente.", "select": "Por favor, selecione", - "set_tfa": "Defina o método de autenticação de dois fatores", + "set_tfa": "Método de autenticação de dois fatores", "start_webauthn_validation": "Iniciar validação", "tfa": "Autenticação de dois fatores", "tfa_token_invalid": "Token TFA inválido", @@ -1151,7 +1242,12 @@ "webauthn": "Autenticação WebAuthn", "waiting_usb_auth": "Aguardando o dispositivo USB...

    Toque no botão no seu dispositivo USB agora.", "waiting_usb_register": "Aguardando o dispositivo USB...

    Digite sua senha acima e confirme seu registro tocando no botão no seu dispositivo USB.", - "yubi_otp": "Autenticação Yubico OTP" + "yubi_otp": "Autenticação Yubico OTP", + "force_tfa": "Forçar o cadastro de 2FA no login", + "force_tfa_info": "O usuário será obrigado a configurar a autenticação de dois fatores antes de acessar o painel.", + "setup_title": "Autenticação de Dois Fatores Obrigatória", + "setup_required": "Sua conta exige autenticação de dois fatores. Configure um método de 2FA para continuar.", + "cancel_setup": "Cancelar e sair" }, "user": { "action": "Ação", @@ -1190,7 +1286,7 @@ "delete_ays": "Confirme o processo de exclusão.", "direct_aliases": "Endereços de alias diretos", "direct_aliases_desc": "Os endereços de alias diretos são afetados pelo filtro de spam e pelas configurações da política TLS.", - "direct_protocol_access": "Esse usuário da mailbox tem acesso externo direto aos seguintes protocolos e aplicativos. Essa configuração é controlada pelo administrador. As senhas de aplicativos podem ser criadas para conceder acesso a protocolos e aplicativos individuais.
    O botão “Login no webmail” fornece login único no SoGo e está sempre disponível.", + "direct_protocol_access": "Este usuário da mailbox possui acesso direto e externo aos seguintes protocolos e aplicações. Esta configuração é controlada pelo seu administrador. Senhas de aplicativo podem ser criadas para conceder acesso a protocolos e aplicações específicos.
    O botão “Webmail” fornece login único (SSO) para o SOGo e está sempre disponível.", "eas_reset": "Redefinir o cache do dispositivo ActiveSync", "eas_reset_help": "Em muitos casos, uma redefinição do cache do dispositivo ajudará a recuperar um perfil quebrado do ActiveSync.
    Atenção: Todos os elementos serão baixados novamente!", "eas_reset_now": "Reinicie agora", @@ -1266,12 +1362,12 @@ "sogo_profile_reset": "Redefinir perfil SoGo", "sogo_profile_reset_help": "Isso destruirá o perfil SoGo de um usuário e excluirá todos os dados de contato e calendário irrecuperáveis.", "sogo_profile_reset_now": "Redefina o perfil agora", - "spam_aliases": "Aliases de e-mail temporários", + "spam_aliases": "Aliases de e-mail de spam", "spam_score_reset": "Redefinir para o padrão do servidor", "spamfilter": "Filtro de spam", "spamfilter_behavior": "Avaliação", - "spamfilter_bl": "Lista negra", - "spamfilter_bl_desc": "Endereços de e-mail na lista negra para sempre serem classificados como spam e rejeitados. E-mails rejeitados não serão copiados para a quarentena. Podem ser usados curingas. Um filtro só é aplicado a aliases diretos (aliases com uma única caixa de correio de destino), excluindo aliases abrangentes e a própria mailbox.", + "spamfilter_bl": "Lista de bloqueio", + "spamfilter_bl_desc": "Endereços de e-mail na lista de bloqueio para sempre classificar como spam e rejeitados. E-mails rejeitados não serão copiados para a quarentena. Coringas podem ser utilizados. O filtro é aplicado apenas a aliases diretos (aliases com uma única mailbox de destino), excluindo aliases catch-all e a própria mailbox.", "spamfilter_default_score": "Valores padrão", "spamfilter_green": "Verde: esta mensagem não é spam", "spamfilter_hint": "O primeiro valor descreve a “pontuação baixa de spam”, o segundo representa a “alta pontuação de spam”.", @@ -1282,8 +1378,8 @@ "spamfilter_table_empty": "Não há dados para exibir", "spamfilter_table_remove": "remover", "spamfilter_table_rule": "Regra", - "spamfilter_wl": "Lista branca", - "spamfilter_wl_desc": "Os endereços de e-mail incluídos na lista branca são programados para nunca serem classificados como spam. Podem ser usados curingas. Um filtro só é aplicado a aliases diretos (aliases com uma única mailbox de destino), excluindo aliases abrangentes e a própria mailbox.", + "spamfilter_wl": "Lista de permissões", + "spamfilter_wl_desc": "Endereços de e-mail na lista de permissões são configurados para nunca serem classificados como spam. Coringas podem ser utilizados. O filtro é aplicado apenas a aliases diretos (aliases com uma única mailbox de destino), excluindo aliases catch-all e a própria mailbox.", "spamfilter_yellow": "Amarelo: esta mensagem pode ser spam, será marcada como spam e movida para sua pasta de lixo eletrônico", "status": "Status", "sync_jobs": "Trabalhos de sincronização", @@ -1297,7 +1393,7 @@ "syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Não é possível se conectar ao servidor remoto", "syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome de usuário ou senha incorretos", "tag_handling": "Definir o tratamento para e-mails marcados", - "tag_help_example": "Exemplo de um endereço de e-mail marcado: me +Facebook @example .org", + "tag_help_example": "Exemplo de endereço de e-mail com tag: eu+Facebook@exemplo.org", "tag_help_explain": "Na subpasta: uma nova subpasta com o nome da tag será criada abaixo da CAIXA DE ENTRADA (“Caixa de entrada/Facebook”).
    \r\nNo assunto: o nome das tags será anexado ao assunto do e-mail, por exemplo: “[Facebook] Minhas notícias”.", "tag_in_none": "Não faça nada", "tag_in_subfolder": "Na subpasta", @@ -1318,7 +1414,15 @@ "weeks": "semanas", "with_app_password": "com senha do aplicativo", "year": "ano", - "years": "anos" + "years": "anos", + "authentication": "Autenticação", + "overview": "Visão geral", + "protocols": "Protocolos", + "tfa_info": "A autenticação de dois fatores ajuda a proteger sua conta. Se você habilitá-la, precisará de senhas de aplicativo para fazer login em aplicativos ou serviços que não suportam autenticação de dois fatores (por exemplo, clientes de email).", + "expire_never": "Nunca expirar", + "forever": "Para sempre", + "pw_update_required": "Sua conta exige a alteração de senha. Defina uma nova senha para continuar.", + "spam_aliases_info": "Um alias de spam é um endereço de e-mail temporário que pode ser usado para proteger endereços de e-mail reais.
    Opcionalmente, pode-se definir um tempo de expiração para que o alias seja automaticamente desativado após o período definido, descartando efetivamente endereços que tenham sido abusados ou vazados." }, "warning": { "cannot_delete_self": "Não é possível excluir o usuário conectado", diff --git a/data/web/lang/lang.pt-pt.json b/data/web/lang/lang.pt-pt.json index e86391fa7..64f85dd35 100644 --- a/data/web/lang/lang.pt-pt.json +++ b/data/web/lang/lang.pt-pt.json @@ -340,7 +340,8 @@ "tls_policy": "Política de TLS", "quarantine_attachments": "Anexos de quarentena", "filters": "Filtros", - "smtp_ip_access": "Mudar anfitriões permitidos para SMTP" + "smtp_ip_access": "Mudar anfitriões permitidos para SMTP", + "app_passwds": "Gerenciar senhas de aplicativos" }, "warning": { "no_active_admin": "Não é possível desactivar o último administrador activo" diff --git a/data/web/lang/lang.ru-ru.json b/data/web/lang/lang.ru-ru.json index b0180256d..58353fb5c 100644 --- a/data/web/lang/lang.ru-ru.json +++ b/data/web/lang/lang.ru-ru.json @@ -109,7 +109,10 @@ "timeout2": "Тайм-аут для подключения к локальному хосту", "username": "Имя пользователя", "validate": "Проверить", - "validation_success": "Проверка прошла успешно" + "validation_success": "Проверка прошла успешно", + "internal": "Внутренний", + "internal_info": "Внутренние псевдонимы доступны только из самого домена или доменов-псевдонимов.", + "sender_allowed": "Разрешить отправку с этим псевдонимом" }, "admin": { "access": "Настройки доступа", @@ -550,7 +553,11 @@ "generic_server_error": "На сервере произошла непредвиденная ошибка. Пожалуйста, свяжитесь с вашим администратором.", "authsource_in_use": "Поставщик идентификационных данных не может быть изменен или удален, так как в данный момент он используется одним или несколькими пользователями.", "iam_test_connection": "Ошибка соединения", - "required_data_missing": "Отсутствуют необходимые данные %s" + "required_data_missing": "Отсутствуют необходимые данные %s", + "max_age_invalid": "Максимальный возраст %s недействителен", + "mode_invalid": "Режим %s недействителен", + "mx_invalid": "Запись MX %s недействительна", + "version_invalid": "Версия %s недействительна" }, "datatables": { "collapse_all": "Свернуть все", @@ -762,7 +769,22 @@ "title": "Изменение объекта", "unchanged_if_empty": "Если без изменений - оставьте пустым", "username": "Имя пользователя", - "validate_save": "Подтвердить и сохранить" + "validate_save": "Подтвердить и сохранить", + "internal": "Внутренний", + "internal_info": "Внутренние псевдонимы доступны только из самого домена или доменов-псевдонимов.", + "mta_sts": "MTA-STS", + "mta_sts_info": "MTA-STS — это стандарт, который обязывает почтовые серверы использовать TLS с подлинными сертификатами для доставки электронной почты.
    Он используется, когда DANE невозможен из-за неиспользуемого или неподдерживаемого DNSSEC.
    Примечание: если принимающий домен поддерживает DANE с DNSSEC, всегда предпочитается DANE — MTA-STS действует только как резервный вариант.", + "mta_sts_version": "Версия", + "mta_sts_version_info": "Определяет версию стандарта MTA-STS – на данный момент существует только STSv1.", + "mta_sts_mode": "Режим", + "mta_sts_mode_info": "Есть три режима на выбор:
    • testing – политика только наблюдается, нарушения не имеют последствий.
    • enforce – политика соблюдается строго, соединения без подлинного TLS отклоняются.
    • none – политика опубликована, но не применяется.
    ", + "mta_sts_max_age": "Максимальный возраст", + "mta_sts_max_age_info": "Время в секундах, в течение которого принимающие почтовые серверы могут кэшировать эту политику перед повторной загрузкой.", + "mta_sts_mx": "Сервер MX", + "mta_sts_mx_info": "Разрешает отправку только на явно указанные имена хостов почтовых серверов; отправляющий MTA проверяет, соответствует ли DNS-имя MX-хоста списку политик, и разрешает доставку только с подлинным TLS-сертификатом (защита от MITM).", + "mta_sts_mx_notice": "Можно указать несколько MX-серверов (через запятую).", + "sender_allowed": "Разрешить отправку с этим псевдонимом", + "sender_allowed_info": "Если отключено, этот псевдоним может только принимать почту. Используйте ACL отправителя, чтобы переопределить и предоставить определенным почтовым ящикам разрешение на отправку." }, "fido2": { "confirm": "Подтвердить", @@ -832,7 +854,8 @@ "login_admintext": "Войти как администратор", "login_user": "Вход для пользователей", "login_dadmin": "Вход для администраторов домена", - "login_admin": "Вход для администраторов" + "login_admin": "Вход для администраторов", + "email": "Email-адрес" }, "mailbox": { "action": "Действия", @@ -1008,7 +1031,8 @@ "waiting": "В ожидании", "weekly": "Раз в неделю", "yes": "✓", - "iam": "Поставщик идентификационных данных" + "iam": "Поставщик идентификационных данных", + "internal": "Внутренний" }, "oauth2": { "access_denied": "Пожалуйста, войдите в систему как владелец почтового аккаунта, чтобы получить доступ через OAuth2.", @@ -1331,7 +1355,7 @@ "sogo_profile_reset": "Сбросить профиль SOGo", "sogo_profile_reset_help": "Внимание: это удалит настройки профиля SOGo вместе с всеми контактами, календарями и фильтрами безвозвратно.", "sogo_profile_reset_now": "Сбросить профиль сейчас", - "spam_aliases": "Временные псевдонимы электронной почты", + "spam_aliases": "Псевдонимы для спама", "spam_score_reset": "Сброс на настройки по умолчанию", "spamfilter": "Спам фильтр", "spamfilter_behavior": "Фильтрация спама", @@ -1387,7 +1411,10 @@ "authentication": "Аутентификация", "tfa_info": "Двухфакторная аутентификация помогает защитить вашу учетную запись. Если вы включите эту функцию, вам понадобятся пароли приложений для входа в приложения или службы, которые не поддерживают двухфакторную аутентификацию (например, почтовые клиенты).", "protocols": "Протоколы", - "overview": "Обзор" + "overview": "Обзор", + "expire_never": "Никогда не истекает", + "forever": "Навсегда", + "spam_aliases_info": "Псевдоним для спама — это временный адрес электронной почты, который можно использовать для защиты реальных адресов.
    При желании можно установить срок действия, по истечении которого псевдоним будет автоматически деактивирован, что позволяет эффективно избавляться от адресов, которые были использованы не по назначению или стали доступны посторонним лицам." }, "warning": { "cannot_delete_self": "Вы не можете удалить сами себя", diff --git a/data/web/lang/lang.si-si.json b/data/web/lang/lang.si-si.json index 56da00f74..39d57eb31 100644 --- a/data/web/lang/lang.si-si.json +++ b/data/web/lang/lang.si-si.json @@ -42,7 +42,7 @@ "app_name": "Ime aplikacije", "app_password": "Dodaj geslo aplikacije", "app_passwd_protocols": "Dovoljeni protokoli za geslo aplikacije", - "automap": "Poskusi samodejno preslikati mape (\"Sent items\", \"Sent\" => \"Poslano\" ipd.)", + "automap": "Poskusi samodejno preslikati mape (\"Poslani elementi\", \"Poslano\" => \"Poslano\" ipd.)", "backup_mx_options": "Možnosti posredovanja (relay)", "comment_info": "Zasebni komentarji niso vidni uporabnikom, javni komentarji pa so prikazani kot opis, ko se z miško postavimo nad uporabnika v pregledu", "custom_params": "Parametri po meri", @@ -109,7 +109,10 @@ "relay_transport_info": "
    Info
    Definirate lahko preslikave transportov za cilj po meri za to domeno. Če ni nastavljena, se ustvari MX poizvedba.", "syncjob_hint": "Pozor! Gesla se morajo shraniti v golo besedilo!", "timeout2": "Časovna omejitev za povezavo do lokalnega gostitelja", - "dry": "Simuliraj sinhronizacijo" + "dry": "Simuliraj sinhronizacijo", + "internal": "Notranje", + "internal_info": "Notranji vzdevki so dostopni samo iz lastne domene ali vzdevkov domen.", + "sender_allowed": "Dovoli pošiljanje kot ta vzdevek" }, "admin": { "access": "Dostop", @@ -150,7 +153,7 @@ "customize": "Prilagodi", "destination": "Cilj", "dkim_add_key": "Dodaj ARC/DKIM ključ", - "dkim_domains_selector": "Izbira", + "dkim_domains_selector": "Izbirnik", "dkim_domains_wo_keys": "Izberi domene z manjkajočimi ključi", "dkim_from": "Od", "dkim_from_title": "Izvorna domena od katere prekopiram podatke", @@ -176,8 +179,8 @@ "f2b_filter": "Regex filtri", "f2b_max_attempts": "Največ poskusov", "f2b_max_ban_time": "Maksimalno trajanje blokade (s)", - "f2b_netban_ipv4": "velikost subneta IPv4 za blokiranje (8-32)", - "f2b_netban_ipv6": "Velikost subneta IPv6 za blokiranje (8-128)", + "f2b_netban_ipv4": "velikost podomrežja IPv4 za blokiranje (8-32)", + "f2b_netban_ipv6": "Velikost podomrežja IPv6 za blokiranje (8-128)", "f2b_parameters": "Fail2ban parametri", "f2b_regex_info": "Upoštevajo se dnevniki SOGo, Postfix, Dovecot, PHP-FPM.", "f2b_retry_window": "Upoštevan čas (s) za največ poskusov", @@ -187,41 +190,41 @@ "generate": "ustvari", "guid": "GUID - enolični ID instance", "guid_and_license": "GUID & licenca", - "hash_remove_info": "Odstranitev hasha za omejitev (če obstaja) bo povsem ponastavilo njen števec.
    \n Vsak hash je prikazan z individualno barvo.", - "help_text": "Zamenjaj tekst za pomoč pod masko za prijavo (HTML je dovoljen)", + "hash_remove_info": "Odstranitev zgoščene vrednosti za omejitev (če obstaja) bo povsem ponastavilo njen števec.
    \n Vsaka zgoščena vrednost je prikazana z individualno barvo.", + "help_text": "Preglasi besedilo za pomoč pod masko za prijavo (HTML je dovoljen)", "host": "Gostitelj", "html": "HTML", "import": "Uvozi", "import_private_key": "Uvozi zasebni ključ", - "in_use_by": "V uporabi", + "in_use_by": "V uporabi od", "inactive": "Neaktivno", "include_exclude": "Vključi/Izključi", "include_exclude_info": "Privzeto - če ni izbire - so vključeni vsi poštni predali", "includes": "Vključi te prejemnike", - "ip_check": "Kontrola IP", - "ip_check_disabled": "Kontrola IP je onemogočena. Lahko jo omogočite pod
    Sistem > Konfiguracija > Možnosti > Prilagodi", - "ip_check_opt_in": "Opt-in za uporabo zunanje storitve ipv4.mailcow.email in ipv6.mailcow.email za razreševanje zunanjih IP.", - "is_mx_based": "Glede na MX", - "last_applied": "Nazadnje aplicirano", + "ip_check": "Preverjanje IP-ja", + "ip_check_disabled": "Preverjanje IP-ja je onemogočeno. Lahko ga omogočite pod
    Sistem > Konfiguracija > Možnosti > Prilagodi", + "ip_check_opt_in": "Prijavite se za uporabo storitev tretjih oseb ipv4.mailcow.email in ipv6.mailcow.email za razreševanje zunanjih IP naslovov.", + "is_mx_based": "Glede na MX zapis", + "last_applied": "Nazadnje uporabljeno", "link": "Povezava", "loading": "Prosim počakajte...", "login_time": "Čas prijave", - "logo_info": "Vaša slika bo pomanjšana na velikost 40px za zgornjo navigacijo in največjo velikost 250px za začetno stran. Zelo priporočena je uporaba grafike brez izgube kakovosti ob spremembi velikosti.", + "logo_info": "Vaša slika bo pomanjšana na višino 40 slikovnih pik za zgornjo navigacijsko vrstico in na največjo širino 250 slikovnih pik za začetno stran. Zelo priporočljiva je skalabilna grafika.", "message": "Sporočilo", "message_size": "Velikost sporočila", "nexthop": "Naslednji skok", "no": "✕", "no_active_bans": "Ni aktivnih blokad", - "no_new_rows": "Ni dodatnih vrstic", + "no_new_rows": "Nadaljnjih vrstic ni na voljo", "no_record": "Ni zapisa", "oauth2_apps": "OAuth2 aplikacije", "oauth2_add_client": "Dodaj OAuth2 klienta", - "oauth2_client_id": "ID klienta", - "oauth2_client_secret": "Skrivnost (secret)", + "oauth2_client_id": "ID odjemalca", + "oauth2_client_secret": "Skrivnost odjemalca", "oauth2_redirect_uri": "URI za preusmeritev", - "oauth2_renew_secret": "Generiraj nov client secret", - "oauth2_revoke_tokens": "Zavrni vse tokene klientov", - "optional": "opcijsko", + "oauth2_renew_secret": "Generiraj novo skrivnost odjemalca", + "oauth2_revoke_tokens": "Prekliči vse žetone odjemalca", + "optional": "neobvezno", "options": "Možnosti", "password": "Geslo", "password_length": "Dolžina gesla", @@ -236,21 +239,21 @@ "private_key": "Zasebni ključ", "quarantine": "Karantena", "quarantine_bcc": "Pošlji kopijo vseh obvestil (BCC) temu prejemniku:
    Pustite prazno za izklop te funkcije. Nepodpisana, nepreverjena pošta. Uporabljalo naj bi se samo za interno dostavo.", - "quarantine_exclude_domains": "Izključi domene in alias-domene", - "quarantine_max_age": "Maksimalna starost v dnevnih
    Vrednost mora biti večja ali enaka 1 dnevu", - "quarantine_max_score": "Opusti obvestilo, če je ocena spama večja od te vrednosti:
    Privzeto 9999.0", - "quarantine_max_size": "Največja velikost v MiB (Večji elementi so zavrženi):
    0 ne pomeni neomejeno.", - "quarantine_notification_html": "Predloga sporočila za obvestilo:
    Pustite prazno za obnovitev privzete predloge.", - "quarantine_notification_sender": "Pošiljatelj obvestila", - "quarantine_notification_subject": "Naslov obvestila", - "quarantine_release_format": "Oblika sproščenih elementov", + "quarantine_exclude_domains": "Izključi domene in vzdevke domen", + "quarantine_max_age": "Najvišja starost v dneh
    Vrednost mora biti enaka ali večja od 1 dneva.", + "quarantine_max_score": "Zavrzi obvestilo, če je ocena neželene pošte višja od te vrednosti:
    Privzeto 9999,0", + "quarantine_max_size": "Največja velikost v MiB (večji elementi so zavrženi):
    0 ne pomeni neomejeno.", + "quarantine_notification_html": "Predloga za obvestilo po e-pošti:
    Pustite prazno, če želite obnoviti privzeto predlogo.", + "quarantine_notification_sender": "Pošiljatelj obvestil po e-pošti", + "quarantine_notification_subject": "Zadeva e-poštnega obvestila", + "quarantine_release_format": "Oblika izdanih elementov", "quarantine_release_format_att": "Kot priponka", - "quarantine_release_format_raw": "Nespremenjen original", - "quarantine_retention_size": "Število zadržanj na poštni predal:
    0 pomeni neaktivno,", + "quarantine_release_format_raw": "Nespremenjen izvirnik", + "quarantine_retention_size": "Hrambe na poštni predal:
    0 pomeni neaktivno.", "quota_notification_sender": "Pošiljatelj obvestila", "quota_notification_subject": "Predmet obvestila", - "quota_notifications": "Obvestila o omejitvi", - "quota_notifications_info": "Obvestila o omejitvi so poslana uporabnikom enkrat, ko presežejo 80% in enkrat ko presežejo 95% zasedenosti.", + "quota_notifications": "Obvestila o kvotah", + "quota_notifications_info": "Obvestila o kvoti se uporabnikom pošljejo enkrat, ko presežejo 80 % in enkrat, ko presežejo 95 % porabe.", "queue_unban": "odblokiraj", "r_active": "Aktivne omejitve", "r_inactive": "Neaktivne omejitve", @@ -266,8 +269,8 @@ "remove": "Odstrani", "remove_row": "Odstrani vrstico", "reset_default": "Ponastavi na privzeto", - "reset_limit": "Odstrani hash", - "routing": "Routing", + "reset_limit": "Odstrani zgoščeno vrednost", + "routing": "Usmerjanje", "rsetting_add_rule": "Dodaj pravilo", "rsetting_content": "Vsebina pravila", "rsetting_desc": "Kratek opis", @@ -275,10 +278,10 @@ "rsetting_none": "Ni pravil na voljo", "rsettings_insert_preset": "Vstavi prednastavljen primer \"%s\"", "rsettings_preset_1": "Onemogoči vse razen DKIM in omejitve za prijavljene uporabnike", - "rsettings_preset_2": "Postmasterji želijo spam", - "rsettings_preset_3": "Dovoli samo specifične pošiljatelje za poštni predal (npr. uporaba samo kot interni poštni predal)", + "rsettings_preset_2": "Poštni upravitelji želijo neželeno pošto", + "rsettings_preset_3": "Dovoli samo določene pošiljatelje za poštni predal (tj. uporabo samo kot notranji poštni predal)", "rsettings_preset_4": "Onemogoči Rspamd za domeno", - "rspamd_com_settings": "Ime nastavitve bo samodejno generirano. Prosim oglejte si primere nastavitev spodaj. Za več informacij si oglejte dokumentacijo Rspamd", + "rspamd_com_settings": "Ime nastavitve bo samodejno ustvarjeno, oglejte si spodnje primere prednastavitev. Za več podrobnosti glejte dokumentacijo Rspamd", "rspamd_global_filters": "Globalne preslikave filtrov", "rspamd_global_filters_agree": "Previden bom!", "rspamd_global_filters_info": "Globalni filtri vsebujejo različne vrste globalnih seznamov zavrnjenih in dovoljenih vsebin.", @@ -293,30 +296,30 @@ "f2b_list_info": "Gostitelj ali omrežje na seznamu zavrnjenih bo vedno imelo prednost pred entiteto na seznamu dovoljenih. Posodobitve seznama bodo trajale nekaj sekund, da se uporabijo.", "forwarding_hosts": "Gostitelji za posredovanje", "forwarding_hosts_add_hint": "Lahko vpišete IPv4/IPv6 naslove, mreže v CIDR obliki, imena gostiteljev (kateri se prevedejo v IP naslove) ali imena domen (katera se prevedejo v IP naslove glede na poizvedbo po SPF zapisih, v primeru manjkajočih zapisov pa MX zapisih).", - "forwarding_hosts_hint": "Dohodna sporočila so brezpogojno sprejeta od katerih koli gostiteljev v tem seznamu. Ti gostitelji se ne bodo preverjali po DNSBL seznamih in ne bodo dodani v greyliste. Prejeti spam s teh gostiteljev ni nikoli zavrnjen, opcijsko pa se lahko premakne v mapo neželene pošte. Najpogostejša uporaba za to je navedba poštnih strežnikov, iz katerih ste nastavili pravilo za posredovanje pošte na vaš mailcow strežnik.", - "license_info": "Licenca ni zahtevana, a pomaga pri nadaljnjem razvoju.
    Registrirajte svoj GUID tukaj ali Kupite podporo za svojo namestitev Mailcow.", - "lookup_mx": "Cilj je regular expression za ujemanje MX zapisov (.*\\.google\\.com za usmeritev vse pošte na MX, ki se konča z google.com, preko tega skoka)", + "forwarding_hosts_hint": "Dohodna sporočila so brezpogojno sprejeta od katerih koli gostiteljev v tem seznamu. Ti gostitelji se ne bodo preverjali po DNSBL seznamih in ne bodo dodani v listo sivih. Prejeta neželena pošta s teh gostiteljev ni nikoli zavrnjena, opcijsko pa se lahko premakne v mapo neželene pošte. Najpogostejša uporaba za to je navedba poštnih strežnikov, iz katerih ste nastavili pravilo za posredovanje pošte na vaš mailcow strežnik.", + "license_info": "Licenca ni zahtevana, a pomaga pri nadaljnjem razvoju.
    Registrirajte svoj GUID tukaj ali Kupite podporo za svojo namestitev Mailcow.", + "lookup_mx": "Cilj je regularni izraz, ki se ujema z imenom MX (.*\\.google\\.com za usmerjanje vse pošte, usmerjene na MX, ki se konča na google.com, prek tega skoka)", "main_name": "Naziv \"mailcow UI\"", "merged_vars_hint": "Sive vrstice so združene iz vars.(local.)inc.php in jih ni mogoče spremeniti.", - "oauth2_info": "OAuth2 implementacija omogoča grant vrste \"Authorization code\" in izdaja refresh tokene.
    \nStrežnik prav tako izda nove refresh tokene, ko je bil refresh token uporabljen

    \n• Privzeti obseg je profile. Samo uporabniki poštnih predalov se lahko prijavijo s pomočjo OAuth2. Če parameter obsega ni vnesen, se nastavi na profile.
    \n• Parameter state mora biti poslan s strani klienta kot del zahtevka za avtorizacijo .

    \nPoti za OAuth2 API:
    \n
      \n
    • Endpoint za avtorizacijo: /oauth/authorize
    • \n
    • Endpoint za tokene: /oauth/token
    • \n
    • Stran vira: /oauth/profile
    • \n
    \nPonovno generiranje client secret ne bo razveljavilo obstoječih avtorizacijskih kod, ne bodo pa mogle obnoviti svoje tokene.

    \nZavrnitev client tokenov bo povzročilo tekojčno prekinitev aktivnih sej. Vsi klienti se bodo morali ponovno prijaviti.", - "quarantine_redirect": "Preusmeri vsa obvestila k temu prejemniku:
    Pustite prazno, da onemogočite. Nepodpisana, nepreverjena pošta. Uporabljalo bi se naj samo za interno dostavo.", + "oauth2_info": "Implementacija OAuth2 podpira vrsto odobritve »Avtorizacijska koda« in izda osvežilne žetone.
    \nStrežnik samodejno izda tudi nove osvežilne žetone, ko je žeton za osvežitev uporabljen.

    \n• Privzeti obseg je profile. Prek OAuth2 je mogoče overiti samo uporabnike poštnega predala. Če parameter obsega izpustite, se vrne na profile.
    \n• Parameter state mora odjemalec poslati kot del zahteve za avtorizacijo.

    \nPoti za zahteve do API-ja OAuth2:
    \n
      \n
    • Končna točka avtorizacije: /oauth/authorize
    • \n
    • Končna točka žetona: /oauth/token
    • \n
    • Stran z viri: /oauth/profile
    • \n
    \nPonovno ustvarjanje skrivnosti odjemalca ne bo poteklo obstoječih kod za avtorizacijo, vendar ne bo obnovilo žetona.

    \nPreklic žetonov odjemalca bo povzročil takojšnjo prekinitev vseh aktivnih sej. Vse stranke se morajo ponovno overiti.", + "quarantine_redirect": "Preusmerite vsa obvestila temu prejemniku:
    Pustite prazno, če želite onemogočiti. Nepodpisana, nepreverjena pošta. Dostavljeno samo interno.", "quota_notification_html": "Predloga sporočila za obvestilo:
    Pustite prazno za obnovitev privzete predloge.", - "quota_notifications_vars": "{{percent}} pomeni trenutna omejitev uporabnika
    {{username}} je ime poštnega predala", - "r_info": "Sivi/onemogočeni elementi v seznamu aktivnih omejitev niso znane kot veljavne omejitve za mailcow in ne morejo biti premaknjene. Neznane omejitve bodo kljub temu nastavljene po vrstnem redu pojavitve.
    Nove elemente lahko dodate v inc/vars.local.inc.php da jih lahko vklopite ali izklopite.", - "relayhosts_hint": "Določite transporte glede na pošiljatelja, da jih lahko izberete v konfiguraciji domene.
    \nTransportni servis je vedno \"smtp:\" in bo poskušal s TLS ko bo na voljo. Wrapped TLS (SMTPS) ni podprto. Upošteva se uporabnikova politika odhodnega TLS.
    \nVpliva na izbrane domene vključno z alias domenami.", - "transport_dest_format": "Regex ali sintaksa: example.org, .example.org, *, box@example.org (več vrednosti ločite z vejico)", - "transport_test_rcpt_info": "• Uporabite null@hosted.mailcow.de za testiranje relaya na drugo destinacijo.", - "rspamd_global_filters_regex": "Njihovi nazivi pojasnijo njihov namen. Vsa vsebina mora imeti veljaven regular expression v obliki \"/pattern/options\" (npr. /.+@domain\\.tld/i).
    \nČeprav se v vsaki vrstici regexa izvedejo osnovni pregledi, je lahko funkcionalnost programa Rspamd motena, če sintaksa ni pravilna.
    \nRspamd bo poskušal prebrati vsebino preslikave, ko bo spremenjena. Če imate težave, ponovno zaženite Rspamd, da prisilite ponovno nalaganje preslikav.
    Elementi na seznamu zavrnjenih so izključeni iz karantene.", + "quota_notifications_vars": "{{percent}} je enako trenutni kvoti uporabnika
    {{username}} je ime poštnega predala", + "r_info": "Sivi/onemogočeni elementi v seznamu aktivnih omejitev niso znane kot veljavne omejitve za mailcow in ne morejo biti premaknjene. Neznane omejitve bodo kljub temu nastavljene po vrstnem redu pojavljanja.
    Nove elemente lahko dodate v inc/vars.local.inc.php da jih lahko vklopite ali izklopite.", + "relayhosts_hint": "Določite transporte, odvisne od pošiljatelja, da jih boste lahko izbrali v pogovornem oknu za konfiguracijo domen.
    \n Transportna storitev je vedno »smtp:« in bo zato poskusila s TLS, ko bo ponujena. Zaviti TLS (SMTPS) ni podprt. Upošteva se individualna nastavitev pravilnika za odhodni TLS uporabnika.
    \n Vpliva na izbrane domene, vključno z vzdevki domen.", + "transport_dest_format": "Regex ali sintaksa: example.org, .example.org, *, box@example.org (več vrednosti je lahko ločenih z vejicami)", + "transport_test_rcpt_info": "• Za preizkus posredovanja v tujino uporabite null@hosted.mailcow.de.", + "rspamd_global_filters_regex": "Njihova imena pojasnjujejo njihov namen. Vsa vsebina mora vsebovati veljaven regularni izraz v obliki »/vzorec/možnosti« (npr. /.+@domena\\.tld/i).
    \n Čeprav se v vsaki vrstici regularnega izraza izvajajo osnovna preverjanja, je lahko funkcionalnost Rspamdsa pokvarjena, če sintakse ne prebere pravilno.
    \n Rspamd bo poskušal prebrati vsebino zemljevida, ko se bo spremenila. Če pride do težav, znova zaženite Rspamd, da uveljavite ponovno nalaganje zemljevida.
    Elementi na seznamu zavrnjenih so izključeni iz karantene.", "rspamd_settings_map": "Preslikava nastavitev Rspamd", "sal_level": "Moo stopnja", "save": "Shrani spremembe", "search_domain_da": "Išči domene", "send": "Pošlji", "sender": "Pošiljatelj", - "service": "Servis", - "service_id": "ID servisa", + "service": "Storitev", + "service_id": "ID storitve", "source": "Vir", - "spamfilter": "Spam filter", + "spamfilter": "Filter neželene pošte", "subject": "Predmet", "success": "Uspešno", "sys_mails": "Sistemska pošta", @@ -326,7 +329,7 @@ "title_name": "Naziv spletnega mesta \"mailcow UI\"", "to_top": "Nazaj na vrh", "transport_maps": "Preslikave transportov", - "transports_hint": "• Vpis preslikave transporta nadredi preslikavo transporta odvisno od pošiljatelja.
    \n• Preferenčno se uporabljajo transporti glede na MX zapise.
    \n• Izhodne TLS politike na uporabnika so ignorirane in se lahko vsilijo samo s preslikavami TLS politik.
    \n• Transportni servis za definirane transporte je vedno \"smtp:\" in bo posledično poskušal TLS ko bo ponujeno. Wrapped TLS (SMTPS) ni podprto.
    \n• Naslovi, ki se ujemajo z \"/localhost$/\" bodo vedno preneseni preko \"local:\", in zato destinacija \"*\" ne bo vplivala na te naslove.
    \n• Za določitev poverilnic za naslednji skok (npr. \"[host]:25\"), Postfix vedno preveri \"host\" preden išče \"[host]:25\". Zaradi takšnega obnašanja je nemogoče hkrati uporabiti \"host\" in \"[host]:25\".", + "transports_hint": "• Vnos preslikave transporta preglasi preslikavo transporta, ki je odvisen od pošiljatelja.
    \n• Po možnosti se uporabljajo transporti, ki temeljijo na MX.
    \n• Nastavitve pravilnika TLS za odhodne uporabnike se prezrejo in jih je mogoče uveljaviti le z vnosi v zemljevidu pravilnika TLS.
    \n• Storitev transporta za definirane transporte je vedno »smtp:« in bo zato poskusila s TLS, ko bo ponujena. Zaviti TLS (SMTPS) ni podprt.
    \n• Naslovi, ki se ujemajo z \"/localhost$/\" bodo vedno preneseni preko \"local:\", in zato cilj \"*\" ne bo veljal za te naslove.
    \n• Za določitev poverilnic za zgledni naslednji skok \"[host]:25\", Postfix vedno poišče \"host\" preden poišče \"[host]:25\". Zaradi tega vedenja je nemogoče hkrati uporabljati \"host\" in \"[host]:25\".", "ui_footer": "Noga (HTML dovoljen)", "ui_header_announcement": "Obvestila", "ui_header_announcement_active": "Nastavi obvestilo kot aktivno", @@ -338,7 +341,7 @@ "ui_header_announcement_type_info": "Info", "ui_header_announcement_type_warning": "Pomembno", "ui_texts": "Oznake in besedila UI", - "unban_pending": "unban v postopku", + "unban_pending": "odblokada v teku", "unchanged_if_empty": "Če je nespremenjeno, pustite prazno", "upload": "Naloži", "username": "Uporabniško ime", @@ -393,7 +396,7 @@ "iam_token_url": "Končna točka žetona", "iam_userinfo_url": "Končna točka z uporabniškimi podatki", "iam_username_field": "Polje z uporabniškim imenom", - "iam_binddn": "Povezava DN", + "iam_binddn": "Vezava DN", "iam_use_ssl": "Uporabi SSL", "iam_use_tls": "Uporabi StartTLS", "iam_version": "Različica", @@ -412,9 +415,9 @@ "needs_restart": "potreben je ponovni zagon" }, "danger": { - "alias_goto_identical": "Alias in goto naslov morata biti identična", - "aliasd_targetd_identical": "Alias domena ne sme biti enaka ciljni domeni: %s", - "bcc_exists": "BCC preslikava obstaja za vrsto %s", + "alias_goto_identical": "Vzdevek in ciljni naslov se ne smeta ujemati", + "aliasd_targetd_identical": "Vzdevek domene ne sme biti enak ciljni domeni: %s", + "bcc_exists": "Za tip %s obstaja BCC preslikava %s", "dkim_domain_or_sel_exists": "DKIM ključ za \"%s\" obstaja in ne bo prepisan", "domain_quota_m_in_use": "Kvota domene mora biti večja ali enaka %s MiB", "extra_acl_invalid_domain": "Zunanji pošiljatelj \"%s\" uporablja neveljavno domeno", @@ -423,71 +426,71 @@ "invalid_nexthop": "Oblika naslednjega skoka ni veljavna", "invalid_nexthop_authenticated": "Naslednji skok obstaja z drugačnimi poverilnicami. Prosim najprej posodobite obstoječe poverilnice za ta naslednji skok.", "demo_mode_enabled": "Demo način je omogočen", - "access_denied": "Dostop zavrnjen ali pa so podatki obrazca napačni", - "alias_domain_invalid": "Alias domena %s ni veljavna", - "alias_empty": "Alias naslov ne sme biti prazen", - "alias_invalid": "Alias naslov %s ni veljaven", - "aliases_in_use": "Max. aliasov mora biti večje ali enako %d", - "app_name_empty": "Naziv aplikacije ne more biti prazno", - "app_passwd_id_invalid": "ID gesla aplikacije %s je neveljaven", - "bcc_empty": "BCC cilj ne more biti prazen", - "bcc_must_be_email": "BCC cilj %s ni veljaven e-poštni naslov", + "access_denied": "Dostop zavrnjen ali neveljavni podatki obrazca", + "alias_domain_invalid": "Vzdevek domene %s ni veljaven", + "alias_empty": "Naslov vzdevka ne sme biti prazen", + "alias_invalid": "Naslov vzdevka %s ni veljaven", + "aliases_in_use": "Največje število vzdevkov mora biti večje ali enako %d", + "app_name_empty": "Ime aplikacije ne sme biti prazno", + "app_passwd_id_invalid": "ID gesla za aplikacijo %s neveljaven", + "bcc_empty": "Polje za prejemnika BCC ne sme biti prazno", + "bcc_must_be_email": "Cilj BCC %s ni veljaven e-poštni naslov", "comment_too_long": "Komentar je predolg, dovoljeno je največ 160 znakov", "defquota_empty": "Privzeta kvota na poštni predal ne more biti 0.", - "description_invalid": "Opis resursa za %s ni veljaven", + "description_invalid": "Opis vira za %s je neveljaven", "dkim_domain_or_sel_invalid": "Domena ali izbirnik DKIM ni veljaven: %s", "domain_cannot_match_hostname": "Domena se ne more ujemati z imenom gostitelja", "domain_exists": "Domena %s že obstaja", - "domain_invalid": "Manjka ali napačno ime domene", - "domain_not_empty": "Ne morem odstraniti ne-prazno domeno %s", + "domain_invalid": "Ime domene je prazno ali neveljavno", + "domain_not_empty": "Neprazne domene %s ni mogoče odstraniti", "domain_not_found": "Domene %s ni bilo mogoče najti", "extended_sender_acl_denied": "manjka ACL za določitev naslovov zunanjih pošiljateljev", "extra_acl_invalid": "Naslov zunanjega pošiljatelja \"%s\" ni veljaven", "fido2_verification_failed": "Preverjanje FIDO2 ni uspelo: %s", - "file_open_error": "Datoteka ne more biti odprta za urejanje", + "file_open_error": "Datoteke ni mogoče odpreti za pisanje", "filter_type": "Napačna vrsta filtra", - "from_invalid": "Pošiljatelj ne sme biti prazno", + "from_invalid": "Polje za pošiljatelja ne sme biti prazno", "global_filter_write_error": "Ni mogoče zapisati datoteke filtra: %s", "global_map_invalid": "ID globalne preslikave %s ni veljaven", - "goto_empty": "Alias naslov mora vsebovati vsaj en veljaven goto naslov", - "goto_invalid": "Goto naslov %s ni veljaven", + "goto_empty": "Naslov vzdevka mora vsebovati vsaj en veljaven ciljni naslov", + "goto_invalid": "Ciljni naslov %s ni veljaven", "ham_learn_error": "Napaka pri učenju Ham: %s", - "imagick_exception": "Napaka: Imagick napaka pri branju slike", + "imagick_exception": "Napaka: Izjema Imagick med branjem slike", "img_invalid": "Ni možno preveriti slikovne datoteke", "invalid_bcc_map_type": "Neveljavna vrsta preslikave BCC", "invalid_destination": "Ciljna oblika \"%s\" ni veljavna", "invalid_filter_type": "Neveljavna vrsta filtra", "invalid_host": "Naveden je neveljaven gostitelj (host): %s", - "invalid_mime_type": "Neveljaven mime type", + "invalid_mime_type": "Neveljavna vrsta MIME", "max_quota_in_use": "Kvota poštnega predala mora biti večja ali enaka %d MB", "password_complexity": "Geslo ne ustreza varnostni politiki", - "pushover_credentials_missing": "Manjka Pushover token ali ključ", + "pushover_credentials_missing": "Manjka žeton in/ali ključ Pushover", "release_send_failed": "Sporočila ni bilo mogoče sprostiti: %s", - "tls_policy_map_dest_invalid": "Cilj politike ni veljaven", + "tls_policy_map_dest_invalid": "Cilj pravilnika je neveljaven", "webauthn_authenticator_failed": "Izbrani avtentikator ni bil najden", "reset_f2b_regex": "Regex filter ni bilo možno ponastaviti v ustreznem času. Prosim poskusite ponovno ali počakajte nekaj sekund in ponovno naložite stran.", "target_domain_invalid": "Ciljna domena %s ni veljavna", - "validity_missing": "Prosim nastavite obdobje veljavnosti", + "validity_missing": "Prosim določite obdobje veljavnosti", "invalid_recipient_map_old": "Naveden neveljaven izvirni prejemnik: %s", - "ip_list_empty": "Seznam dovoljenih IPjev ne sme biti prazen", - "is_alias": "%s je že znan kot alias naslov", - "is_alias_or_mailbox": "%s je že znan kot alias, poštni naslov, ali alias izveden iz alias domene.", - "is_spam_alias": "%s že obstaja kot začasen alias (spam alias naslov)", + "ip_list_empty": "Seznam dovoljenih IP-jev ne sme biti prazen", + "is_alias": "%s je že znan kot naslov vzdevka", + "is_alias_or_mailbox": "%s je že znan kot vzdevek, poštni predal ali naslov vzdevka, razširjen iz vzdevka domene.", + "is_spam_alias": "%s je že znan kot začasni vzdevek (neželeni vzdevek)", "last_key": "Zadnji ključ ne more biti izbrisan, prosim raje deaktivirajte dvofaktorsko avtentikacijo (TFA).", "login_failed": "Prijava ni uspela", "mailbox_defquota_exceeds_mailbox_maxquota": "Privzeta kvota presega najvišjo omejitev", "mailbox_invalid": "Ime poštnega predala ni veljavno", - "mailbox_quota_exceeded": "Kvota presega omejitev domene (maksimalno %d MB)", + "mailbox_quota_exceeded": "Kvota presega omejitev domene (največ %d MB)", "mailbox_quota_exceeds_domain_quota": "Najvišja kvota presega omejitev domene", "mailbox_quota_left_exceeded": "Ni dovolj prostora (preostali prostor: %d MB)", "mailboxes_in_use": "Največje število poštnih predalov mora biti večje ali enako %d", "malformed_username": "Nepravilno oblikovano uporabniško ime", "map_content_empty": "Preslikava vsebine ne more biti prazna", - "max_alias_exceeded": "Preseženo največje število aliasov", + "max_alias_exceeded": "Preseženo največje število vzdevkov", "max_mailbox_exceeded": "Preseženo največje število poštnih predalov (%d od %d)", "maxquota_empty": "Največja kvota na poštni predal ne more biti 0.", "mysql_error": "Napaka MySQL: %s", - "network_host_invalid": "Nepravilno omrežje ali gostitel: %s", + "network_host_invalid": "Nepravilno omrežje ali gostitelj: %s", "next_hop_interferes": "% moti naslednji skok %s", "next_hop_interferes_any": "Obstoječi naslednji skok moti %s", "nginx_reload_failed": "Ponovni zagon Nginx ni uspel: %s", @@ -500,30 +503,30 @@ "policy_list_from_invalid": "Zapis ima nepravilno obliko", "private_key_error": "Napaka zasebnega ključa: %s", "pushover_key": "Pushover ključ ni v pravilni obliki", - "pushover_token": "Pushover token ni v pravilni obliki", - "quota_not_0_not_numeric": "Quota mora biti število in večje ali enako 0", + "pushover_token": "Pushover žeton ni v pravilni obliki", + "quota_not_0_not_numeric": "Kvota mora biti numerična in >= 0", "recipient_map_entry_exists": "Preslikava prejemnika \"%s\" že obstaja", "redis_error": "Napaka Redis: %s", "relayhost_invalid": "Vnos preslikave %s ni pravilen", - "resource_invalid": "Ime vira je neveljavno", - "rl_timeframe": "Časovni okvir za rate limit je nepravilen", + "resource_invalid": "Ime vira %s je neveljavno", + "rl_timeframe": "Časovni okvir omejitve je nepravilen", "rspamd_ui_pw_length": "Rspamd UI geslo mora biti dolgo vsaj 6 znakov", - "script_empty": "Script ne more biti prazen", + "script_empty": "Skripta ne sme biti prazna", "sender_acl_invalid": "Vrednost ACL pošiljatelja %s ni veljavna", "set_acl_failed": "Ni uspelo nastaviti ACL", "settings_map_invalid": "ID preslikave nastavitev %s ni veljaven", - "sieve_error": "Napaka Sieve parserja: %s", - "spam_learn_error": "Napaka pri učenju spama: %s", - "subject_empty": "Predmet ne sme biti prazno", + "sieve_error": "Napaka Sieve razčlenjevalnika: %s", + "spam_learn_error": "Napaka pri učenju neželene pošte: %s", + "subject_empty": "Zadeva ne sme biti prazna", "targetd_not_found": "Ciljna domena %s ni bila najdena", - "targetd_relay_domain": "Ciljna domena %s je relay domena", + "targetd_relay_domain": "Ciljna domena %s je posredovalna domena", "template_exists": "Predloga %s že obstaja", "template_id_invalid": "ID predloge %s ni veljaven", "template_name_invalid": "Ime predloge ni veljavno", "text_empty": "Besedilo ne sme biti prazno", - "tfa_token_invalid": "Neveljaven token TFA", - "tls_policy_map_entry_exists": "Vpis preslikave TLS \"%s\" že obstaja", - "tls_policy_map_parameter_invalid": "Parameter politike ni pravilen", + "tfa_token_invalid": "Neveljaven TFA žeton", + "tls_policy_map_entry_exists": "Vnos pravilnika preslikave TLS \"%s\" obstaja", + "tls_policy_map_parameter_invalid": "Parameter pravilnika je neveljaven", "totp_verification_failed": "Neuspešno preverjanje TOTP", "transport_dest_exists": "Cilj transporta \"%s\" že obstaja", "webauthn_verification_failed": "Preverjanje WebAuthn ni uspelo: %s", @@ -554,10 +557,12 @@ "max_age_invalid": "Najvišja starost %s je neveljavna", "mode_invalid": "Način %s ni veljaven", "mx_invalid": "Zapis MX %s je neveljaven", - "version_invalid": "Različica %s je neveljavna" + "version_invalid": "Različica %s je neveljavna", + "tfa_removal_blocked": "Dvofaktorske avtentikacije ni mogoče odstraniti, ker je obvezna za vaš račun.", + "quarantine_category_invalid": "Kategorija karantene mora biti ena od: add_header, reject, all" }, "debug": { - "containers_info": "Informacije o vsebniku (containerju)", + "containers_info": "Informacije o zabojniku", "architecture": "Arhitektura", "chart_this_server": "Diagram (ta strežnik)", "container_running": "Aktiven", @@ -571,23 +576,23 @@ "external_logs": "Zunanji dnevniki", "last_modified": "Nazadnje spremenjeno", "history_all_servers": "Zgodovina (vsi strežniki)", - "in_memory_logs": "In-memory dnevniki", - "service": "Servis", + "in_memory_logs": "Dnevniki v pomnilniku", + "service": "Storitev", "show_ip": "Prikaži javni IP", "size": "Velikost", "started_at": "Zagnano ob", "started_on": "Zagnano na", "static_logs": "Statični dnevniki", "success": "Uspešno", - "system_containers": "Sistem in Containerji", + "system_containers": "Sistem in zabojniki", "timezone": "Časovni pas", "uptime": "Čas delovanja", "update_available": "Posodobitev je na voljo", "no_update_available": "Sistem je na najnovejši verziji", "update_failed": "Ni mogoče preveriti za posodobitve", "username": "Uporabniško ime", - "wip": "Trenutno v delu", - "log_info": "

    mailcow in-memory dnevniki se zbirajo v Redis seznamih in se vsako minuto omejijo na LOG_LINES (%d) da se zmanjša obremenitev.\n
    In-memory dnevniki niso namenjeni trajnemu shranjevanju. Vse aplikacije, ki beležijo dnevnike in-memory, tudi beležijo v Docker daemon in posledično v privzeti gonilnik za dnevnik.\n
    In-memory dnevniki se naj uporabljajo za odpravljanje manjših napak s containerji.

    \n

    Eksterni dnevniki se zbirajo preko API-ja posamezne aplikacije.

    \n

    Statični dnevniki so večinoma dnevniki aktivnosti, ki se ne beležijo v Dockerd, a jih je vseeno treba hraniti (razen API dnevnikov).

    ", + "wip": "Trenutno delo v teku", + "log_info": "

    Dnevniki v pomnilniku mailcow se zbirajo na seznamih Redis in vsako minuto skrajšajo na LOG_LINES (%d), da se zmanjša preobremenitev.\n
    Dnevniki v pomnilniku niso namenjeni trajnemu beleženju. Vse aplikacije, ki se beležijo v pomnilnik, se beležijo tudi v Dockerjev demon in s tem v privzeti gonilnik beleženja.

    \n

    Vrsta dnevnika v pomnilniku se mora uporabljati za odpravljanje manjših težav s kontejnerji.

    \n

    Zunanji dnevniki se zbirajo prek API-ja dane aplikacije.

    \n

    Statični dnevniki so večinoma dnevniki dejavnosti, ki se ne beležijo v Dockerd, vendar morajo biti še vedno trajni (razen dnevnikov API-ja).

    ", "login_time": "Čas", "logs": "Dnevniki", "memory": "Spomin", @@ -598,7 +603,7 @@ "infoFiltered": "(filtrirano od _MAX_ skupaj zapisov)", "collapse_all": "Strni vse", "decimal": ",", - "emptyTable": "Ni podatkov", + "emptyTable": "V tabeli ni na voljo podatkov", "expand_all": "Razširi vse", "info": "Prikazano _START_ do _END_ od _TOTAL_ zapisov", "infoEmpty": "Prikazano 0 do 0 od 0 zapisov", @@ -620,9 +625,9 @@ } }, "diagnostics": { - "cname_from_a": "Vrednost pridobljena iz A/AAAA zapisa. To je podprto, če zapis kaže na pravilen resurs.", + "cname_from_a": "Vrednost, izpeljana iz zapisa A/AAAA. To je podprto, če zapis kaže na pravilen vir.", "dns_records": "DNS zapisi", - "dns_records_24hours": "Prosim upoštevajte, da lahko traja do 24 ur da se spremembe v DNS pravilno prikažejo na tej strani. Namen je da lahko enostavno vidite, kako konfigurirati svoje DNS zapise in preverite ali so vaši zapisi pravilno shranjeni v DNS.", + "dns_records_24hours": "Upoštevajte, da se lahko spremembe DNS-a pravilno odražajo na tej strani v 24 urah. Namenjena je temu, da si preprosto ogledate, kako konfigurirati zapise DNS, in preverite, ali so vsi vaši zapisi pravilno shranjeni v DNS-u.", "dns_records_data": "Pravilni podatki", "dns_records_docs": "Prosim preverite tudi dokumentacijo.", "dns_records_name": "Ime", @@ -633,60 +638,60 @@ "edit": { "acl": "ACL (Dovoljenje)", "active": "Aktivno", - "allow_from_smtp": "Dovoli samo tem IP naslovom da uporabijo SMTP", - "bcc_dest_format": "Cilj BCC mora biti en veljaven email naslov.
    Če morate poslati kopijo na več naslovov, ustvarite alias in ga uporabite tukaj.", - "automap": "Poskušaj samodejno preslikati mape (\"Sent items\", \"Sent\" => \"Poslano\" ipd.)", + "allow_from_smtp": "Dovoli samo tem IP naslovom uporabo SMTP", + "bcc_dest_format": "Ciljna stran za polje SKP (BCC) mora biti en veljaven e-poštni naslov.
    Če morate kopijo poslati na več naslovov, ustvarite vzdevek in ga uporabite tukaj.", + "automap": "Poskusite samodejno preslikati mape (\"Poslani predmeti\", \"Poslano\" => \"Poslano\" itd.)", "admin": "Uredi skrbnika", "domain_footer_info_vars": { - "custom": "{= foo =} - Če ima poštni predal atribut po meri \"foo\" z vrednostjo \"bar\", spremenljivka vrne \"bar\"", - "auth_user": "{= auth_user =} - Prijavljeno uporabniško ime, ki ga določi MTA", - "from_user": "{= from_user =} - leva stran email naslova uporabnika, npr. za \"moo@mailcow.tld\" vrne \"moo\"", - "from_name": "{= from_name =} - Prikazno ime, npr. za \"Mailcow <moo@mailcow.tld>\" vrne \"Mailcow\"", - "from_addr": "{= from_addr =} - e-poštni naslov \"Od\"", - "from_domain": "{= from_domain =} - domena e-poštnega naslova \"Od\"" + "custom": "{= foo =} - Če ima poštni predal atribut po meri \"foo\" z vrednostjo \"bar\", vrne \"bar\"", + "auth_user": "{= auth_user =} - Preverjeno uporabniško ime, ki ga določi MTA", + "from_user": "{= from_user =} - Iz uporabniškega dela ovojnice, npr. za \"moo@mailcow.tld\" vrne \"moo\"", + "from_name": "{= from_name =} - Iz imena ovojnice, npr. za \"Mailcow <moo@mailcow.tld>\" vrne \"Mailcow\"", + "from_addr": "{= from_addr =} - Del ovojnice z naslovom od", + "from_domain": "{= from_domain =} - Iz domenskega dela ovojnice" }, - "dont_check_sender_acl": "Onemogoči kontrolo pošiljatelja za domeno %s (+ alias domene)", + "dont_check_sender_acl": "Onemogoči preverjanje pošiljatelja za domeno %s (+ vzdevki domen)", "pushover_title": "Naslov obvestila", "domains": "Domene", - "extended_sender_acl_info": "Če je DKIM domenski ključ na voljo, ga uvozite.
    \n Ne pozabite dodati ta strežnik k ustreznemu SPF TXT zapisu.
    \n Kadar koli je domena ali alias domena dodana k tem strežniku, ki se prekriva z zunanjim naslovom, je zunanji naslov odstranjen.
    \n uporabite @domain.tld da dovolite pošiljanje kot *@domain.tld.", - "lookup_mx": "Cilj je regular expression za ujemanje MX zapisov (.*\\.google\\.com za usmeritev vse pošte na MX, ki se konča z google.com, preko tega skoka)", - "maxbytespersecond": "Največ bytov na sekundo
    (0 = neomejeno)", + "extended_sender_acl_info": "Uvoziti je treba ključ domene DKIM, če je na voljo.
    \n Ne pozabite dodati tega strežnika v ustrezni zapis SPF TXT.
    \n Kadar koli je temu strežniku dodana domena ali vzdevek domene, ki se prekriva z zunanjim naslovom, se zunanji naslov odstrani.
    \n Uporabite @domain.tld, da omogočite pošiljanje kot *@domain.tld.", + "lookup_mx": "Cilj je regularni izraz, ki se ujema z imenom MX (.*\\.google\\.com za usmerjanje vse pošte, usmerjene na MX, ki se konča na google.com, prek tega skoka)", + "maxbytespersecond": "Največ bajtov na sekundo
    (0 = neomejeno)", "pushover_sender_array": "Upoštevaj samo sledeče e-poštne naslove pošiljateljev (ločeni z vejico)", - "mbox_rl_info": "Ta omejitev velja za SASL uporabniško ime, preverja se ujemanje s katerim koli \"from\" naslovom, ki ga uporablja prijavljeni uporabnik. Omejitev pošiljanja za poštni predal preglasi pravilo omejitve za domeno.", + "mbox_rl_info": "Ta omejitev se uporabi za prijavno ime SASL in se ujema z naslovom \"od\", ki ga uporablja prijavljeni uporabnik. Omejitev poštnega nabiralnika preglasi omejitev za celotno domeno.", "kind": "Tip", "client_secret": "Skrivnost odjemalca", "comment_info": "Zasebni komentar ni viden uporabniku, javni komentar pa se prikaže kot opis orodja, ko nanj v pregledu uporabnika zadržite miško", "created_on": "Ustvarjeno", "custom_attributes": "Atributi po meri", - "delete1": "Izbriši na viru, ko je končano", - "delete2": "Izbriši sporočila na cilju, ki ne obstajajo na viru", + "delete1": "Izbriši iz vira, ko je končano", + "delete2": "Izbriši sporočila na cilju, ki niso na izvoru", "delete2duplicates": "Izbriši dvojnike na cilju", "delete_ays": "Prosim potrdite proces izbrisa.", "description": "Opis", "disable_login": "Onemogoči prijavo (dohodna pošta je še vedno sprejeta)", "domain": "Uredi domeno", - "domain_admin": "Uredi domenskega skrbnika", + "domain_admin": "Uredi skrbnika domene", "domain_footer": "Noga za celo domeno", "domain_footer_html": "HTML noga", - "pushover_vars": "Če ni definiran noben filter pošiljatelja, bodo upoštevana vsa sporočila.
    Regex filtre in natančna preverjanja pošiljateljev je mogoče definirati posamezno in bodo obravnavani v nadaljevanju. Niso odvisni drug od drugega.
    Uporabne spremenljivke za besedilo in naslov (prosimo, upoštevajte politike varstva podatkov)", + "pushover_vars": "Če filter pošiljatelja ni definiran, bodo upoštevana vsa e-poštna sporočila.
    Filtre regularnih izrazov in natančna preverjanja pošiljateljev je mogoče definirati posamično in bodo obravnavana zaporedno. Niso odvisna drug od drugega.
    Uporabne spremenljivke za besedilo in naslov (upoštevajte pravilnike o varstvu podatkov)", "pushover_verify": "Preveri poverilnice", "quota_mb": "Omejitev (MiB)", - "quota_warning_bcc": "BCC za sporočilo z opozorilom omejitve", + "quota_warning_bcc": "Opozorilo o kvoti BCC", "quota_warning_bcc_info": "Opozorila bodo poslana kot ločene kopije naslednjim prejemnikom. Zadevi bo v oklepaju dodano ustrezno uporabniško ime, na primer: Opozorilo o kvoti (uporabnik@example.com).", "ratelimit": "Omejitev pošiljanja", "advanced_settings": "Napredne nastavitve", - "allow_from_smtp_info": "Pustite prazno da dovolite vse pošiljatelje.
    IPv4/IPv6 naslovi in omrežja.", + "allow_from_smtp_info": "Pustite prazno, da dovolite vse pošiljatelje.
    Naslovi in omrežja IPv4/IPv6.", "allowed_protocols": "Dovoljeni protokoli za neposreden dostop uporabnikov (ne vpliva na protokole za gesla aplikacij)", "app_name": "Ime aplikacije", "app_passwd": "Geslo aplikacije", "app_passwd_protocols": "Dovoljeni protokoli za geslo aplikacije", - "backup_mx_options": "Možnosti posredovanja (relay)", - "client_id": "Client ID", - "domain_footer_info": "Noge za celo domeno so dodane k vsem izhodnim e-poštnim sporočilom v tej domeni.
    V nogi se lahko uporabijo sledeče spremenljivke:", - "domain_footer_plain": "PLAIN noga", - "domain_footer_skip_replies": "Ne dodajaj noge v odgovorih na e-poštna sporočila", - "domain_quota": "Omejitev (kvota) domene", - "edit_alias_domain": "Uredi alias domeno", + "backup_mx_options": "Možnosti posredovanja", + "client_id": "ID odjemalca", + "domain_footer_info": "Noge za celotno domeno so dodane vsem odhodnim e-poštnim sporočilom, povezanim z naslovom znotraj te domene.
    Za nogo se lahko uporabijo naslednje spremenljivke:", + "domain_footer_plain": "NAVADNA noga", + "domain_footer_skip_replies": "Prezri nogo v odgovorih na e-poštna sporočila", + "domain_quota": "Kvota domene", + "edit_alias_domain": "Uredi vzdevek domene", "exclude": "Izključi objekte (regex)", "extended_sender_acl": "Naslovi zunanjih pošiljateljev", "force_pw_update": "Obvezna zamenjava gesla ob naslednji prijavi", @@ -695,18 +700,18 @@ "full_name": "Polno ime", "gal": "Globalni seznam naslovov (GAL)", "gal_info": "GAL vsebuje vse objekte v domeni in jih uporabniki ne morejo urejati. Če je onemogočeno, ni podatkov o o zasedenosti objekta! Ponovno zaženite SOGo za uveljavitev sprememb.", - "generate": "generiraj", + "generate": "ustvari", "grant_types": "Vrste dovoljenj", "hostname": "Ime gostitelja", "inactive": "Neaktivno", "last_modified": "Nazadnje spremenjeno", "mailbox": "Uredi poštni predal", - "mailbox_quota_def": "Privzeta omejitev/kvota za poštni predal", - "mailbox_relayhost_info": "Velja samo za poštni predal in neposredne aliase. Ne prepiše domenskega relay gostitelja.", - "max_aliases": "Največ aliasov", - "max_mailboxes": "Največ možnih poštnih predalov", - "max_quota": "Največja omejitev/kvota na poštni predal (MiB)", - "maxage": "Največja starost sporočil (v dnevih), po katerih bo poizvedeno iz oddaljenega vira
    (0 = ne omejuj)", + "mailbox_quota_def": "Privzeta kvota nabiralnika", + "mailbox_relayhost_info": "Uporablja se samo za poštni nabiralnik in neposredne vzdevke, preglasi gostitelja posredovalne domene.", + "max_aliases": "Največje število vzdevkov", + "max_mailboxes": "Največje možno število poštnih predalov", + "max_quota": "Največja kvota na poštni predal (MiB)", + "maxage": "Najvišja starost sporočil v dnevih, ki bodo prebrana z oddaljenega strežnika
    (0 = prezri starost)", "mins_interval": "Interval (min)", "multiple_bookings": "Več rezervacij", "none_inherit": "Brez / podeduj", @@ -724,7 +729,7 @@ "pushover_text": "Besedilo obvestila", "pushover_sound": "Zvok", "encryption": "Šifriranje", - "alias": "Uredi alias", + "alias": "Uredi vzdevek", "relayhost": "Prenosi, odvisni od pošiljatelja", "mailbox_rename_alias": "Samodejno ustvari vzdevek", "sender_acl_info": "Če lahko uporabnik poštnega predala A pošilja kot uporabnik poštnega predala B, se naslov pošiljatelja v SOGo ne prikaže samodejno kot izbirno polje \"od\".
    \n Uporabnik poštnega predala B mora v SOGo ustvariti pooblastilo, da lahko uporabnik poštnega predala A izbere svoj naslov kot pošiljatelja. Če želite pooblastiti poštni predal v SOGo, uporabite meni (tri pike) desno od imena vašega poštnega predala v zgornjem levem kotu v pogledu pošte. To vedenje ne velja za vzdevke.", @@ -747,7 +752,7 @@ "sogo_access": "Neposredno posredovanje na SOGo", "sogo_access_info": "Po prijavi je uporabnik samodejno preusmerjen na SOGo.", "sogo_visible": "Vzdevek je viden v SOGo", - "sogo_visible_info": "Ta možnost vpliva samo na objekte, ki jih je mogoče prikazati v SOGo (naslovi aliasov v skupni rabi ali brez nje, ki kažejo na vsaj en lokalni poštni predal). Če je skrita, vzdevek ne bo prikazan kot izbirni pošiljatelj v SOGo.", + "sogo_visible_info": "Ta možnost vpliva samo na objekte, ki jih je mogoče prikazati v SOGo (naslovi vzdevkov v skupni rabi ali brez nje, ki kažejo na vsaj en lokalni poštni predal). Če je skrita, vzdevek ne bo prikazan kot izbirni pošiljatelj v SOGo.", "spam_alias": "Ustvarjanje ali spreminjanje časovno omejenih vzdevkovnih naslovov", "spam_filter": "Filter neželene pošte", "spam_policy": "Dodajanje ali odstranjevanje elementov na seznam dovoljenih/zavrnjenih", @@ -773,13 +778,17 @@ "mta_sts_mode": "Način", "mta_sts_mode_info": "Na voljo so trije načini:
    • testiranje – pravilnik se samo spremlja, kršitve nimajo vpliva.
    • uveljavljanje – pravilnik se strogo uveljavlja, povezave brez veljavnega TLS so zavrnjene.
    • brez – pravilnik je objavljen, vendar se ne uporablja.
    ", "mta_sts_max_age": "Najvišja starost", - "mta_sts_max_age_info": "Čas v sekundah, ki ga lahko prejemni poštni strežniki shranijo v predpomnilnik, dokler se ne ponovno naloži.", + "mta_sts_max_age_info": "Čas v sekundah, ki ga lahko prejemni poštni strežniki shranijo v predpomnilnik, dokler se ne naloži ponovno.", "mta_sts_mx": "MX strežnik", "mta_sts_mx_info": "Omogoča pošiljanje samo na izrecno navedena imena gostiteljskih strežnikov poštnih strežnikov; pošiljajoči MTA preveri, ali se ime gostitelja DNS MX ujema s seznamom pravilnikov, in dovoljuje dostavo le z veljavnim potrdilom TLS (zaščita pred MITM).", - "mta_sts_mx_notice": "Določiti je mogoče več strežnikov MX (ločenih z vejicami)." + "mta_sts_mx_notice": "Določiti je mogoče več strežnikov MX (ločenih z vejicami).", + "internal": "Notranje", + "internal_info": "Notranji vzdevki so dostopni samo iz lastne domene ali vzdevkov domen.", + "sender_allowed": "Dovoli pošiljanje kot ta vzdevek", + "sender_allowed_info": "Če je onemogočeno, lahko ta vzdevek samo prejema pošto. Za preglasitev in dodelitev dovoljenja za pošiljanje določenim poštnim predalom uporabite seznam za nadzor dostopa pošiljatelja." }, "footer": { - "restart_container_info": "Pomembno: Ugoden ponovni zagon lahko traja nekaj časa, zato počakajte, da se konča.", + "restart_container_info": "Pomembno: Eleganten ponovni zagon lahko traja nekaj časa, zato počakajte, da se konča.", "delete_these_items": "Prosimo, potrdite spremembe naslednjega ID-ja objekta", "confirm_delete": "Potrdi brisanje", "delete_now": "Izbriši zdaj", @@ -991,7 +1000,9 @@ "yes": "✓", "weekly": "Tedensko", "sieve_info": "Na uporabnika lahko shranite več filtrov, vendar je lahko hkrati aktiven le en predfilter in en postfilter.
    \nVsak filter bo obdelan v opisanem vrstnem redu. Niti neuspešen skript niti izdan ukaz »keep;« ne bosta ustavila obdelave nadaljnjih skript. Spremembe globalnih skriptov sita bodo sprožile ponovni zagon Dovecota.

    Globalni predfilter sita • Predfilter • Uporabniški skripti • Postfilter • Globalni postfilter sita", - "tls_policy_maps_info": "Ta preslikava pravilnikov preglasi pravila odhodnega prenosa TLS neodvisno od uporabnikovih nastavitev pravilnikov TLS.
    \n Za več informacij preverite dokumentacijo »smtp_tls_policy_maps«." + "tls_policy_maps_info": "Ta preslikava pravilnikov preglasi pravila odhodnega prenosa TLS neodvisno od uporabnikovih nastavitev pravilnikov TLS.
    \n Za več informacij preverite dokumentacijo »smtp_tls_policy_maps«.", + "internal": "Notranje", + "force_tfa": "TFA" }, "fido2": { "known_ids": "Znani ID-ji", @@ -1224,7 +1235,12 @@ "u2f_deprecated_important": "Prosimo, registrirajte svoj ključ v skrbniški plošči z novo metodo WebAuthn.", "waiting_usb_auth": "Čakanje na napravo USB ...

    Zdaj se dotaknite gumba na napravi USB.", "waiting_usb_register": "Čakanje na napravo USB ...

    Vnesite svoje geslo zgoraj in potrdite registracijo tako, da tapnete gumb na napravi USB.", - "yubi_otp": "Avtentikacija z enkratnim geslom Yubico" + "yubi_otp": "Avtentikacija z enkratnim geslom Yubico", + "force_tfa": "Vsiljena registracija 2FA ob prijavi", + "force_tfa_info": "Uporabnik bo moral pred dostopom do nadzorne plošče nastaviti dvofaktorsko overjanje.", + "setup_title": "Zahtevana je dvofaktorska avtentikacija", + "setup_required": "Vaš račun zahteva dvofaktorsko overjanje. Za nadaljevanje nastavite metodo dvofaktorske overitve.", + "cancel_setup": "Prekliči in se odjavi" }, "ratelimit": { "disabled": "Onemogočeno", @@ -1354,7 +1370,7 @@ "sogo_profile_reset": "Ponastavi profil SOGo", "sogo_profile_reset_help": "S tem boste uničili uporabnikov profil SOGo in nepovratno izbrisali vse stike in podatke koledarja.", "sogo_profile_reset_now": "Ponastavi profil zdaj", - "spam_aliases": "Začasni vzdevki e-pošte", + "spam_aliases": "Vzdevki neželene e-pošte", "spam_score_reset": "Ponastavi na privzete nastavitve strežnika", "spamfilter": "Filter neželene pošte", "spamfilter_behavior": "Ocena", @@ -1362,7 +1378,7 @@ "spamfilter_default_score": "Privzete vrednosti", "spamfilter_green": "Zelena: to sporočilo ni neželena pošta", "spamfilter_hint": "Prva vrednost opisuje »nizko oceno neželene pošte«, druga pa »visoko oceno neželene pošte«.", - "spamfilter_red": "Rdeča: To sporočilo je neželena pošta in ga bo strežnik zavrnil.", + "spamfilter_red": "Rdeča: To sporočilo je neželena pošta in ga bo strežnik zavrnil", "spamfilter_table_action": "Dejanje", "spamfilter_table_add": "Dodaj element", "spamfilter_table_domain_policy": "ni na voljo (pravilnik domene)", @@ -1402,12 +1418,16 @@ "years": "leta", "waiting": "Čakanje", "q_all": "Vse kategorije", - "syncjob_EX_OK": "Uspeh" + "syncjob_EX_OK": "Uspeh", + "expire_never": "Nikoli ne poteče", + "forever": "Za vedno", + "spam_aliases_info": "Vzdevek za neželeno pošto je začasni e-poštni naslov, ki ga je mogoče uporabiti za zaščito pravih e-poštnih naslovov.
    Po želji je mogoče nastaviti čas poteka veljavnosti, tako da se vzdevek po določenem obdobju samodejno deaktivira, s čimer se učinkovito znebite zlorabljenih ali razkritih naslovov.", + "pw_update_required": "Za vaš račun je potrebna sprememba gesla. Za nadaljevanje nastavite novo geslo." }, "warning": { "cannot_delete_self": "Prijavljenega uporabnika ni mogoče izbrisati", "domain_added_sogo_failed": "Domena je bila dodana, vendar ponovni zagon SOGo ni uspel. Preverite dnevnike strežnika.", - "dovecot_restart_failed": "Dovecota ni uspelo znova zagnati, preverite dnevnike.", + "dovecot_restart_failed": "Dovecota ni uspelo znova zagnati, preverite dnevnike", "fuzzy_learn_error": "Napaka učenja mehkega zgoščevanja: %s", "hash_not_found": "Zgoščena vrednost ni bila najdena ali je bila že izbrisana", "ip_invalid": "Preskočen neveljaven IP: %s", diff --git a/data/web/lang/lang.uz-uz.json b/data/web/lang/lang.uz-uz.json new file mode 100644 index 000000000..a8d2ecf82 --- /dev/null +++ b/data/web/lang/lang.uz-uz.json @@ -0,0 +1,1440 @@ +{ + "acl": { + "alias_domains": "Taxallus domenlarini qoʻshish", + "app_passwds": "Ilova parollarini boshqarish", + "bcc_maps": "BCC xaritalari", + "delimiter_action": "Ajratuvchi amali", + "domain_desc": "Domen tavsifini oʻzgartirish", + "domain_relayhost": "Domen uchun relay xostni oʻzgartirish", + "eas_reset": "EAS qurilmalarini tiklash", + "extend_sender_acl": "Tashqi manzillar yordamida joʻnatuvchi ACL ni kengaytirishga ruxsat berish", + "filters": "Filtrlar", + "login_as": "Pochta qutisi foydalanuvchisi sifatida kirish", + "mailbox_relayhost": "Pochta qutisi uchun relay xostni oʻzgartirish", + "prohibited": "ACL tomonidan taqiqlangan", + "protocol_access": "Protokolga kirishni oʻzgartirish", + "pushover": "Pushover", + "pw_reset": "mailcow foydalanuvchi parolini tiklashga ruxsat berish", + "quarantine": "Karantin amallari", + "quarantine_attachments": "Karantindagi ilovalar", + "quarantine_category": "Karantin bildirishnoma turkumini oʻzgartirish", + "quarantine_notification": "Karantin bildirishnomalarini oʻzgartirish", + "ratelimit": "Tezlik chegarasi", + "recipient_maps": "Qabul qiluvchi xaritalari", + "smtp_ip_access": "SMTP uchun ruxsat etilgan xostlarni oʻzgartirish", + "sogo_access": "SOGo kirishini boshqarishga ruxsat berish", + "sogo_profile_reset": "SOGo profilini tiklash", + "spam_alias": "Vaqtinchalik taxalluslar", + "spam_policy": "Qora roʻyxat/Oq roʻyxat", + "spam_score": "Spam bahosi", + "syncjobs": "Sinxronizatsiya vazifalari", + "tls_policy": "TLS siyosati", + "unlimited_quota": "Pochta qutilari uchun cheksiz kvota" + }, + "add": { + "activate_filter_warn": "Faol belgilansa, boshqa barcha filtrlar oʻchirib qoʻyiladi.", + "active": "Faol", + "add": "Qoʻshish", + "add_domain_only": "Faqat domen qoʻshish", + "add_domain_restart": "Domen qoʻshish va SOGoni qayta ishga tushirish", + "alias_address": "Taxallus manzil(lar)i", + "alias_address_info": "Toʻliq e-pochta manzil(lar)i yoki @example.com — domenga keladigan barcha xabarlarni qabul qilish uchun (vergul bilan ajratilgan). Faqat mailcow domenlari.", + "alias_domain": "Taxallus domeni", + "alias_domain_info": "Faqat haqiqiy domen nomlari (vergul bilan ajratilgan).", + "app_name": "Ilova nomi", + "app_password": "Ilova paroli qoʻshish", + "app_passwd_protocols": "Ilova paroli uchun ruxsat etilgan protokollar", + "automap": "Papkalarni avtomatik xaritalashga harakat qilish (\"Sent items\", \"Sent\" => \"Sent\" va h.k.)", + "backup_mx_options": "Relay sozlamalari", + "bcc_dest_format": "BCC manzili bitta haqiqiy e-pochta manzili boʻlishi kerak.
    Agar bir nechta manzilga nusxa joʻnatish kerak boʻlsa, taxallus yarating va shu yerda ishlating.", + "comment_info": "Maxfiy izoh foydalanuvchiga koʻrinmaydi, ommaviy izoh esa foydalanuvchi koʻrinishida ustiga sichqoncha kelganda maslahat sifatida koʻrsatiladi", + "custom_params": "Maxsus parametrlar", + "custom_params_hint": "Toʻgʻri: --param=xy, notoʻgʻri: --param xy", + "delete1": "Tugatilgach manbadan oʻchirish", + "delete2": "Manbada boʻlmagan xabarlarni manzildan oʻchirish", + "delete2duplicates": "Manzildagi dublikatlarni oʻchirish", + "description": "Tavsif", + "destination": "Manzil", + "disable_login": "Kirishni taqiqlash (kiruvchi pochta hali ham qabul qilinadi)", + "domain": "Domen", + "domain_matches_hostname": "%s domeni xost nomiga mos keladi", + "domain_quota_m": "Domenning umumiy kvotasi (MiB)", + "dry": "Sinxronizatsiyani simulyatsiya qilish", + "enc_method": "Shifrlash usuli", + "exclude": "Obyektlarni istisno qilish (regex)", + "full_name": "Toʻliq ism", + "gal": "Global manzillar roʻyxati", + "gal_info": "GAL domenning barcha obyektlarini oʻz ichiga oladi va hech bir foydalanuvchi tomonidan tahrirlanmaydi. Oʻchirilsa, SOGodagi bandlik maʼlumotlari yoʻqoladi! Oʻzgarishlarni qoʻllash uchun SOGoni qayta ishga tushiring.", + "generate": "yaratish", + "goto_ham": "ham sifatida oʻrgatish", + "goto_null": "Xatni jimgina rad etish", + "goto_spam": "spam sifatida oʻrgatish", + "hostname": "Xost", + "inactive": "Nofaol", + "internal": "Ichki", + "internal_info": "Ichki taxalluslarga faqat oʻz domeni yoki taxallus domenlaridan kirish mumkin.", + "sender_allowed": "Ushbu taxallus orqali joʻnatishga ruxsat berish", + "kind": "Turi", + "mailbox_quota_def": "Standart pochta qutisi kvotasi", + "mailbox_quota_m": "Bitta pochta qutisining maks. kvotasi (MiB)", + "mailbox_username": "Foydalanuvchi nomi (e-pochta manzilining chap qismi)", + "max_aliases": "Mumkin boʻlgan maks. taxalluslar", + "max_mailboxes": "Mumkin boʻlgan maks. pochta qutilari", + "mins_interval": "Soʻrov oraligʻi (daqiqa)", + "multiple_bookings": "Bir nechta bandlovlar", + "nexthop": "Keyingi nuqta", + "password": "Parol", + "password_repeat": "Tasdiqlash paroli (takrorlang)", + "port": "Port", + "post_domain_add": "Yangi domen qoʻshilgandan keyin SOGo konteyneri (\"sogo-mailcow\") qayta ishga tushirilishi kerak!

    Bundan tashqari, domenning DNS sozlamalarini koʻrib chiqing. DNS sozlamalari tasdiqlangach, yangi domeningiz (autoconfig.<domain>, autodiscover.<domain>) uchun sertifikatlarni avtomatik yaratish uchun \"acme-mailcow\" konteynerini qayta ishga tushiring.
    Bu qadam ixtiyoriy va har 24 soatda qayta urinib koʻriladi.", + "private_comment": "Maxfiy izoh", + "public_comment": "Ommaviy izoh", + "quota_mb": "Kvota (MiB)", + "relay_all": "Barcha qabul qiluvchilarni relay qilish", + "relay_all_info": "↪ Agar barcha qabul qiluvchilarni relay qilmaslikni tanlasangiz, relay qilinishi kerak boʻlgan har bir qabul qiluvchi uchun (\"koʻr\") pochta qutisi qoʻshishingiz kerak boʻladi.", + "relay_domain": "Ushbu domenni relay qilish", + "relay_transport_info": "
    Maʼlumot
    Bu domen uchun maxsus manzilga transport xaritalarini belgilashingiz mumkin. Agar oʻrnatilmagan boʻlsa, MX qidiruvi amalga oshiriladi.", + "relay_unknown_only": "Faqat mavjud boʻlmagan pochta qutilarini relay qilish. Mavjud pochta qutilari mahalliy yetkazib beriladi.", + "relayhost_wrapped_tls_info": "Iltimos, TLS-oʻralgan portlardan foydalanmang (asosan 465-portda).
    \r\nO'ralmagan istalgan portdan foydalaning va STARTTLS chiqaring. TLS ni majburlash uchun TLS siyosatini \"TLS siyosati xaritalari\"da yaratish mumkin.", + "select": "Iltimos, tanlang...", + "select_domain": "Avval domenni tanlang", + "sieve_desc": "Qisqa tavsif", + "sieve_type": "Filtr turi", + "skipcrossduplicates": "Papkalararo dublikat xabarlarni oʻtkazib yuborish (birinchi kelgan, birinchi xizmatlanadi)", + "subscribeall": "Barcha papkalarga obuna boʻlish", + "syncjob": "Sinxronizatsiya vazifasi qoʻshish", + "syncjob_hint": "Parollar oddiy matn sifatida saqlanishini unutmang!", + "tags": "Teglar", + "target_address": "Yoʻnaltirish manzillari", + "target_address_info": "Toʻliq e-pochta manzil(lar)i (vergul bilan ajratilgan).", + "target_domain": "Maqsad domen", + "timeout1": "Masofaviy xostga ulanish vaqtining tugashi", + "timeout2": "Mahalliy xostga ulanish vaqtining tugashi", + "username": "Foydalanuvchi nomi", + "validate": "Tekshirish", + "validation_success": "Muvaffaqiyatli tekshirildi" + }, + "admin": { + "access": "Kirish", + "action": "Amal", + "activate_api": "API ni faollashtirish", + "activate_send": "Joʻnatish tugmasini faollashtirish", + "active": "Faol", + "active_rspamd_settings_map": "Faol sozlamalar xaritasi", + "add": "Qoʻshish", + "add_admin": "Administrator qoʻshish", + "add_domain_admin": "Domen administratorini qoʻshish", + "add_forwarding_host": "Yoʻnaltirish xosti qoʻshish", + "add_relayhost": "Joʻnatuvchiga bogʻliq transport qoʻshish", + "add_relayhost_hint": "Autentifikatsiya maʼlumotlari, agar boʻlsa, oddiy matn sifatida saqlanishini unutmang.", + "add_row": "Satr qoʻshish", + "add_settings_rule": "Sozlamalar qoidasini qoʻshish", + "add_transport": "Transport qoʻshish", + "add_transports_hint": "Autentifikatsiya maʼlumotlari, agar boʻlsa, oddiy matn sifatida saqlanishini unutmang.", + "additional_rows": " ta qoʻshimcha satr qoʻshildi", + "admin": "Administrator", + "admin_details": "Administrator maʼlumotlarini tahrirlash", + "admin_domains": "Domen tayinlashlari", + "admins": "Administratorlar", + "admins_ldap": "LDAP administratorlari", + "admin_quicklink": "Admin kirish sahifasiga tezkor havolani yashirish", + "advanced_settings": "Kengaytirilgan sozlamalar", + "allowed_methods": "Access-Control-Allow-Methods", + "allowed_origins": "Access-Control-Allow-Origin", + "api_allow_from": "API kirishini ushbu IP / CIDR tarmoq belgilanishlaridan ruxsat berish", + "api_info": "API ishlab chiqilmoqda. Hujjatlarni /api manzilida topishingiz mumkin", + "api_key": "API kaliti", + "api_read_only": "Faqat oʻqish uchun kirish", + "api_read_write": "Oʻqish va yozish uchun kirish", + "api_skip_ip_check": "API uchun IP tekshiruvini oʻtkazib yuborish", + "app_hide": "Kirishda yashirish", + "app_links": "Ilova havolalari", + "app_name": "Ilova nomi", + "apps_name": "\"mailcow Apps\" nomi", + "arrival_time": "Kelish vaqti (server vaqti)", + "authed_user": "Avtorizatsiya qilingan foydalanuvchi", + "ays": "Davom etishni xohlayotganingizga ishonchingiz komilmi?", + "ban_list_info": "Quyida bloklangan IP roʻyxati keltirilgan: tarmoq (qolgan blok vaqti) - [amallar].
    Blokdan chiqarishga navbatga qoʻyilgan IP lar bir necha soniya ichida faol bloklash roʻyxatidan olib tashlanadi.
    Qizil yorliqlar qora roʻyxat orqali faol doimiy bloklarni bildiradi.", + "change_logo": "Logotipni oʻzgartirish", + "logo_normal_label": "Oddiy", + "logo_dark_label": "Qorongʻi rejim uchun teskari", + "configuration": "Konfiguratsiya", + "convert_html_to_text": "HTMLni oddiy matnga aylantirish", + "copy_to_clipboard": "Matn vaqtinchalik xotiraga koʻchirildi!", + "cors_settings": "CORS sozlamalari", + "credentials_transport_warning": "Diqqat: Yangi transport xaritasi yozuvi qoʻshish keyingi nuqta ustunlari mos keladigan barcha yozuvlar uchun maʼlumotlarni yangilaydi.", + "customer_id": "Mijoz ID", + "customize": "Sozlash", + "login_page": "Kirish sahifasi", + "destination": "Manzil", + "dkim_add_key": "ARC/DKIM kaliti qoʻshish", + "dkim_domains_selector": "Tanlovchi", + "dkim_domains_wo_keys": "Kalit yetishmaydigan domenlarni tanlash", + "dkim_from": "Kimdan", + "dkim_from_title": "Maʼlumotlarni koʻchirib olish uchun manba domen", + "dkim_key_length": "DKIM kalit uzunligi (bit)", + "dkim_key_missing": "Kalit yetishmaydi", + "dkim_key_unused": "Kalit ishlatilmagan", + "dkim_key_valid": "Kalit haqiqiy", + "dkim_keys": "ARC/DKIM kalitlari", + "dkim_overwrite_key": "Mavjud DKIM kalitini ustidan yozish", + "dkim_private_key": "Maxfiy kalit", + "dkim_to": "Kimga", + "dkim_to_title": "Maqsad domen(lar) — ustidan yoziladi", + "domain": "Domen", + "domain_admin": "Domen administratori", + "domain_admins": "Domen administratorlari", + "domainadmin_quicklink": "Domen admin kirish sahifasiga tezkor havolani yashirish", + "domain_s": "Domen(lar)", + "duplicate": "Nusxalash", + "duplicate_dkim": "DKIM yozuvini nusxalash", + "edit": "Tahrirlash", + "empty": "Natijalar yoʻq", + "excludes": "Ushbu qabul qiluvchilarni istisno qiladi", + "f2b_ban_time": "Bloklash vaqti (s)", + "f2b_ban_time_increment": "Har bir bloklashda blok vaqti ortadi", + "f2b_blacklist": "Qora roʻyxatdagi tarmoqlar/xostlar", + "f2b_filter": "Regex filtrlari", + "f2b_list_info": "Qora roʻyxatdagi xost yoki tarmoq har doim oq roʻyxat elementidan ustun keladi. Roʻyxat yangilanishlarini qoʻllash uchun bir necha soniya kerak boʻladi.", + "f2b_manage_external": "Fail2ban ni tashqi boshqarish", + "f2b_manage_external_info": "Fail2ban hali ham banlist ni saqlaydi, ammo trafikni bloklash uchun faol qoidalarni oʻrnatmaydi. Trafikni tashqi tomondan bloklash uchun quyida yaratilgan banlist dan foydalaning.", + "f2b_max_attempts": "Maks. urinishlar", + "f2b_max_ban_time": "Maks. bloklash vaqti (s)", + "f2b_netban_ipv4": "Blokni qoʻllash uchun IPv4 subtarmoq oʻlchami (8-32)", + "f2b_netban_ipv6": "Blokni qoʻllash uchun IPv6 subtarmoq oʻlchami (8-128)", + "f2b_parameters": "Fail2ban parametrlari", + "f2b_regex_info": "Eʼtiborga olinadigan jurnallar: SOGo, Postfix, Dovecot, PHP-FPM.", + "f2b_retry_window": "Maks. urinishlar uchun qayta urinish oynasi (s)", + "f2b_whitelist": "Oq roʻyxatdagi tarmoqlar/xostlar", + "filter": "Filtr", + "filter_table": "Filtr jadvali", + "force_sso_text": "Tashqi OIDC provayder sozlangan boʻlsa, bu variant standart mailcow kirish formalarini yashiradi va faqat Yagona kirish tugmasini koʻrsatadi", + "force_sso": "mailcow kirishini oʻchirib, faqat Yagona kirishni koʻrsatish", + "forwarding_hosts": "Yoʻnaltirish xostlari", + "forwarding_hosts_add_hint": "Siz IPv4/IPv6 manzillarini, CIDR yozuvidagi tarmoqlarni, xost nomlarini (IP manzillariga aylantiriladi) yoki domen nomlarini (SPF TXT yoki yoʻq boʻlsa MX yozuvlarini soʻrash orqali IP manzillariga aylantiriladi) belgilashingiz mumkin.", + "forwarding_hosts_hint": "Kiruvchi xabarlar bu yerda keltirilgan istalgan xostlardan shartsiz qabul qilinadi. Bu xostlar keyin DNSBL ga qarshi tekshirilmaydi yoki greylisting ga olinmaydi. Ulardan kelgan spam hech qachon rad etilmaydi, ammo ixtiyoriy ravishda Junk papkasiga joylashtirilishi mumkin. Buning eng keng tarqalgan ishlatilishi — mailcow serveringizga kiruvchi xatlarni yoʻnaltirish qoidasi oʻrnatilgan pochta serverlarini koʻrsatish.", + "from": "Kimdan", + "generate": "yaratish", + "guid": "GUID - noyob nusxa ID si", + "guid_and_license": "GUID va litsenziya", + "hash_remove_info": "Ratelimit hashini oʻchirish (agar mavjud boʻlsa) uning hisoblagichini butunlay tiklaydi.
    \r\n Har bir hash alohida rang bilan koʻrsatiladi.", + "help_text": "Kirish maskasi ostidagi yordam matnini bekor qilish (HTML ruxsat etilgan)", + "host": "Xost", + "html": "HTML", + "iam": "Identifikatsiya provayderi", + "iam_attribute_field": "Atribut maydoni", + "iam_authorize_url": "Avtorizatsiya nuqtasi", + "iam_auth_flow": "Autentifikatsiya oqimi", + "iam_auth_flow_info": "Yagona kirish (SSO) uchun ishlatiladigan Authorization Code Flow (Keycloak da Standard Flow) qoʻshimcha ravishda, mailcow toʻgʻridan-toʻgʻri maʼlumotlar bilan Autentifikatsiya oqimini ham qoʻllab-quvvatlaydi. Mailpassword Flow Keycloak Admin REST API yordamida foydalanuvchi maʼlumotlarini tekshirishga harakat qiladi. mailcow Keycloakda xaritalangan mailcow_password atributidan xeshlangan parolni oladi.", + "iam_basedn": "Base DN", + "iam_client_id": "Mijoz ID", + "iam_client_secret": "Mijoz maxfiy soʻzi", + "iam_client_scopes": "Mijoz qamrovlari", + "iam_default_template": "Standart shablon", + "iam_default_template_description": "Agar foydalanuvchiga shablon tayinlanmagan boʻlsa, pochta qutisini yaratishda standart shablon ishlatiladi, lekin pochta qutisini yangilash uchun emas.", + "iam_description": "Autentifikatsiya uchun tashqi provayderni sozlash
    Foydalanuvchi pochta qutilari atribut xaritalashi oʻrnatilgan boʻlsa, ular birinchi marta kirgan zahoti avtomatik yaratiladi.", + "iam_extra_permission": "Quyidagi sozlamalarning ishlashi uchun Keycloak dagi mailcow mijoziga Service account va view-users ruxsati kerak.", + "iam_host": "Xost", + "iam_host_info": "Bir yoki bir nechta LDAP xostni vergul bilan ajratib kiriting.", + "iam_import_users": "Foydalanuvchilarni import qilish", + "iam_login_provisioning": "Kirishda foydalanuvchilarni avto-yaratish", + "iam_mapping": "Atribut xaritalashi", + "iam_bindpass": "Bind paroli", + "iam_periodic_full_sync": "Davriy toʻliq sinxronizatsiya", + "iam_port": "Port", + "iam_realm": "Realm", + "iam_redirect_url": "Yoʻnaltirish URL", + "iam_rest_flow": "Mailpassword Flow", + "iam_server_url": "Server URL", + "iam_sso": "Yagona kirish (SSO)", + "iam_sync_interval": "Sinxronizatsiya / import oraligʻi (daqiqa)", + "iam_test_connection": "Ulanishni tekshirish", + "iam_token_url": "Token nuqtasi", + "iam_userinfo_url": "Foydalanuvchi maʼlumotlari nuqtasi", + "iam_username_field": "Foydalanuvchi nomi maydoni", + "iam_binddn": "Bind DN", + "iam_use_ssl": "SSL dan foydalanish", + "iam_use_ssl_info": "SSL yoqilsa va port 389 ga oʻrnatilgan boʻlsa, u avtomatik ravishda 636 ga oʻzgartiriladi.", + "iam_use_tls": "StartTLS dan foydalanish", + "iam_use_tls_info": "TLS yoqilsa, LDAP serveringiz uchun standart portni (389) ishlatishingiz kerak. SSL portlari ishlatilmaydi.", + "iam_version": "Versiya", + "ignore_ssl_error": "SSL xatolarini eʼtiborsiz qoldirish", + "import": "Import", + "import_private_key": "Maxfiy kalitni import qilish", + "in_use_by": "Kim ishlatadi", + "inactive": "Nofaol", + "include_exclude": "Kiritish/Istisno qilish", + "include_exclude_info": "Standart holatda — tanlovsiz — barcha pochta qutilariga murojaat qilinadi", + "includes": "Ushbu qabul qiluvchilarni kiritish", + "ip_check": "IP tekshiruvi", + "ip_check_disabled": "IP tekshiruvi oʻchirilgan. Uni quyidagi joydan yoqishingiz mumkin:
    Tizim > Konfiguratsiya > Variantlar > Sozlash", + "ip_check_opt_in": "Tashqi IP manzillarni aniqlash uchun ipv4.mailcow.email va ipv6.mailcow.email uchinchi tomon xizmatidan foydalanishga rozilik.", + "is_mx_based": "MX asosida", + "last_applied": "Oxirgi qoʻllanilgan", + "license_info": "Litsenziya talab qilinmaydi, ammo bu loyihaning rivojlanishiga yordam beradi.
    GUID ni bu yerda roʻyxatdan oʻtkazing yoki mailcow oʻrnatmangiz uchun yordam sotib oling.", + "link": "Havola", + "loading": "Iltimos, kuting...", + "login_time": "Kirish vaqti", + "logo_info": "Tasviringiz yuqori navigatsiya paneli uchun 40px balandlikka, bosh sahifa uchun maks. 250px kenglikka oʻlchanadi. Masshtablanadigan grafika tavsiya etiladi.", + "lookup_mx": "Manzil — MX nomiga mos keladigan muntazam ifoda (.*\\.google\\.com — google.com bilan tugaydigan MX ga yoʻnaltirilgan barcha pochtani ushbu nuqta orqali yoʻnaltirish uchun)", + "main_name": "\"mailcow UI\" nomi", + "merged_vars_hint": "Kulrang satrlar vars.(local.)inc.php dan birlashtirilgan va oʻzgartirib boʻlmaydi.", + "message": "Xabar", + "message_size": "Xabar oʻlchami", + "nexthop": "Keyingi nuqta", + "needs_restart": "qayta ishga tushirish kerak", + "no": "✕", + "no_active_bans": "Faol bloklar yoʻq", + "no_new_rows": "Boshqa satrlar mavjud emas", + "no_record": "Yozuv yoʻq", + "oauth2_apps": "OAuth2 ilovalari", + "oauth2_add_client": "OAuth2 mijozini qoʻshish", + "oauth2_client_id": "Mijoz ID", + "oauth2_client_secret": "Mijoz maxfiy soʻzi", + "oauth2_info": "OAuth2 amalga oshirilishi \"Authorization Code\" grant turini qoʻllab-quvvatlaydi va refresh tokenlarini chiqaradi.
    \r\nServer shuningdek refresh token ishlatilgandan keyin avtomatik ravishda yangi refresh tokenlarini chiqaradi.

    \r\n• Standart qamrov — profile. Faqat pochta qutisi foydalanuvchilari OAuth2 ga qarshi autentifikatsiya qilinishi mumkin. Agar scope parametri tashlab ketilgan boʻlsa, u profile ga qaytadi.
    \r\n• state parametri mijoz tomonidan avtorizatsiya soʻrovining bir qismi sifatida yuborilishi shart.

    \r\nOAuth2 API ga soʻrovlar uchun yoʻllar:
    \r\n
      \r\n
    • Avtorizatsiya nuqtasi: /oauth/authorize
    • \r\n
    • Token nuqtasi: /oauth/token
    • \r\n
    • Resurs sahifasi: /oauth/profile
    • \r\n
    \r\nMijoz maxfiy soʻzini qayta yaratish mavjud avtorizatsiya kodlarini muddati tugashiga olib kelmaydi, lekin ular tokenni yangilashda muvaffaqiyatsiz boʻladi.

    \r\nMijoz tokenlarini bekor qilish barcha faol sessiyalarning darhol tugashiga olib keladi. Barcha mijozlar qayta autentifikatsiya qilishlari kerak.", + "oauth2_redirect_uri": "Yoʻnaltirish URI", + "oauth2_renew_secret": "Yangi mijoz maxfiy soʻzini yaratish", + "oauth2_revoke_tokens": "Barcha mijoz tokenlarini bekor qilish", + "optional": "ixtiyoriy", + "options": "Variantlar", + "password": "Parol", + "password_length": "Parol uzunligi", + "password_policy": "Parol siyosati", + "password_policy_chars": "Kamida bitta alifbo belgisini oʻz ichiga olishi kerak", + "password_policy_length": "Minimal parol uzunligi %d", + "password_policy_lowerupper": "Kichik va katta harflarni oʻz ichiga olishi kerak", + "password_policy_numbers": "Kamida bitta raqamni oʻz ichiga olishi kerak", + "password_policy_special_chars": "Maxsus belgilarni oʻz ichiga olishi kerak", + "password_repeat": "Tasdiqlash paroli (takrorlang)", + "password_reset_info": "Agar tiklash uchun e-pochta kiritilmagan boʻlsa, bu funksiyadan foydalanib boʻlmaydi.", + "password_reset_settings": "Parolni tiklash sozlamalari", + "password_reset_tmpl_html": "HTML shablon", + "password_reset_tmpl_text": "Matnli shablon", + "password_settings": "Parol sozlamalari", + "priority": "Muhimlik darajasi", + "private_key": "Maxfiy kalit", + "quarantine": "Karantin", + "quarantine_bcc": "Barcha bildirishnomalarning nusxasini (BCC) ushbu qabul qiluvchiga yuborish:
    Oʻchirish uchun boʻsh qoldiring. Imzolanmagan, tekshirilmagan xat. Faqat ichki yetkazib berilishi kerak.", + "quarantine_exclude_domains": "Domenlar va taxallus domenlarini istisno qilish", + "quarantine_max_age": "Maksimal yosh (kunlarda)
    Qiymat 1 kun yoki undan koʻp boʻlishi kerak.", + "quarantine_max_score": "Agar xatning spam bahosi ushbu qiymatdan yuqori boʻlsa, bildirishnomani rad etish:
    Standart 9999.0", + "quarantine_max_size": "Maksimal oʻlcham (MiB) (kattaroq elementlar rad etiladi):
    0 cheksizlikni bildirmaydi.", + "quarantine_notification_html": "Bildirishnoma e-pochta shabloni:
    Standart shablonni tiklash uchun boʻsh qoldiring.", + "quarantine_notification_sender": "Bildirishnoma e-pochta joʻnatuvchisi", + "quarantine_notification_subject": "Bildirishnoma e-pochta mavzusi", + "quarantine_redirect": "Barcha bildirishnomalarni ushbu qabul qiluvchiga yoʻnaltirish:
    Oʻchirish uchun boʻsh qoldiring. Imzolanmagan, tekshirilmagan xat. Faqat ichki yetkazib berilishi kerak.", + "quarantine_release_format": "Chiqarilgan elementlar formati", + "quarantine_release_format_att": "Ilova sifatida", + "quarantine_release_format_raw": "Oʻzgartirilmagan asl nusxa", + "quarantine_retention_size": "Pochta qutisi uchun saqlash:
    0 — nofaolligini bildiradi.", + "quicklink_text": "Boshqa kirish sahifalariga tezkor havolalarni kirish formasi ostida koʻrsatish yoki yashirish", + "quota_notification_html": "Bildirishnoma e-pochta shabloni:
    Standart shablonni tiklash uchun boʻsh qoldiring.", + "quota_notification_sender": "Bildirishnoma e-pochta joʻnatuvchisi", + "quota_notification_subject": "Bildirishnoma e-pochta mavzusi", + "quota_notifications": "Kvota bildirishnomalari", + "quota_notifications_info": "Kvota bildirishnomalari foydalanuvchilarga foydalanish 80% va 95% ga yetganda bir martadan yuboriladi.", + "quota_notifications_vars": "{{percent}} — foydalanuvchining joriy kvotasi
    {{username}} — pochta qutisi nomi", + "queue_unban": "blokdan chiqarish", + "r_active": "Faol cheklovlar", + "r_inactive": "Nofaol cheklovlar", + "r_info": "Faol cheklovlar roʻyxatidagi kulrang/oʻchirilgan elementlar mailcow uchun haqiqiy cheklovlar sifatida tanilmaydi va koʻchirib boʻlmaydi. Nomaʼlum cheklovlar baribir koʻrinish tartibida oʻrnatiladi.
    inc/vars.local.inc.php da yangi elementlarni qoʻshib, ularni almashtirishingiz mumkin.", + "rate_name": "Tezlik nomi", + "recipients": "Qabul qiluvchilar", + "refresh": "Yangilash", + "regen_api_key": "API kalitini qayta yaratish", + "regex_maps": "Regex xaritalari", + "relay_from": "\"From:\" manzili", + "relay_rcpt": "\"To:\" manzili", + "relay_run": "Sinovni ishga tushirish", + "relayhosts": "Joʻnatuvchiga bogʻliq transportlar", + "relayhosts_hint": "Domen konfiguratsiya muloqotida tanlash mumkin boʻlgan joʻnatuvchiga bogʻliq transportlarni belgilang.
    \r\n Transport xizmati har doim \"smtp:\" va shuning uchun taklif qilinganda TLS ga harakat qiladi. Oʻralgan TLS (SMTPS) qoʻllab-quvvatlanmaydi. Foydalanuvchining individual chiquvchi TLS siyosati sozlamasi eʼtiborga olinadi.
    \r\n Taxallus domenlari bilan birga tanlangan domenlarga taʼsir qiladi.", + "remove": "Olib tashlash", + "remove_row": "Satrni olib tashlash", + "reset_default": "Standartga qaytarish", + "reset_limit": "Hash ni olib tashlash", + "reset_password_vars": "{{link}} Yaratilgan parolni tiklash havolasi
    {{username}} Parolni tiklashni soʻragan foydalanuvchining pochta qutisi nomi
    {{username2}} Tiklash uchun pochta qutisi nomi
    {{date}} Parolni tiklash soʻrovi qilingan sana
    {{token_lifetime}} Daqiqalardagi token muddati
    {{hostname}} mailcow xost nomi", + "restore_template": "Standart shablonni tiklash uchun boʻsh qoldiring.", + "routing": "Yoʻnaltirish", + "rsetting_add_rule": "Qoida qoʻshish", + "rsetting_content": "Qoida mazmuni", + "rsetting_desc": "Qisqa tavsif", + "rsetting_no_selection": "Iltimos, qoidani tanlang", + "rsetting_none": "Qoidalar mavjud emas", + "rsettings_insert_preset": "Misol presetini kiritish \"%s\"", + "rsettings_preset_1": "Autentifikatsiya qilingan foydalanuvchilar uchun DKIM va tezlik chegarasidan boshqa hamma narsani oʻchirib qoʻyish", + "rsettings_preset_2": "Postmasterlar spamni xohlaydi", + "rsettings_preset_3": "Pochta qutisi uchun faqat maxsus joʻnatuvchilarga ruxsat berish (yaʼni, faqat ichki pochta qutisi sifatida ishlatish)", + "rsettings_preset_4": "Domen uchun Rspamd ni oʻchirish", + "rspamd_com_settings": "Sozlama nomi avtomatik yaratiladi, quyidagi misol presetlariga qarang. Batafsil maʼlumot uchun Rspamd hujjatlariga qarang", + "rspamd_global_filters": "Global filtr xaritalari", + "rspamd_global_filters_agree": "Ehtiyot boʻlaman!", + "rspamd_global_filters_info": "Global filtr xaritalari turli xil global rad etish va ruxsat berish roʻyxatlarini oʻz ichiga oladi.", + "rspamd_global_filters_regex": "Ularning nomlari ularning maqsadini tushuntiradi. Barcha mazmun \"/pattern/options\" formatidagi haqiqiy muntazam ifodalarni oʻz ichiga olishi kerak (masalan, /.+@domain\\.tld/i).
    \r\n Har bir regex satriga oddiy tekshiruvlar bajariladi, ammo agar Rspamd sintaksisni toʻgʻri oʻqiy olmasa, uning funksionalligi buzilishi mumkin.
    \r\n Rspamd oʻzgarganda xarita mazmunini oʻqishga harakat qiladi. Agar muammolarga duch kelsangiz, xarita qayta yuklanishi uchun Rspamd ni qayta ishga tushiring.
    Qora roʻyxatdagi elementlar karantindan istisno qilinadi.", + "rspamd_settings_map": "Rspamd sozlamalar xaritasi", + "sal_level": "Moo darajasi", + "save": "Oʻzgarishlarni saqlash", + "search_domain_da": "Domenlarni qidirish", + "send": "Joʻnatish", + "sender": "Joʻnatuvchi", + "service": "Xizmat", + "service_id": "Xizmat ID", + "source": "Manba", + "spamfilter": "Spam filtri", + "subject": "Mavzu", + "success": "Muvaffaqiyatli", + "sys_mails": "Tizim xatlari", + "task": "Vazifa", + "text": "Matn", + "time": "Vaqt", + "title": "Sarlavha", + "title_name": "\"mailcow UI\" veb-sayt sarlavhasi", + "to_top": "Yuqoriga qaytish", + "transport_dest_format": "Regex yoki sintaksis: example.org, .example.org, *, box@example.org (bir nechta qiymatlarni vergul bilan ajratish mumkin)", + "transport_maps": "Transport xaritalari", + "transport_test_rcpt_info": "• Tashqi manzilga relay qilishni sinash uchun null@hosted.mailcow.de dan foydalaning.", + "transports_hint": "• Transport xaritasi yozuvi joʻnatuvchiga bogʻliq transport xaritasidan ustun.
    \r\n• MX asosidagi transportlar afzalroq ishlatiladi.
    \r\n• Foydalanuvchi boʻyicha chiquvchi TLS siyosati sozlamalari eʼtiborga olinmaydi va faqat TLS siyosati xaritasi yozuvlari orqali majburlanishi mumkin.
    \r\n• Belgilangan transportlar uchun transport xizmati har doim \"smtp:\" va shuning uchun taklif qilinganda TLS ga harakat qiladi. Oʻralgan TLS (SMTPS) qoʻllab-quvvatlanmaydi.
    \r\n• \"/localhost$/\" ga mos keladigan manzillar har doim \"local:\" orqali tashiladi, shuning uchun \"*\" manzili bu manzillarga qoʻllanmaydi.
    \r\n• Misol uchun keyingi nuqta \"[host]:25\" uchun maʼlumotlarni aniqlash uchun, Postfix har doim \"[host]:25\" ni qidirishdan oldin \"host\" ni soʻraydi. Bu xatti-harakat \"host\" va \"[host]:25\" ni bir vaqtning oʻzida ishlatishni imkonsiz qiladi.", + "ui_footer": "Pastki qism (HTML ruxsat etilgan)", + "ui_header_announcement": "Eʼlonlar", + "ui_header_announcement_active": "Eʼlonni faollashtirish", + "ui_header_announcement_content": "Matn (HTML ruxsat etilgan)", + "ui_header_announcement_help": "Eʼlon barcha tizimga kirgan foydalanuvchilarga va UI ning kirish ekranida koʻrinadi.", + "ui_header_announcement_select": "Eʼlon turini tanlang", + "ui_header_announcement_type": "Tur", + "ui_header_announcement_type_danger": "Juda muhim", + "ui_header_announcement_type_info": "Maʼlumot", + "ui_header_announcement_type_warning": "Muhim", + "ui_texts": "UI yorliqlari va matnlari", + "unban_pending": "blokdan chiqarish kutilmoqda", + "unchanged_if_empty": "Oʻzgartirilmagan boʻlsa, boʻsh qoldiring", + "upload": "Yuklash", + "username": "Foydalanuvchi nomi", + "user_link": "Foydalanuvchi havolasi", + "user_quicklink": "Foydalanuvchi kirish sahifasiga tezkor havolani yashirish", + "validate_license_now": "GUID ni litsenziya serveriga qarshi tekshirish", + "verify": "Tasdiqlash", + "yes": "✓" + }, + "danger": { + "access_denied": "Kirish rad etildi yoki noma'lum forma ma'lumotlari", + "tfa_removal_blocked": "Ikki faktorli autentifikatsiyani olib tashlab boʻlmaydi, chunki u sizning hisobingiz uchun talab qilinadi.", + "alias_domain_invalid": "%s taxallus domeni noto'g'ri", + "alias_empty": "Taxallus manzili boʻsh boʻlmasligi kerak", + "alias_goto_identical": "Taxallus va yoʻnaltirish manzili bir xil boʻlmasligi kerak", + "alias_invalid": "%s taxallus manzili noto'g'ri", + "aliasd_targetd_identical": "Taxallus domeni maqsad domeniga teng boʻlmasligi kerak: %s", + "aliases_in_use": "Maks. taxalluslar %d dan katta yoki teng boʻlishi kerak", + "app_name_empty": "Ilova nomi boʻsh boʻlolmaydi", + "app_passwd_id_invalid": "Ilova paroli ID %s noto'g'ri", + "authsource_in_use": "Identifikatsiya provayderini oʻzgartirib yoki oʻchirib boʻlmaydi, chunki u hozirda bir yoki bir nechta foydalanuvchilar tomonidan ishlatilmoqda.", + "bcc_empty": "BCC manzili boʻsh boʻlolmaydi", + "bcc_exists": "%s turi uchun %s BCC xaritasi mavjud", + "bcc_must_be_email": "BCC manzili %s haqiqiy e-pochta manzili emas", + "comment_too_long": "Izoh juda uzun, maks. 160 ta belgiga ruxsat berilgan", + "cors_invalid_method": "Noto'g'ri Allow-Method belgilangan", + "cors_invalid_origin": "Noto'g'ri Allow-Origin belgilangan", + "defquota_empty": "Bitta pochta qutisi uchun standart kvota 0 boʻlmasligi kerak.", + "demo_mode_enabled": "Demo rejim yoqilgan", + "description_invalid": "%s uchun resurs tavsifi noto'g'ri", + "dkim_domain_or_sel_exists": "\"%s\" uchun DKIM kaliti mavjud va u ustidan yozilmaydi", + "dkim_domain_or_sel_invalid": "DKIM domeni yoki tanlovchi noto'g'ri: %s", + "domain_cannot_match_hostname": "Domen xost nomiga mos kelishi mumkin emas", + "domain_exists": "%s domeni allaqachon mavjud", + "domain_invalid": "Domen nomi boʻsh yoki noto'g'ri", + "domain_not_empty": "Boʻsh boʻlmagan %s domenini olib tashlab boʻlmaydi", + "domain_not_found": "%s domeni topilmadi", + "domain_quota_m_in_use": "Domen kvotasi %s MiB dan katta yoki teng boʻlishi kerak", + "extended_sender_acl_denied": "Tashqi joʻnatuvchi manzillarini oʻrnatish uchun ACL yetishmayapti", + "extra_acl_invalid": "Tashqi joʻnatuvchi manzili \"%s\" noto'g'ri", + "extra_acl_invalid_domain": "Tashqi joʻnatuvchi \"%s\" noto'g'ri domendan foydalanmoqda", + "fido2_verification_failed": "FIDO2 tekshiruvi muvaffaqiyatsiz: %s", + "file_open_error": "Faylni yozish uchun ochib boʻlmadi", + "filter_type": "Notoʻgʻri filtr turi", + "from_invalid": "Joʻnatuvchi boʻsh boʻlmasligi kerak", + "generic_server_error": "Kutilmagan server xatosi yuz berdi. Iltimos, administratoringiz bilan bogʻlaning.", + "global_filter_write_error": "Filtr faylini yozib boʻlmadi: %s", + "global_map_invalid": "Global xarita ID %s noto'g'ri", + "global_map_write_error": "Global xarita ID %s ni yozib boʻlmadi: %s", + "goto_empty": "Taxallus manzili kamida bitta haqiqiy yoʻnaltirish manzilini oʻz ichiga olishi kerak", + "goto_invalid": "Yoʻnaltirish manzili %s noto'g'ri", + "ham_learn_error": "Ham oʻrgatish xatosi: %s", + "iam_test_connection": "Ulanish muvaffaqiyatsiz", + "imagick_exception": "Xato: Tasvirni oʻqishda Imagick istisnosi", + "img_dimensions_exceeded": "Tasvir maksimal tasvir oʻlchamidan oshib ketdi", + "img_invalid": "Tasvir faylini tekshirib boʻlmadi", + "img_size_exceeded": "Tasvir maksimal fayl oʻlchamidan oshib ketdi", + "img_tmp_missing": "Tasvir faylini tekshirib boʻlmadi: Vaqtinchalik fayl topilmadi", + "invalid_bcc_map_type": "Noto'g'ri BCC xarita turi", + "invalid_destination": "Manzil formati \"%s\" noto'g'ri", + "invalid_filter_type": "Noto'g'ri filtr turi", + "invalid_host": "Noto'g'ri xost belgilangan: %s", + "invalid_mime_type": "Noto'g'ri MIME turi", + "invalid_nexthop": "Keyingi nuqta formati noto'g'ri", + "invalid_nexthop_authenticated": "Keyingi nuqta boshqa maʼlumotlar bilan mavjud, iltimos avval bu keyingi nuqta uchun mavjud maʼlumotlarni yangilang.", + "invalid_recipient_map_new": "Yangi qabul qiluvchi noto'g'ri belgilangan: %s", + "invalid_recipient_map_old": "Asl qabul qiluvchi noto'g'ri belgilangan: %s", + "invalid_reset_token": "Noto'g'ri tiklash tokeni", + "ip_list_empty": "Ruxsat etilgan IP roʻyxati boʻsh boʻlolmaydi", + "is_alias": "%s allaqachon taxallus manzili sifatida tanilgan", + "is_alias_or_mailbox": "%s allaqachon taxallus, pochta qutisi yoki taxallus domenidan kengaytirilgan taxallus manzili sifatida tanilgan.", + "is_spam_alias": "%s allaqachon vaqtinchalik taxallus manzili (spam taxallus manzili) sifatida tanilgan", + "last_key": "Oxirgi kalitni oʻchirib boʻlmaydi, iltimos uning oʻrniga TFA ni oʻchiring.", + "login_failed": "Kirish muvaffaqiyatsiz", + "mailbox_defquota_exceeds_mailbox_maxquota": "Standart kvota maksimal kvota chegarasidan oshib ketdi", + "mailbox_invalid": "Pochta qutisi nomi noto'g'ri", + "mailbox_quota_exceeded": "Kvota domen chegarasidan oshib ketdi (maks. %d MiB)", + "mailbox_quota_exceeds_domain_quota": "Maks. kvota domen kvota chegarasidan oshib ketdi", + "mailbox_quota_left_exceeded": "Yetarli joy qolmadi (qolgan joy: %d MiB)", + "mailboxes_in_use": "Maks. pochta qutilari %d dan katta yoki teng boʻlishi kerak", + "malformed_username": "Noto'g'ri shakldagi foydalanuvchi nomi", + "map_content_empty": "Xarita mazmuni boʻsh boʻlolmaydi", + "max_age_invalid": "Maks. yosh %s noto'g'ri", + "max_alias_exceeded": "Maks. taxalluslar oshib ketdi", + "max_mailbox_exceeded": "Maks. pochta qutilari oshib ketdi (%d / %d)", + "max_quota_in_use": "Pochta qutisi kvotasi %d MiB dan katta yoki teng boʻlishi kerak", + "maxquota_empty": "Bitta pochta qutisi uchun maks. kvota 0 boʻlmasligi kerak.", + "mode_invalid": "Rejim %s noto'g'ri", + "mx_invalid": "MX yozuvi %s noto'g'ri", + "mysql_error": "MySQL xatosi: %s", + "network_host_invalid": "Noto'g'ri tarmoq yoki xost: %s", + "next_hop_interferes": "%s keyingi nuqta %s bilan tortishuvga kirishadi", + "next_hop_interferes_any": "Mavjud keyingi nuqta %s bilan tortishuvga kirishadi", + "nginx_reload_failed": "Nginx qayta yuklash muvaffaqiyatsiz: %s", + "no_user_defined": "Foydalanuvchi belgilanmagan", + "object_exists": "%s obyekti allaqachon mavjud", + "object_is_not_numeric": "%s qiymati raqamli emas", + "password_complexity": "Parol siyosatga mos kelmaydi", + "password_empty": "Parol boʻsh boʻlmasligi kerak", + "password_mismatch": "Tasdiqlash paroli mos kelmadi", + "password_reset_invalid_user": "Pochta qutisi topilmadi yoki tiklash uchun e-pochta oʻrnatilmagan", + "password_reset_na": "Parolni tiklash hozirda mavjud emas. Iltimos, administratoringiz bilan bogʻlaning.", + "policy_list_from_exists": "Berilgan nomli yozuv mavjud", + "policy_list_from_invalid": "Yozuv noto'g'ri formatga ega", + "private_key_error": "Maxfiy kalit xatosi: %s", + "pushover_credentials_missing": "Pushover tokeni va/yoki kaliti yetishmayapti", + "pushover_key": "Pushover kaliti notoʻgʻri formatga ega", + "pushover_token": "Pushover tokeni notoʻgʻri formatga ega", + "quarantine_category_invalid": "Karantin turkumi quyidagilardan biri boʻlishi kerak: add_header, reject, all", + "quota_not_0_not_numeric": "Kvota raqamli va >= 0 boʻlishi kerak", + "recipient_map_entry_exists": "Qabul qiluvchi xaritasi yozuvi \"%s\" mavjud", + "recovery_email_failed": "Tiklash e-pochtasini yuborib boʻlmadi. Iltimos, administratoringiz bilan bogʻlaning.", + "redis_error": "Redis xatosi: %s", + "relayhost_invalid": "Xarita yozuvi %s noto'g'ri", + "release_send_failed": "Xabarni chiqarib boʻlmadi: %s", + "required_data_missing": "Talab qilinadigan ma'lumotlar %s yetishmayapti", + "reset_f2b_regex": "Regex filtrini oʻz vaqtida tiklab boʻlmadi, iltimos qaytadan urinib koʻring yoki bir necha soniya kuting va sahifani qayta yuklang.", + "reset_token_limit_exceeded": "Tiklash tokeni chegarasi oshib ketdi. Iltimos, keyinroq urinib koʻring.", + "resource_invalid": "Resurs nomi %s noto'g'ri", + "rl_timeframe": "Tezlik chegarasi vaqt oraligʻi noto'g'ri", + "rspamd_ui_pw_length": "Rspamd UI paroli kamida 6 ta belgi uzunlikda boʻlishi kerak", + "script_empty": "Skript boʻsh boʻlolmaydi", + "sender_acl_invalid": "Joʻnatuvchi ACL qiymati %s noto'g'ri", + "set_acl_failed": "ACL ni oʻrnatib boʻlmadi", + "settings_map_invalid": "Sozlamalar xaritasi ID %s noto'g'ri", + "sieve_error": "Sieve tahlilchisi xatosi: %s", + "spam_learn_error": "Spam oʻrgatish xatosi: %s", + "subject_empty": "Mavzu boʻsh boʻlmasligi kerak", + "target_domain_invalid": "Maqsad domen %s noto'g'ri", + "targetd_not_found": "Maqsad domen %s topilmadi", + "targetd_relay_domain": "Maqsad domen %s relay domen", + "template_exists": "%s shabloni allaqachon mavjud", + "template_id_invalid": "Shablon ID %s noto'g'ri", + "template_name_invalid": "Shablon nomi noto'g'ri", + "temp_error": "Vaqtinchalik xato", + "text_empty": "Matn boʻsh boʻlmasligi kerak", + "tfa_token_invalid": "TFA tokeni noto'g'ri", + "tls_policy_map_dest_invalid": "Siyosat manzili noto'g'ri", + "tls_policy_map_entry_exists": "TLS siyosati xaritasi yozuvi \"%s\" mavjud", + "tls_policy_map_parameter_invalid": "Siyosat parametri noto'g'ri", + "to_invalid": "Qabul qiluvchi boʻsh boʻlmasligi kerak", + "totp_verification_failed": "TOTP tekshiruvi muvaffaqiyatsiz", + "transport_dest_exists": "Transport manzili \"%s\" mavjud", + "webauthn_verification_failed": "WebAuthn tekshiruvi muvaffaqiyatsiz: %s", + "webauthn_authenticator_failed": "Tanlangan autentifikator topilmadi", + "webauthn_publickey_failed": "Tanlangan autentifikator uchun ochiq kalit saqlanmagan", + "webauthn_username_failed": "Tanlangan autentifikator boshqa hisobga tegishli", + "unknown": "Noma'lum xato yuz berdi", + "unknown_tfa_method": "Noma'lum TFA usuli", + "unlimited_quota_acl": "Cheksiz kvota ACL tomonidan taqiqlangan", + "username_invalid": "Foydalanuvchi nomi %s ishlatib boʻlmaydi", + "validity_missing": "Iltimos, amal qilish muddatini tayinlang", + "value_missing": "Iltimos, barcha qiymatlarni kiriting", + "version_invalid": "Versiya %s noto'g'ri", + "yotp_verification_failed": "Yubico OTP tekshiruvi muvaffaqiyatsiz: %s" + }, + "datatables": { + "collapse_all": "Hammasini yigʻish", + "decimal": ".", + "emptyTable": "Jadvalda ma'lumot mavjud emas", + "expand_all": "Hammasini kengaytirish", + "info": "_TOTAL_ tadan _START_ dan _END_ gacha koʻrsatilmoqda", + "infoEmpty": "0 tadan 0 dan 0 gacha koʻrsatilmoqda", + "infoFiltered": "(jami _MAX_ tadan filtrlangan)", + "infoPostFix": "", + "thousands": ",", + "lengthMenu": "_MENU_ ta yozuvni koʻrsatish", + "loadingRecords": "Yuklanmoqda...", + "processing": "Iltimos, kuting...", + "search": "Qidirish:", + "zeroRecords": "Mos keladigan yozuvlar topilmadi", + "paginate": { + "first": "Birinchi", + "last": "Oxirgi", + "next": "Keyingi", + "previous": "Oldingi" + }, + "aria": { + "sortAscending": ": ustunni oʻsish boʻyicha saralash uchun faollashtiring", + "sortDescending": ": ustunni kamayish boʻyicha saralash uchun faollashtiring" + } + }, + "debug": { + "architecture": "Arxitektura", + "chart_this_server": "Diagramma (bu server)", + "containers_info": "Konteyner ma'lumotlari", + "container_running": "Ishlamoqda", + "container_disabled": "Konteyner toʻxtatilgan yoki oʻchirilgan", + "container_stopped": "Toʻxtatilgan", + "cores": "Yadrolar", + "current_time": "Tizim vaqti", + "disk_usage": "Disk foydalanish", + "docs": "Hujjatlar", + "error_show_ip": "Ommaviy IP manzillarini aniqlab boʻlmadi", + "external_logs": "Tashqi jurnallar", + "history_all_servers": "Tarix (barcha serverlar)", + "in_memory_logs": "Xotiradagi jurnallar", + "last_modified": "Oxirgi oʻzgartirilgan", + "log_info": "

    mailcow xotiradagi jurnallar Redis roʻyxatlarida toʻplanadi va har daqiqada hammomilik kamaytirish uchun LOG_LINES (%d) ga qisqartiriladi.\r\n
    Xotiradagi jurnallar doimiy boʻlishi mumkin emas. Xotirada jurnal yurituvchi barcha ilovalar ham Docker daemoniga, demak standart jurnal yuritish drayveriga ham yozadi.\r\n
    Xotiradagi jurnal turi konteynerlar bilan kichik muammolarni nosozliklarni bartaraf etish uchun ishlatilishi kerak.

    \r\n

    Tashqi jurnallar berilgan ilovaning API si orqali toʻplanadi.

    \r\n

    Statik jurnallar asosan faollik jurnallaridir, ular Dockerd ga yozilmaydi, lekin baribir doimiy boʻlishi kerak (API jurnallaridan tashqari).

    ", + "login_time": "Vaqt", + "logs": "Jurnallar", + "memory": "Xotira", + "online_users": "Onlayn foydalanuvchilar", + "restart_container": "Qayta ishga tushirish", + "service": "Xizmat", + "show_ip": "Ommaviy IP ni koʻrsatish", + "size": "Oʻlcham", + "started_at": "Boshlangan vaqt", + "started_on": "Boshlangan sana", + "static_logs": "Statik jurnallar", + "success": "Muvaffaqiyatli", + "system_containers": "Tizim va konteynerlar", + "timezone": "Vaqt mintaqasi", + "uptime": "Ishlash vaqti", + "update_available": "Yangilanish mavjud", + "no_update_available": "Tizim oxirgi versiyada", + "update_failed": "Yangilanishni tekshirib boʻlmadi", + "username": "Foydalanuvchi nomi", + "wip": "Hozir ishlab chiqilmoqda" + }, + "diagnostics": { + "cname_from_a": "Qiymat A/AAAA yozuvidan olingan. Yozuv toʻgʻri resursga ishora qilsa, bu qoʻllab-quvvatlanadi.", + "dns_records": "DNS yozuvlari", + "dns_records_24hours": "DNS ga kiritilgan oʻzgarishlar ushbu sahifada toʻgʻri aks ettirilishi uchun 24 soatgacha vaqt ketishi mumkinligini hisobga oling. Bu DNS yozuvlaringizni qanday sozlash kerakligini osongina koʻrish va barcha yozuvlaringiz DNS da toʻgʻri saqlanganligini tekshirish uchun moʻljallangan.", + "dns_records_data": "Toʻgʻri ma'lumot", + "dns_records_docs": "Iltimos, hujjatlarga ham murojaat qiling.", + "dns_records_name": "Nomi", + "dns_records_status": "Joriy holat", + "dns_records_type": "Tur", + "optional": "Bu yozuv ixtiyoriy." + }, + "edit": { + "acl": "ACL (Ruxsat)", + "active": "Faol", + "admin": "Administratorni tahrirlash", + "advanced_settings": "Kengaytirilgan sozlamalar", + "alias": "Taxallusni tahrirlash", + "allow_from_smtp": "Faqat ushbu IP larga SMTP dan foydalanishga ruxsat berish", + "allow_from_smtp_info": "Barcha joʻnatuvchilarga ruxsat berish uchun boʻsh qoldiring.
    IPv4/IPv6 manzillari va tarmoqlari.", + "allowed_protocols": "Toʻgʻridan-toʻgʻri foydalanuvchi kirishi uchun ruxsat etilgan protokollar (ilova paroli protokollariga taʼsir qilmaydi)", + "app_name": "Ilova nomi", + "app_passwd": "Ilova paroli", + "app_passwd_protocols": "Ilova paroli uchun ruxsat etilgan protokollar", + "automap": "Papkalarni avtomatik xaritalashga harakat qilish (\"Sent items\", \"Sent\" => \"Sent\" va h.k.)", + "backup_mx_options": "Relay sozlamalari", + "bcc_dest_format": "BCC manzili bitta haqiqiy e-pochta manzili boʻlishi kerak.
    Agar bir nechta manzilga nusxa joʻnatish kerak boʻlsa, taxallus yarating va shu yerda ishlating.", + "client_id": "Mijoz ID", + "client_secret": "Mijoz maxfiy soʻzi", + "comment_info": "Maxfiy izoh foydalanuvchiga koʻrinmaydi, ommaviy izoh esa foydalanuvchi koʻrinishida ustiga sichqoncha kelganda maslahat sifatida koʻrsatiladi", + "created_on": "Yaratilgan sana", + "custom_attributes": "Maxsus atributlar", + "delete1": "Tugatilgach manbadan oʻchirish", + "delete2": "Manbada boʻlmagan xabarlarni manzildan oʻchirish", + "delete2duplicates": "Manzildagi dublikatlarni oʻchirish", + "delete_ays": "Iltimos, oʻchirish jarayonini tasdiqlang.", + "description": "Tavsif", + "disable_login": "Kirishni taqiqlash (kiruvchi pochta hali ham qabul qilinadi)", + "domain": "Domenni tahrirlash", + "domain_admin": "Domen administratorini tahrirlash", + "domain_footer": "Domen boʻyicha pastki qism", + "domain_footer_html": "HTML pastki qism", + "domain_footer_info": "Domen boʻyicha pastki qismlar ushbu domendagi manzilga bog'liq barcha chiquvchi xatlarga qoʻshiladi.
    Pastki qism uchun quyidagi oʻzgaruvchilarni ishlatish mumkin:", + "domain_footer_info_vars": { + "auth_user": "{= auth_user =} - MTA tomonidan belgilangan autentifikatsiya qilingan foydalanuvchi nomi", + "from_user": "{= from_user =} - Konvertning Kimdan qismi, masalan \"moo@mailcow.tld\" uchun \"moo\" qaytaradi", + "from_name": "{= from_name =} - Konvertning Kimdan ismi, masalan \"Mailcow <moo@mailcow.tld>\" uchun \"Mailcow\" qaytaradi", + "from_addr": "{= from_addr =} - Konvertning Kimdan manzili qismi", + "from_domain": "{= from_domain =} - Konvertning Kimdan domeni qismi", + "custom": "{= foo =} - Agar pochta qutisida \"bar\" qiymatli \"foo\" maxsus atributi boʻlsa \"bar\" qaytaradi" + }, + "domain_footer_plain": "PLAIN pastki qism", + "domain_footer_skip_replies": "Javob xatlarida pastki qismni e'tiborsiz qoldirish", + "domain_quota": "Domen kvotasi", + "domains": "Domenlar", + "dont_check_sender_acl": "%s domeni uchun joʻnatuvchi tekshiruvini oʻchirish (+ taxallus domenlari)", + "edit_alias_domain": "Taxallus domenini tahrirlash", + "encryption": "Shifrlash", + "exclude": "Obyektlarni istisno qilish (regex)", + "extended_sender_acl": "Tashqi joʻnatuvchi manzillari", + "extended_sender_acl_info": "DKIM domen kaliti mavjud boʻlsa, import qilinishi kerak.
    \r\n Ushbu serverni mos SPF TXT yozuviga qoʻshishni unutmang.
    \r\n Ushbu serverga tashqi manzil bilan ust-ust tushadigan domen yoki taxallus domeni qoʻshilganda, tashqi manzil olib tashlanadi.
    \r\n *@domain.tld sifatida joʻnatishga ruxsat berish uchun @domain.tld dan foydalaning.", + "force_pw_update": "Keyingi kirishda parolni majburiy yangilash", + "force_pw_update_info": "Bu foydalanuvchi faqat %s ga kira oladi. Ilova parollari ishlash holatida qoladi.", + "footer_exclude": "Pastki qismdan istisno qilish", + "full_name": "Toʻliq ism", + "gal": "Global manzillar roʻyxati", + "gal_info": "GAL domenning barcha obyektlarini oʻz ichiga oladi va hech bir foydalanuvchi tomonidan tahrirlanmaydi. Oʻchirilsa, SOGodagi bandlik maʼlumotlari yoʻqoladi! Oʻzgarishlarni qoʻllash uchun SOGoni qayta ishga tushiring.", + "generate": "yaratish", + "grant_types": "Grant turlari", + "hostname": "Xost nomi", + "inactive": "Nofaol", + "internal": "Ichki", + "internal_info": "Ichki taxalluslarga faqat oʻz domeni yoki taxallus domenlaridan kirish mumkin.", + "sender_allowed": "Ushbu taxallus orqali joʻnatishga ruxsat berish", + "sender_allowed_info": "Oʻchirilgan boʻlsa, bu taxallus faqat xat qabul qilishi mumkin. Maxsus pochta qutilariga joʻnatishga ruxsat berish uchun joʻnatuvchi ACL dan foydalaning.", + "kind": "Turi", + "last_modified": "Oxirgi oʻzgartirilgan", + "lookup_mx": "Manzil — MX nomiga mos keladigan muntazam ifoda (.*\\.google\\.com — google.com bilan tugaydigan MX ga yoʻnaltirilgan barcha pochtani ushbu nuqta orqali yoʻnaltirish uchun)", + "mailbox": "Pochta qutisini tahrirlash", + "mailbox_quota_def": "Standart pochta qutisi kvotasi", + "mailbox_relayhost_info": "Faqat pochta qutisi va to'g'ridan-to'g'ri taxalluslarga qoʻllaniladi, domen relay xostini bekor qiladi.", + "mailbox_rename": "Pochta qutisini qayta nomlash", + "mailbox_rename_agree": "Men zaxira nusxasini yaratdim.", + "mailbox_rename_warning": "MUHIM! Pochta qutisini qayta nomlashdan oldin zaxira nusxasi yarating.", + "mailbox_rename_alias": "Taxallusni avtomatik yaratish", + "mailbox_rename_title": "Yangi mahalliy pochta qutisi nomi", + "max_aliases": "Maks. taxalluslar", + "max_mailboxes": "Mumkin boʻlgan maks. pochta qutilari", + "max_quota": "Bitta pochta qutisining maks. kvotasi (MiB)", + "maxage": "Masofadan soʻraladigan xabarlarning maksimal yoshi (kunlarda)
    (0 = yoshni e'tiborsiz qoldirish)", + "maxbytespersecond": "Maks. soniyaga bayt
    (0 = cheksiz)", + "mbox_rl_info": "Bu tezlik chegarasi SASL kirish nomiga qoʻllaniladi, u kirgan foydalanuvchi tomonidan ishlatiladigan har qanday \"from\" manziliga mos keladi. Pochta qutisi tezlik chegarasi domen boʻyicha tezlik chegarasini bekor qiladi.", + "mins_interval": "Oraliq (daqiqa)", + "mta_sts": "MTA-STS", + "mta_sts_info": "MTA-STS — pochta serverlari oʻrtasida xat yetkazib berishni haqiqiy sertifikatli TLS dan foydalanishga majburlovchi standart.
    U DNSSEC mavjud emasligi yoki qoʻllab-quvvatlanmasligi sababli DANE imkoni boʻlmaganda ishlatiladi.
    Eslatma: Agar qabul qiluvchi domen DNSSEC bilan DANE ni qoʻllab-quvvatlasa, DANE har doim afzal koʻriladi — MTA-STS faqat zaxira sifatida ishlaydi.", + "mta_sts_version": "Versiya", + "mta_sts_version_info": "MTA-STS standartining versiyasini belgilaydi — hozirda faqat STSv1 haqiqiy.", + "mta_sts_mode": "Rejim", + "mta_sts_mode_info": "Tanlash uchun uchta rejim mavjud:
    • testing — siyosat faqat kuzatiladi, buzilishlar taʼsir koʻrsatmaydi.
    • enforce — siyosat qatʼiy qoʻllaniladi, haqiqiy TLS siz ulanishlar rad etiladi.
    • none — siyosat eʼlon qilinadi, ammo qoʻllanilmaydi.
    ", + "mta_sts_max_age": "Maks. yosh", + "mta_sts_max_age_info": "Qabul qiluvchi pochta serverlari ushbu siyosatni qayta olishdan oldin keshlashi mumkin boʻlgan vaqt (soniyalarda).", + "mta_sts_mx": "MX server", + "mta_sts_mx_info": "Faqat aniq sanab oʻtilgan pochta server xost nomlariga joʻnatishga ruxsat beradi; joʻnatuvchi MTA DNS MX xost nomi siyosat roʻyxatiga mos kelishini tekshiradi va faqat haqiqiy TLS sertifikati bilan yetkazib berishga ruxsat beradi (MITM ga qarshi himoya qiladi).", + "mta_sts_mx_notice": "Bir nechta MX serverlar belgilanishi mumkin (vergul bilan ajratilgan).", + "multiple_bookings": "Bir nechta bandlovlar", + "none_inherit": "Hech biri / Meros qilib olish", + "nexthop": "Keyingi nuqta", + "password": "Parol", + "password_recovery_email": "Parolni tiklash e-pochtasi", + "password_repeat": "Tasdiqlash paroli (takrorlang)", + "previous": "Oldingi sahifa", + "private_comment": "Maxfiy izoh", + "public_comment": "Ommaviy izoh", + "pushover": "Pushover", + "pushover_evaluate_x_prio": "Yuqori muhimlikdagi xatni eskalatsiya qilish [X-Priority: 1]", + "pushover_info": "Push bildirishnoma sozlamalari %s ga taxalluslar bilan birga (umumiy, umumiy boʻlmagan, teglangan) yetkazib beriladigan barcha toza (spam boʻlmagan) xatlarga qoʻllaniladi.", + "pushover_only_x_prio": "Faqat yuqori muhimlikdagi xatlarni koʻrib chiqish [X-Priority: 1]", + "pushover_sender_array": "Faqat quyidagi joʻnatuvchi e-pochta manzillarini koʻrib chiqish (vergul bilan ajratilgan)", + "pushover_sender_regex": "Quyidagi joʻnatuvchi regexini koʻrib chiqish", + "pushover_text": "Bildirishnoma matni", + "pushover_title": "Bildirishnoma sarlavhasi", + "pushover_sound": "Tovush", + "pushover_vars": "Joʻnatuvchi filtri belgilanmagan boʻlsa, barcha xatlar koʻrib chiqiladi.
    Regex filtrlari va aniq joʻnatuvchi tekshiruvlari alohida belgilanishi mumkin va ketma-ket koʻrib chiqiladi. Ular bir-biriga bogʻliq emas.
    Matn va sarlavha uchun ishlatiladigan oʻzgaruvchilar (ma'lumotlarni himoya qilish siyosatiga e'tibor bering)", + "pushover_verify": "Maʼlumotlarni tekshirish", + "quota_mb": "Kvota (MiB)", + "quota_warning_bcc": "Kvota ogohlantirishi BCC", + "quota_warning_bcc_info": "Ogohlantirishlar alohida nusxa sifatida quyidagi qabul qiluvchilarga yuboriladi. Mavzu mos foydalanuvchi nomi bilan qavs ichida qoʻshib qoʻyiladi, masalan: Kvota ogohlantirishi (user@example.com).", + "ratelimit": "Tezlik chegarasi", + "redirect_uri": "Yoʻnaltirish/Qaytarish URL", + "relay_all": "Barcha qabul qiluvchilarni relay qilish", + "relay_all_info": "↪ Agar barcha qabul qiluvchilarni relay qilmaslikni tanlasangiz, relay qilinishi kerak boʻlgan har bir qabul qiluvchi uchun (\"koʻr\") pochta qutisi qoʻshishingiz kerak boʻladi.", + "relay_domain": "Ushbu domenni relay qilish", + "relay_transport_info": "
    Maʼlumot
    Bu domen uchun maxsus manzilga transport xaritalarini belgilashingiz mumkin. Agar oʻrnatilmagan boʻlsa, MX qidiruvi amalga oshiriladi.", + "relay_unknown_only": "Faqat mavjud boʻlmagan pochta qutilarini relay qilish. Mavjud pochta qutilari mahalliy yetkazib beriladi.", + "relayhost": "Joʻnatuvchiga bogʻliq transportlar", + "remove": "Olib tashlash", + "resource": "Resurs", + "save": "Oʻzgarishlarni saqlash", + "scope": "Qamrov", + "sender_acl": "Quyidagi sifatida joʻnatishga ruxsat berish", + "sender_acl_disabled": "Joʻnatuvchi tekshiruvi oʻchirilgan", + "sender_acl_info": "Agar A pochta qutisi foydalanuvchisi B pochta qutisi foydalanuvchisi sifatida joʻnatishga ruxsat etilgan boʻlsa, joʻnatuvchi manzili SOGo da tanlanadigan \"from\" maydoni sifatida avtomatik koʻrsatilmaydi.
    \r\n B pochta qutisi foydalanuvchisi A pochta qutisi foydalanuvchisi oʻz manzilini joʻnatuvchi sifatida tanlay olishi uchun SOGo da delegatsiya yaratishi kerak. SOGo da pochta qutisini delegatsiya qilish uchun pochta koʻrinishida yuqori chap qismdagi pochta qutisi nomingiz oʻng tomonidagi menyudan (uch nuqta) foydalaning. Bu xatti-harakat taxallus manzillariga qoʻllanmaydi.", + "sieve_desc": "Qisqa tavsif", + "sieve_type": "Filtr turi", + "skipcrossduplicates": "Papkalararo dublikat xabarlarni oʻtkazib yuborish (birinchi kelgan, birinchi xizmatlanadi)", + "sogo_access": "SOGo ga toʻgʻridan-toʻgʻri yoʻnaltirish", + "sogo_access_info": "Tizimga kirgandan keyin foydalanuvchi avtomatik ravishda SOGo ga yoʻnaltiriladi.", + "sogo_visible": "Taxallus SOGo da koʻrinadi", + "sogo_visible_info": "Bu variant faqat SOGo da koʻrsatilishi mumkin boʻlgan obyektlarga taʼsir qiladi (kamida bitta mahalliy pochta qutisiga ishora qiluvchi umumiy yoki umumiy boʻlmagan taxallus manzillari). Yashirilgan boʻlsa, taxallus SOGo da tanlanadigan joʻnatuvchi sifatida koʻrinmaydi.", + "spam_alias": "Vaqt cheklangan taxallus manzillarini yaratish yoki oʻzgartirish", + "spam_filter": "Spam filtri", + "spam_policy": "Elementlarni ruxsat etilgan/qora roʻyxatga qoʻshish yoki olib tashlash", + "spam_score": "Maxsus spam bahosini oʻrnatish", + "subfolder2": "Manzilning subpapkasiga sinxronizatsiya qilish
    (boʻsh = subpapkadan foydalanmang)", + "syncjob": "Sinxronizatsiya vazifasini tahrirlash", + "target_address": "Yoʻnaltirish manzil(lar)i (vergul bilan ajratilgan)", + "target_domain": "Maqsad domen", + "timeout1": "Masofaviy xostga ulanish vaqtining tugashi", + "timeout2": "Mahalliy xostga ulanish vaqtining tugashi", + "title": "Obyektni tahrirlash", + "unchanged_if_empty": "Oʻzgartirilmagan boʻlsa, boʻsh qoldiring", + "username": "Foydalanuvchi nomi", + "validate_save": "Tekshirish va saqlash" + }, + "fido2": { + "confirm": "Tasdiqlash", + "fido2_auth": "FIDO2 bilan kirish", + "fido2_success": "Qurilma muvaffaqiyatli roʻyxatdan oʻtkazildi", + "fido2_validation_failed": "Tekshirish muvaffaqiyatsiz", + "fn": "Doʻstona nom", + "known_ids": "Maʼlum ID lar", + "none": "Oʻchirilgan", + "register_status": "Roʻyxatga olish holati", + "rename": "Qayta nomlash", + "set_fido2": "FIDO2 qurilmasini roʻyxatdan oʻtkazish", + "set_fido2_touchid": "Apple M1 da Touch ID ni roʻyxatdan oʻtkazish", + "set_fn": "Doʻstona nomni oʻrnatish", + "start_fido2_validation": "FIDO2 tekshiruvini boshlash" + }, + "footer": { + "cancel": "Bekor qilish", + "confirm_delete": "Oʻchirishni tasdiqlash", + "delete_now": "Hozir oʻchirish", + "delete_these_items": "Iltimos, quyidagi obyekt ID siga oʻzgarishlaringizni tasdiqlang", + "hibp_check": "haveibeenpwned.com ga qarshi tekshirish", + "hibp_nok": "Mos keldi! Bu xavfli parol boʻlishi mumkin!", + "hibp_ok": "Mos kelmadi.", + "loading": "Iltimos, kuting...", + "nothing_selected": "Hech narsa tanlanmadi", + "restart_container": "Konteynerni qayta ishga tushirish", + "restart_container_info": "Muhim: Yumshoq qayta ishga tushirish biroz vaqt olishi mumkin, iltimos uning tugashini kuting.", + "restart_now": "Hozir qayta ishga tushirish", + "restarting_container": "Konteyner qayta ishga tushirilmoqda, bu biroz vaqt olishi mumkin" + }, + "header": { + "administration": "Konfiguratsiya va tafsilotlar", + "apps": "Ilovalar", + "debug": "Maʼlumot", + "email": "E-pochta", + "mailcow_system": "Tizim", + "mailcow_config": "Konfiguratsiya", + "quarantine": "Karantin", + "restart_netfilter": "Netfilterni qayta ishga tushirish", + "restart_sogo": "SOGoni qayta ishga tushirish", + "user_settings": "Foydalanuvchi sozlamalari" + }, + "info": { + "awaiting_tfa_confirmation": "TFA tasdiqlanishi kutilmoqda", + "no_action": "Amal qoʻllanib boʻlmaydi", + "session_expires": "Sessiyangiz taxminan 15 soniyadan keyin tugaydi" + }, + "login": { + "back_to_mailcow": "mailcow ga qaytish", + "delayed": "Kirish %s soniyaga kechiktirildi.", + "fido2_webauthn": "FIDO2/WebAuthn kirish", + "forgot_password": "> Parolni unutdingizmi?", + "invalid_pass_reset_token": "Parolni tiklash tokeni noto'g'ri yoki muddati tugagan.
    Iltimos, yangi parolni tiklash havolasini soʻrang.", + "login": "Kirish", + "login_linkstext": "Toʻgʻri kirish emasmi?", + "login_usertext": "Foydalanuvchi sifatida kirish", + "login_domainadmintext": "Domen administratori sifatida kirish", + "login_admintext": "Administrator sifatida kirish", + "login_user": "Foydalanuvchi kirishi", + "login_dadmin": "Domen-administratori kirishi", + "login_admin": "Administrator kirishi", + "mobileconfig_info": "Soʻralgan Apple ulanish profilini yuklab olish uchun pochta qutisi foydalanuvchisi sifatida kiring.", + "new_password": "Yangi parol", + "new_password_confirm": "Yangi parolni tasdiqlang", + "other_logins": "yoki quyidagi orqali kiring", + "password": "Parol", + "reset_password": "Parolni tiklash", + "request_reset_password": "Parol oʻzgartirishni soʻrash", + "username": "Foydalanuvchi nomi", + "email": "E-pochta manzili" + }, + "mailbox": { + "action": "Amal", + "activate": "Faollashtirish", + "active": "Faol", + "add": "Qoʻshish", + "add_alias": "Taxallus qoʻshish", + "add_alias_expand": "Taxallusni taxallus domenlari boʻylab kengaytirish", + "add_bcc_entry": "BCC xaritasi qoʻshish", + "add_domain": "Domen qoʻshish", + "add_domain_alias": "Domen taxallusini qoʻshish", + "add_domain_record_first": "Iltimos, avval domen qoʻshing", + "add_filter": "Filtr qoʻshish", + "add_mailbox": "Pochta qutisi qoʻshish", + "add_recipient_map_entry": "Qabul qiluvchi xaritasini qoʻshish", + "add_resource": "Resurs qoʻshish", + "add_template": "Shablon qoʻshish", + "add_tls_policy_map": "TLS siyosati xaritasini qoʻshish", + "address_rewriting": "Manzilni qayta yozish", + "alias": "Taxallus", + "alias_domain_alias_hint": "Taxalluslar domen taxalluslariga avtomatik qoʻllanilmaydi. my-alias@domain taxallus manzili my-alias@alias-domain manzilini qoplamaydi (bu yerda \"alias-domain\" — \"domain\" uchun tasavvur qilingan taxallus domeni).
    Tashqi pochta qutisiga xatni yoʻnaltirish uchun sieve filtridan foydalaning (\"Filtrlar\" yorligʻiga qarang yoki SOGo -> Forwarder dan foydalaning). Yoʻq taxalluslarni avtomatik qoʻshish uchun \"Taxallusni taxallus domenlari boʻylab kengaytirish\" dan foydalaning.", + "alias_domain_backupmx": "Relay domeni uchun taxallus domeni nofaol", + "aliases": "Taxalluslar", + "all_domains": "Barcha domenlar", + "allow_from_smtp": "Faqat ushbu IP larga SMTP dan foydalanishga ruxsat berish", + "allow_from_smtp_info": "Barcha joʻnatuvchilarga ruxsat berish uchun boʻsh qoldiring.
    IPv4/IPv6 manzillari va tarmoqlari.", + "allowed_protocols": "Ruxsat etilgan protokollar", + "backup_mx": "Relay domeni", + "bcc": "BCC", + "bcc_destination": "BCC manzili", + "bcc_destinations": "BCC manzili", + "bcc_info": "BCC xaritalari barcha xabarlarning nusxalarini boshqa manzilga jimgina yoʻnaltirish uchun ishlatiladi. Qabul qiluvchi xaritasi turi yozuvi mahalliy manzil xatning qabul qiluvchisi sifatida ishlaganda ishlatiladi. Joʻnatuvchi xaritalari ham xuddi shu tamoyilga muvofiq ishlaydi.
    \r\n Mahalliy manzilga yetkazib berish muvaffaqiyatsiz boʻlsa, xabar berilmaydi.", + "bcc_local_dest": "Mahalliy manzil", + "bcc_map": "BCC xaritasi", + "bcc_map_type": "BCC turi", + "bcc_maps": "BCC xaritalari", + "bcc_rcpt_map": "Qabul qiluvchi xaritasi", + "bcc_sender_map": "Joʻnatuvchi xaritasi", + "bcc_to_rcpt": "Qabul qiluvchi xaritasi turiga oʻtish", + "bcc_to_sender": "Joʻnatuvchi xaritasi turiga oʻtish", + "bcc_type": "BCC turi", + "booking_null": "Doimo boʻsh sifatida koʻrsatish", + "booking_0_short": "Doimo boʻsh", + "booking_custom": "Maxsus bandlovlar soni bilan qatʼiy cheklash", + "booking_custom_short": "Qatʼiy chegara", + "booking_ltnull": "Cheksiz, lekin bandlangan boʻlsa band sifatida koʻrsatish", + "booking_lt0_short": "Yumshoq chegara", + "catch_all": "Hammasini-qabul qilish", + "created_on": "Yaratilgan sana", + "daily": "Kunlik", + "deactivate": "Oʻchirish", + "description": "Tavsif", + "disable_login": "Kirishni taqiqlash (kiruvchi pochta hali ham qabul qilinadi)", + "disable_x": "Oʻchirish", + "dkim_domains_selector": "Tanlovchi", + "dkim_key_length": "DKIM kalit uzunligi (bit)", + "domain": "Domen", + "domain_admins": "Domen administratorlari", + "domain_aliases": "Domen taxalluslari", + "domain_templates": "Domen shablonlari", + "domain_quota": "Kvota", + "domain_quota_total": "Umumiy domen kvotasi", + "domains": "Domenlar", + "edit": "Tahrirlash", + "empty": "Natijalar yoʻq", + "enable_x": "Yoqish", + "excludes": "Istisnolar", + "filter_table": "Filtr jadvali", + "filters": "Filtrlar", + "fname": "Toʻliq ism", + "force_pw_update": "Keyingi kirishda parolni majburiy yangilash", + "gal": "Global manzillar roʻyxati", + "goto_ham": "ham sifatida oʻrgatish", + "goto_spam": "spam sifatida oʻrgatish", + "hourly": "Soatlik", + "iam": "Identifikatsiya provayderi", + "in_use": "Foydalanilmoqda (%)", + "inactive": "Nofaol", + "insert_preset": "Misol presetini kiritish \"%s\"", + "internal": "Ichki", + "kind": "Turi", + "last_mail_login": "Oxirgi pochta kirishi", + "last_modified": "Oxirgi oʻzgartirilgan", + "last_pw_change": "Oxirgi parol oʻzgarishi", + "last_run": "Oxirgi ishga tushirilgan", + "last_run_reset": "Keyingisini rejalashtirish", + "mailbox": "Pochta qutisi", + "mailbox_defaults": "Standart sozlamalar", + "mailbox_defaults_info": "Yangi pochta qutilari uchun standart sozlamalarni belgilang.", + "mailbox_defquota": "Standart pochta qutisi oʻlchami", + "mailbox_templates": "Pochta qutisi shablonlari", + "mailbox_quota": "Pochta qutisining maks. oʻlchami", + "mailboxes": "Pochta qutilari", + "max_aliases": "Maks. taxalluslar", + "max_mailboxes": "Mumkin boʻlgan maks. pochta qutilari", + "max_quota": "Bitta pochta qutisining maks. kvotasi", + "mins_interval": "Oraliq (daqiqa)", + "msg_num": "Xabar #", + "multiple_bookings": "Bir nechta bandlovlar", + "never": "Hech qachon", + "no": "✕", + "no_record": "%s obyekti uchun yozuv yoʻq", + "no_record_single": "Yozuv yoʻq", + "open_logs": "Jurnallarni ochish", + "owner": "Egasi", + "private_comment": "Maxfiy izoh", + "public_comment": "Ommaviy izoh", + "q_add_header": "Junk papkasiga koʻchirilganda", + "q_all": " Junk papkasiga koʻchirilganda va rad etilganda", + "q_reject": "rad etilganda", + "quarantine_category": "Karantin bildirishnoma turkumi", + "quarantine_notification": "Karantin bildirishnomalari", + "quick_actions": "Amallar", + "recipient": "Qabul qiluvchi", + "recipient_map": "Qabul qiluvchi xaritasi", + "recipient_map_info": "Qabul qiluvchi xaritalari xabar yetkazib berilishidan oldin manzilni almashtirish uchun ishlatiladi.", + "recipient_map_new": "Yangi qabul qiluvchi", + "recipient_map_new_info": "Qabul qiluvchi xaritasi manzili haqiqiy e-pochta manzili yoki domen nomi boʻlishi kerak.", + "recipient_map_old": "Asl qabul qiluvchi", + "recipient_map_old_info": "Qabul qiluvchi xaritasining asl manzili haqiqiy e-pochta manzili yoki domen nomi boʻlishi kerak.", + "recipient_maps": "Qabul qiluvchi xaritalari", + "relay_all": "Barcha qabul qiluvchilarni relay qilish", + "relay_unknown": "Nomaʼlum pochta qutilarini relay qilish", + "remove": "Olib tashlash", + "resources": "Resurslar", + "running": "Ishlamoqda", + "sender": "Joʻnatuvchi", + "set_postfilter": "Postfiltr sifatida belgilash", + "set_prefilter": "Prefiltr sifatida belgilash", + "sieve_info": "Bitta foydalanuvchi uchun bir nechta filtrlarni saqlash mumkin, lekin bir vaqtning oʻzida faqat bitta prefiltr va bitta postfiltr faol boʻlishi mumkin.
    \r\nHar bir filtr tasvirlangan tartibda qayta ishlanadi. Muvaffaqiyatsiz skript ham, chiqarilgan \"keep;\" ham keyingi skriptlarning qayta ishlanishini toʻxtatmaydi. Global sieve skriptlariga oʻzgartirishlar Dovecot ni qayta ishga tushirishni keltirib chiqaradi.

    Global sieve prefilter • Prefilter • Foydalanuvchi skriptlari • Postfilter • Global sieve postfilter", + "sieve_preset_1": "Xavfli boʻlishi mumkin boʻlgan fayl turlari bilan xatlarni rad etish", + "sieve_preset_2": "Aniq joʻnatuvchining xatini har doim oʻqilgan deb belgilash", + "sieve_preset_3": "Jimgina rad etish, keyingi sieve qayta ishlashini toʻxtatish", + "sieve_preset_4": "INBOX ga joylash, sieve filtrlari tomonidan keyingi qayta ishlashni oʻtkazib yuborish", + "sieve_preset_5": "Avto-javob (taʼtil)", + "sieve_preset_6": "Javob bilan xatni rad etish", + "sieve_preset_7": "Yoʻnaltirish va saqlash/tashlash", + "sieve_preset_8": "Aniq joʻnatuvchining xatini yoʻnaltirish, oʻqilgan deb belgilash va subpapkaga saralash", + "sieve_preset_header": "Iltimos, quyidagi misol presetlariga qarang. Batafsil maʼlumot uchun Wikipediaga qarang.", + "sogo_visible": "Taxallus SOGo da koʻrinadi", + "sogo_visible_n": "Taxallusni SOGo da yashirish", + "sogo_visible_y": "Taxallusni SOGo da koʻrsatish", + "spam_aliases": "Vaqt. taxallus", + "stats": "Statistika", + "status": "Holat", + "sync_jobs": "Sinxronizatsiya vazifalari", + "syncjob_check_log": "Jurnalni tekshirish", + "syncjob_last_run_result": "Oxirgi ishga tushirilgan natija", + "syncjob_EX_OK": "Muvaffaqiyatli", + "syncjob_EXIT_CONNECTION_FAILURE": "Ulanish muammosi", + "syncjob_EXIT_TLS_FAILURE": "Shifrlangan ulanish bilan muammo", + "syncjob_EXIT_AUTHENTICATION_FAILURE": "Autentifikatsiya muammosi", + "syncjob_EXIT_OVERQUOTA": "Maqsad pochta qutisi kvotadan oshib ketdi", + "syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Masofaviy serverga ulanib boʻlmadi", + "syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Notoʻgʻri foydalanuvchi nomi yoki parol", + "table_size": "Jadval oʻlchami", + "table_size_show_n": "%s ta element koʻrsatish", + "target_address": "Yoʻnaltirish manzili", + "target_domain": "Maqsad domen", + "templates": "Shablonlar", + "template": "Shablon", + "tls_enforce_in": "Kiruvchi TLS ni majburlash", + "tls_enforce_out": "Chiquvchi TLS ni majburlash", + "tls_map_dest": "Manzil", + "tls_map_dest_info": "Misollar: example.org, .example.org, [mail.example.org]:25", + "tls_map_parameters": "Parametrlar", + "tls_map_parameters_info": "Boʻsh yoki parametrlar, masalan: protocols=!SSLv2 ciphers=medium exclude=3DES", + "tls_map_policy": "Siyosat", + "tls_policy_maps": "TLS siyosati xaritalari", + "tls_policy_maps_enforced_tls": "Bu siyosatlar chiquvchi TLS ulanishlarini majburlovchi pochta qutisi foydalanuvchilari uchun ham xatti-harakatni bekor qiladi. Agar quyida siyosat boʻlmasa, bu foydalanuvchilar smtp_tls_mandatory_protocols va smtp_tls_mandatory_ciphers sifatida belgilangan standart qiymatlarni qoʻllaydi.", + "tls_policy_maps_info": "Bu siyosat xaritasi foydalanuvchining TLS siyosati sozlamalaridan mustaqil ravishda chiquvchi TLS transport qoidalarini bekor qiladi.
    \r\n Batafsil maʼlumot uchun \"smtp_tls_policy_maps\" hujjatlarini tekshiring.", + "tls_policy_maps_long": "Chiquvchi TLS siyosati xaritasi bekor qilishlari", + "toggle_all": "Hammasini almashtirish", + "username": "Foydalanuvchi nomi", + "waiting": "Kutilmoqda", + "weekly": "Haftalik", + "yes": "✓" + }, + "oauth2": { + "access_denied": "OAuth2 orqali kirishga ruxsat berish uchun pochta qutisi egasi sifatida kiring.", + "authorize_app": "Ilovaga ruxsat berish", + "deny": "Rad etish", + "permit": "Ilovaga ruxsat berish", + "profile": "Profil", + "profile_desc": "Shaxsiy maʼlumotlarni koʻrish: foydalanuvchi nomi, toʻliq ism, yaratilgan, oʻzgartirilgan, faol", + "scope_ask_permission": "Ilova quyidagi ruxsatlarni soʻradi" + }, + "quarantine": { + "action": "Amal", + "atts": "Ilovalar", + "check_hash": "VT da fayl hashini qidirish", + "confirm": "Tasdiqlash", + "confirm_delete": "Ushbu elementni oʻchirishni tasdiqlang.", + "danger": "Xavf", + "deliver_inbox": "Inbox ga yetkazib berish", + "disabled_by_config": "Joriy tizim konfiguratsiyasi karantin funksiyasini oʻchirib qoʻyadi. Iltimos, karantin elementlari uchun \"pochta qutisi boʻyicha saqlash\" va \"maksimal oʻlcham\"ni oʻrnating.", + "download_eml": "Yuklab olish (.eml)", + "empty": "Natijalar yoʻq", + "high_danger": "Yuqori", + "info": "Maʼlumot", + "junk_folder": "Junk papkasi", + "learn_spam_delete": "Spam sifatida oʻrgatish va oʻchirish", + "low_danger": "Past", + "medium_danger": "Oʻrtacha", + "neutral_danger": "Neytral", + "notified": "Xabar berildi", + "qhandler_success": "Soʻrov tizimga muvaffaqiyatli yuborildi. Endi oynani yopishingiz mumkin.", + "qid": "Rspamd QID", + "qinfo": "Karantin tizimi rad etilgan xatni (joʻnatuvchiga xat yetkazib berilgandek taassurot bermay) hamda pochta qutisining Junk papkasiga nusxa sifatida yetkazib berilgan xatni maʼlumotlar bazasiga saqlaydi.\r\n
    \"Spam sifatida oʻrgatish va oʻchirish\" xabarni Bayesian teoremasi orqali spam sifatida oʻrgatadi va kelajakda oʻxshash xabarlarni rad etish uchun fuzzy hashlarni hisoblaydi.\r\n
    Bir nechta xabarlarni oʻrgatish — tizimingizga qarab — vaqt talab qilishi mumkinligini unutmang.
    Qora roʻyxatdagi elementlar karantindan istisno qilinadi.", + "qitem": "Karantin elementi", + "quarantine": "Karantin", + "quick_actions": "Amallar", + "quick_delete_link": "Tezkor oʻchirish havolasini ochish", + "quick_info_link": "Maʼlumot havolasini ochish", + "quick_release_link": "Tezkor chiqarish havolasini ochish", + "rcpt": "Qabul qiluvchi", + "received": "Qabul qilingan", + "recipients": "Qabul qiluvchilar", + "refresh": "Yangilash", + "rejected": "Rad etilgan", + "release": "Chiqarish", + "release_body": "Xabaringizni ushbu xabarga eml fayl sifatida ilova qildik.", + "release_subject": "Potensial xavfli karantin elementi %s", + "remove": "Olib tashlash", + "rewrite_subject": "Mavzuni qayta yozish", + "rspamd_result": "Rspamd natijasi", + "sender": "Joʻnatuvchi (SMTP)", + "sender_header": "Joʻnatuvchi (\"From\" sarlavhasi)", + "settings_info": "Karantinga olinadigan maksimal elementlar soni: %s
    Maksimal e-pochta oʻlchami: %s MiB", + "show_item": "Elementni koʻrsatish", + "spam": "Spam", + "spam_score": "Baho", + "subj": "Mavzu", + "table_size": "Jadval oʻlchami", + "table_size_show_n": "%s ta element koʻrsatish", + "text_from_html_content": "Mazmun (oʻzgartirilgan html)", + "text_plain_content": "Mazmun (text/plain)", + "toggle_all": "Hammasini almashtirish", + "type": "Tur" + }, + "queue": { + "delete": "Hammasini oʻchirish", + "flush": "Navbatni tozalash", + "info": "Pochta navbati yetkazib berilishini kutayotgan barcha xatlarni oʻz ichiga oladi. Agar e-pochta uzoq vaqt davomida pochta navbatida tursa, u tizim tomonidan avtomatik ravishda oʻchiriladi.
    Tegishli xatning xato xabari xat nima uchun yetkazib berilmaganligi haqida maʼlumot beradi.", + "legend": "Pochta navbati amallari funksiyalari:", + "ays": "Iltimos, joriy navbatdagi barcha elementlarni oʻchirishni tasdiqlang.", + "deliver_mail": "Yetkazib berish", + "deliver_mail_legend": "Tanlangan xatlarni qayta yetkazib berishga harakat qiladi.", + "hold_mail": "Ushlab turish", + "hold_mail_legend": "Tanlangan xatlarni ushlab turadi. (Keyingi yetkazib berish urinishlarining oldini oladi)", + "queue_manager": "Navbat menejeri", + "show_message": "Xabarni koʻrsatish", + "unban": "navbatdan blokdan chiqarish", + "unhold_mail": "Ushlab turishni bekor qilish", + "unhold_mail_legend": "Tanlangan xatlarni yetkazib berish uchun chiqaradi. (Avval ushlab turish talab qilinadi)" + }, + "ratelimit": { + "disabled": "Oʻchirilgan", + "second": "xabarlar / soniya", + "minute": "xabarlar / daqiqa", + "hour": "xabarlar / soat", + "day": "xabarlar / kun" + }, + "start": { + "help": "Yordam panelini koʻrsatish/yashirish", + "imap_smtp_server_auth_info": "Iltimos, toʻliq e-pochta manzilingiz va PLAIN autentifikatsiya mexanizmidan foydalaning.
    \r\nKirish maʼlumotlaringiz server tomonidagi majburiy shifrlash orqali shifrlanadi." + }, + "success": { + "acl_saved": "%s obyekti uchun ACL saqlandi", + "admin_added": "Administrator %s qoʻshildi", + "admin_api_modified": "API ga oʻzgarishlar saqlandi", + "admin_modified": "Administrator oʻzgarishlari saqlandi", + "admin_removed": "Administrator %s olib tashlandi", + "alias_added": "Taxallus manzili %s (%d) qoʻshildi", + "alias_domain_removed": "Taxallus domeni %s olib tashlandi", + "alias_modified": "Taxallus manzili %s ga oʻzgarishlar saqlandi", + "alias_removed": "Taxallus %s olib tashlandi", + "aliasd_added": "Taxallus domeni %s qoʻshildi", + "aliasd_modified": "Taxallus domeni %s ga oʻzgarishlar saqlandi", + "app_links": "Ilova havolalariga oʻzgarishlar saqlandi", + "app_passwd_added": "Yangi ilova paroli qoʻshildi", + "app_passwd_removed": "Ilova paroli ID %s olib tashlandi", + "bcc_deleted": "BCC xaritasi yozuvlari oʻchirildi: %s", + "bcc_edited": "BCC xaritasi yozuvi %s tahrirlandi", + "bcc_saved": "BCC xaritasi yozuvi saqlandi", + "cors_headers_edited": "CORS sozlamalari saqlandi", + "custom_login_modified": "Kirishni sozlash muvaffaqiyatli saqlandi", + "db_init_complete": "Maʼlumotlar bazasini ishga tushirish tugadi", + "delete_filter": "Filtr ID %s oʻchirildi", + "delete_filters": "Filtrlar oʻchirildi: %s", + "deleted_syncjob": "Sinxronizatsiya vazifasi ID %s oʻchirildi", + "deleted_syncjobs": "Sinxronizatsiya vazifalari oʻchirildi: %s", + "dkim_added": "DKIM kaliti %s saqlandi", + "domain_add_dkim_available": "DKIM kaliti allaqachon mavjud edi", + "dkim_duplicated": "%s domeni uchun DKIM kaliti %s ga nusxalandi", + "dkim_removed": "DKIM kaliti %s olib tashlandi", + "domain_added": "Domen %s qoʻshildi", + "domain_admin_added": "Domen administratori %s qoʻshildi", + "domain_admin_modified": "Domen administratori %s ga oʻzgarishlar saqlandi", + "domain_admin_removed": "Domen administratori %s olib tashlandi", + "domain_footer_modified": "Domen pastki qismi %s ga oʻzgarishlar saqlandi", + "domain_modified": "Domen %s ga oʻzgarishlar saqlandi", + "domain_removed": "Domen %s olib tashlandi", + "dovecot_restart_success": "Dovecot muvaffaqiyatli qayta ishga tushirildi", + "eas_reset": "%s foydalanuvchisi uchun ActiveSync qurilmalari tiklandi", + "f2b_banlist_refreshed": "Banlist ID muvaffaqiyatli yangilandi.", + "f2b_modified": "Fail2ban parametrlariga oʻzgarishlar saqlandi", + "forwarding_host_added": "Yoʻnaltirish xosti %s qoʻshildi", + "forwarding_host_removed": "Yoʻnaltirish xosti %s olib tashlandi", + "global_filter_written": "Filtr faylga muvaffaqiyatli yozildi", + "hash_deleted": "Hash oʻchirildi", + "iam_test_connection": "Ulanish muvaffaqiyatli", + "ip_check_opt_in_modified": "IP tekshiruvi muvaffaqiyatli saqlandi", + "item_deleted": "Element %s muvaffaqiyatli oʻchirildi", + "item_released": "Element %s chiqarildi", + "items_deleted": "Element %s muvaffaqiyatli oʻchirildi", + "items_released": "Tanlangan elementlar chiqarildi", + "learned_ham": "ID %s ham sifatida muvaffaqiyatli oʻrgatildi", + "license_modified": "Litsenziyaga oʻzgarishlar saqlandi", + "logged_in_as": "%s sifatida tizimga kirildi", + "mailbox_added": "Pochta qutisi %s qoʻshildi", + "mailbox_modified": "Pochta qutisi %s ga oʻzgarishlar saqlandi", + "mailbox_removed": "Pochta qutisi %s olib tashlandi", + "mailbox_renamed": "Pochta qutisi %s dan %s ga qayta nomlandi", + "nginx_reloaded": "Nginx qayta yuklandi", + "object_modified": "Obyekt %s ga oʻzgarishlar saqlandi", + "password_policy_saved": "Parol siyosati muvaffaqiyatli saqlandi", + "password_changed_success": "Parol muvaffaqiyatli oʻzgartirildi", + "pushover_settings_edited": "Pushover sozlamalari muvaffaqiyatli oʻrnatildi, iltimos maʼlumotlarni tekshiring.", + "qlearn_spam": "Xabar ID %s spam sifatida oʻrgatildi va oʻchirildi", + "queue_command_success": "Navbat buyrugʻi muvaffaqiyatli yakunlandi", + "recipient_map_entry_deleted": "Qabul qiluvchi xaritasi ID %s oʻchirildi", + "recipient_map_entry_saved": "Qabul qiluvchi xaritasi yozuvi \"%s\" saqlandi", + "recovery_email_sent": "Tiklash e-pochtasi %s ga yuborildi", + "relayhost_added": "Xarita yozuvi %s qoʻshildi", + "relayhost_removed": "Xarita yozuvi %s olib tashlandi", + "reset_main_logo": "Standart logotipga qaytarildi", + "resource_added": "Resurs %s qoʻshildi", + "resource_modified": "Pochta qutisi %s ga oʻzgarishlar saqlandi", + "resource_removed": "Resurs %s olib tashlandi", + "rl_saved": "%s obyekti uchun tezlik chegarasi saqlandi", + "rspamd_ui_pw_set": "Rspamd UI paroli muvaffaqiyatli oʻrnatildi", + "saved_settings": "Sozlamalar saqlandi", + "settings_map_added": "Sozlamalar xaritasi yozuvi qoʻshildi", + "settings_map_removed": "Sozlamalar xaritasi ID %s olib tashlandi", + "sogo_profile_reset": "%s foydalanuvchisi uchun SOGo profili tiklandi", + "template_added": "Shablon %s qoʻshildi", + "template_modified": "Shablon %s ga oʻzgarishlar saqlandi", + "template_removed": "Shablon ID %s oʻchirildi", + "tls_policy_map_entry_deleted": "TLS siyosati xaritasi ID %s oʻchirildi", + "tls_policy_map_entry_saved": "TLS siyosati xaritasi yozuvi \"%s\" saqlandi", + "ui_texts": "UI matnlariga oʻzgarishlar saqlandi", + "upload_success": "Fayl muvaffaqiyatli yuklandi", + "verified_fido2_login": "FIDO2 kirishi tasdiqlandi", + "verified_totp_login": "TOTP kirishi tasdiqlandi", + "verified_webauthn_login": "WebAuthn kirishi tasdiqlandi", + "verified_yotp_login": "Yubico OTP kirishi tasdiqlandi" + }, + "tfa": { + "authenticators": "Autentifikatorlar", + "api_register": "%s Yubico Cloud API dan foydalanadi. Iltimos, kalitingiz uchun API kalitini bu yerdan oling", + "confirm": "Tasdiqlash", + "confirm_totp_token": "Iltimos, yaratilgan tokenni kiritib oʻzgarishlaringizni tasdiqlang", + "delete_tfa": "TFA ni oʻchirish", + "disable_tfa": "Keyingi muvaffaqiyatli kirishgacha TFA ni oʻchirish", + "force_tfa": "Kirishda 2FA roʻyxatdan oʻtkazishni majburlash", + "force_tfa_info": "Foydalanuvchidan panelga kirishdan oldin ikki faktorli autentifikatsiyani sozlash talab qilinadi.", + "setup_title": "Ikki faktorli autentifikatsiya talab qilinadi", + "setup_required": "Hisobingiz ikki faktorli autentifikatsiyani talab qiladi. Davom etish uchun 2FA usulini sozlang.", + "cancel_setup": "Bekor qilish va chiqish", + "enter_qr_code": "Agar qurilmangiz QR kodlarni skanerlay olmasa, TOTP kodingiz", + "error_code": "Xato kodi", + "init_webauthn": "Ishga tushirilmoqda, iltimos kuting...", + "key_id": "Qurilmangiz uchun identifikator", + "key_id_totp": "Kalitingiz uchun identifikator", + "none": "Oʻchirish", + "reload_retry": "- (xato davom etsa, brauzerni qayta yuklang)", + "scan_qr_code": "Iltimos, quyidagi kodni autentifikator ilovangiz bilan skanerlang yoki kodni qoʻlda kiriting.", + "select": "Iltimos, tanlang", + "set_tfa": "Ikki faktorli autentifikatsiya usulini oʻrnatish", + "start_webauthn_validation": "Tekshiruvni boshlash", + "tfa": "Ikki faktorli autentifikatsiya", + "tfa_token_invalid": "TFA tokeni noto'g'ri", + "totp": "Vaqt asosidagi OTP (Google Authenticator, Authy va h.k.)", + "u2f_deprecated": "Sizning kalitingiz eskirgan U2F usulidan foydalanib roʻyxatdan oʻtkazilganga oʻxshaydi. Biz siz uchun ikki faktorli autentifikatsiyani oʻchirib qoʻyamiz va kalitingizni oʻchiramiz.", + "u2f_deprecated_important": "Iltimos, kalitingizni admin panelida yangi WebAuthn usuli bilan roʻyxatdan oʻtkazing.", + "webauthn": "WebAuthn autentifikatsiya", + "waiting_usb_auth": "USB qurilmasi kutilmoqda...

    Iltimos, hozir USB qurilmangizdagi tugmani bosing.", + "waiting_usb_register": "USB qurilmasi kutilmoqda...

    Iltimos, yuqorida parolingizni kiriting va USB qurilmangizdagi tugmani bosib roʻyxatga olinishingizni tasdiqlang.", + "yubi_otp": "Yubico OTP autentifikatsiya" + }, + "user": { + "action": "Amal", + "active": "Faol", + "active_sieve": "Faol filtr", + "advanced_settings": "Kengaytirilgan sozlamalar", + "alias": "Taxallus", + "alias_create_random": "Tasodifiy taxallus yaratish", + "alias_extend_all": "Taxalluslarni 1 soatga uzaytirish", + "alias_full_date": "d.m.Y, H:i:s T", + "alias_remove_all": "Barcha taxalluslarni olib tashlash", + "alias_select_validity": "Amal qilish muddati", + "alias_time_left": "Qolgan vaqt", + "alias_valid_until": "Amal qiladi (gacha)", + "aliases_also_send_as": "Foydalanuvchi sifatida ham joʻnatishga ruxsat berilgan", + "aliases_send_as_all": "Quyidagi domen(lar) va ularning taxallus domenlari uchun joʻnatuvchi kirishini tekshirmaslik", + "app_hint": "Ilova parollari sizning IMAP, SMTP, CalDAV, CardDAV va EAS kirishi uchun muqobil parollardir. Foydalanuvchi nomi oʻzgarmaydi. SOGo webmail ilova parollari orqali mavjud emas.", + "allowed_protocols": "Ruxsat etilgan protokollar", + "app_name": "Ilova nomi", + "app_passwds": "Ilova parollari", + "apple_connection_profile": "Apple ulanish profili", + "apple_connection_profile_complete": "Bu ulanish profili Apple qurilmasi uchun IMAP va SMTP parametrlari hamda CalDAV (kalendarlar) va CardDAV (kontaktlar) yoʻllarini oʻz ichiga oladi.", + "apple_connection_profile_mailonly": "Bu ulanish profili Apple qurilmasi uchun IMAP va SMTP konfiguratsiya parametrlarini oʻz ichiga oladi.", + "apple_connection_profile_with_app_password": "Yangi ilova paroli yaratiladi va profilga qoʻshiladi, shunda qurilmangizni sozlashda parol kiritish kerak boʻlmaydi. Iltimos, faylni ulashmang, chunki u pochta qutingizga toʻliq kirish huquqini beradi.", + "attribute": "Atribut", + "authentication": "Autentifikatsiya", + "change_password": "Parolni oʻzgartirish", + "change_password_hint_app_passwords": "Hisobingizda %d ta ilova paroli mavjud va ular oʻzgartirilmaydi. Ularni boshqarish uchun Ilova parollari yorligʻiga oʻting.", + "clear_recent_successful_connections": "Koʻrilgan muvaffaqiyatli ulanishlarni tozalash", + "client_configuration": "E-pochta mijozlari va smartfonlar uchun konfiguratsiya qoʻllanmalarini koʻrsatish", + "create_app_passwd": "Ilova paroli yaratish", + "create_syncjob": "Yangi sinxronizatsiya vazifasi yaratish", + "created_on": "Yaratilgan sana", + "daily": "Kunlik", + "day": "kun", + "description": "Tavsif", + "delete_ays": "Iltimos, oʻchirish jarayonini tasdiqlang.", + "direct_aliases": "Toʻgʻridan-toʻgʻri taxallus manzillari", + "direct_aliases_desc": "Toʻgʻridan-toʻgʻri taxallus manzillariga spam filtri va TLS siyosati sozlamalari taʼsir qiladi.", + "direct_protocol_access": "Bu pochta qutisi foydalanuvchisi quyidagi protokollar va ilovalarga toʻgʻridan-toʻgʻri, tashqi kirishga ega. Bu sozlama administratoringiz tomonidan boshqariladi. Maxsus protokollar va ilovalarga kirishga ruxsat berish uchun ilova parollarini yaratish mumkin.
    \"Webmail\" tugmasi SOGo ga yagona kirish (SSO) imkonini beradi va doimo mavjud.", + "eas_reset": "ActiveSync qurilma keshini tiklash", + "eas_reset_help": "Koʻp hollarda qurilma keshini tiklash buzilgan ActiveSync profilini tiklashga yordam beradi.
    Diqqat: Barcha elementlar qayta yuklab olinadi!", + "eas_reset_now": "Hozir tiklash", + "edit": "Tahrirlash", + "email": "E-pochta", + "email_and_dav": "E-pochta, kalendarlar va kontaktlar", + "empty": "Natijalar yoʻq", + "encryption": "Shifrlash", + "excludes": "Istisnolar", + "expire_in": "Tugaydi", + "expire_never": "Hech qachon tugamasin", + "fido2_webauthn": "FIDO2/WebAuthn", + "forever": "Abadiy", + "force_pw_update": "Guruh dasturi xizmatlariga kirish uchun siz yangi parol oʻrnatishingiz kerak.", + "from": "dan", + "generate": "yaratish", + "hour": "soat", + "hourly": "Soatlik", + "hours": "soat", + "in_use": "Foydalanilmoqda", + "interval": "Oraliq", + "is_catch_all": "Domen(lar) uchun Hammasini-qabul qilish", + "last_mail_login": "Oxirgi pochta kirishi", + "last_pw_change": "Oxirgi parol oʻzgarishi", + "last_run": "Oxirgi ishga tushirilgan", + "last_ui_login": "Oxirgi UI kirishi", + "loading": "Yuklanmoqda...", + "login_history": "Kirish tarixi", + "mailbox": "Pochta qutisi", + "mailbox_details": "Tafsilotlar", + "mailbox_general": "Umumiy", + "mailbox_settings": "Sozlamalar", + "messages": "xabarlar", + "month": "oy", + "months": "oy", + "never": "Hech qachon", + "new_password": "Yangi parol", + "new_password_repeat": "Tasdiqlash paroli (takrorlang)", + "no_active_filter": "Faol filtr mavjud emas", + "no_last_login": "Oxirgi UI kirishi haqida maʼlumot yoʻq", + "no_record": "Yozuv yoʻq", + "open_logs": "Jurnallarni ochish", + "open_webmail_sso": "Webmail", + "overview": "Umumiy koʻrinish", + "password": "Parol", + "password_now": "Joriy parol (oʻzgarishlarni tasdiqlang)", + "pw_update_required": "Hisobingiz parol oʻzgartirishni talab qiladi. Davom etish uchun yangi parolni oʻrnating.", + "password_repeat": "Parol (takrorlang)", + "password_reset_info": "Agar parolni tiklash uchun e-pochta kiritilmagan boʻlsa, bu funksiyadan foydalanib boʻlmaydi.", + "protocols": "Protokollar", + "pushover_evaluate_x_prio": "Yuqori muhimlikdagi xatni eskalatsiya qilish [X-Priority: 1]", + "pushover_info": "Push bildirishnoma sozlamalari %s ga taxalluslar bilan birga (umumiy, umumiy boʻlmagan, teglangan) yetkazib beriladigan barcha toza (spam boʻlmagan) xatlarga qoʻllaniladi.", + "pushover_only_x_prio": "Faqat yuqori muhimlikdagi xatlarni koʻrib chiqish [X-Priority: 1]", + "pushover_sender_array": "Quyidagi joʻnatuvchi e-pochta manzillarini koʻrib chiqish (vergul bilan ajratilgan)", + "pushover_sender_regex": "Joʻnatuvchilarni quyidagi regex orqali mos kelishini tekshirish", + "pushover_text": "Bildirishnoma matni", + "pushover_title": "Bildirishnoma sarlavhasi", + "pushover_sound": "Tovush", + "pushover_vars": "Joʻnatuvchi filtri belgilanmagan boʻlsa, barcha xatlar koʻrib chiqiladi.
    Regex filtrlari va aniq joʻnatuvchi tekshiruvlari alohida belgilanishi mumkin va ketma-ket koʻrib chiqiladi. Ular bir-biriga bogʻliq emas.
    Matn va sarlavha uchun ishlatiladigan oʻzgaruvchilar (ma'lumotlarni himoya qilish siyosatiga e'tibor bering)", + "pushover_verify": "Maʼlumotlarni tekshirish", + "pw_recovery_email": "Parolni tiklash e-pochtasi", + "q_add_header": "Junk papkasi", + "q_all": "Barcha turkumlar", + "q_reject": "Rad etilgan", + "quarantine_category": "Karantin bildirishnoma turkumi", + "quarantine_category_info": "\"Rad etilgan\" bildirishnoma turkumi rad etilgan xatlarni oʻz ichiga oladi, \"Junk papkasi\" esa Junk papkasiga joylashtirilgan xatlar haqida foydalanuvchini xabardor qiladi.", + "quarantine_notification": "Karantin bildirishnomalari", + "quarantine_notification_info": "Bildirishnoma yuborilgandan keyin elementlar \"xabar berildi\" deb belgilanadi va bu element uchun keyingi bildirishnomalar yuborilmaydi.", + "recent_successful_connections": "Koʻrilgan muvaffaqiyatli ulanishlar", + "remove": "Olib tashlash", + "running": "Ishlamoqda", + "save": "Oʻzgarishlarni saqlash", + "save_changes": "Oʻzgarishlarni saqlash", + "sender_acl_disabled": "Joʻnatuvchi tekshiruvi oʻchirilgan", + "shared_aliases": "Umumiy taxallus manzillari", + "shared_aliases_desc": "Umumiy taxalluslarga foydalanuvchining maxsus sozlamalari (spam filtri yoki shifrlash siyosati kabi) taʼsir qilmaydi. Mos spam filtrlarini faqat administrator domen boʻyicha siyosat sifatida sozlashi mumkin.", + "show_sieve_filters": "Faol foydalanuvchi sieve filtrini koʻrsatish", + "sogo_profile_reset": "SOGo profilini tiklash", + "sogo_profile_reset_help": "Bu foydalanuvchining SOGo profilini yoʻq qiladi va barcha kontakt va kalendar maʼlumotlarini qaytarib boʻlmas tarzda oʻchiradi.", + "sogo_profile_reset_now": "Profilni hozir tiklash", + "spam_aliases": "Spam e-pochta taxalluslari", + "spam_aliases_info": "Spam taxallusi — bu haqiqiy e-pochta manzillarini himoya qilish uchun ishlatilishi mumkin boʻlgan vaqtinchalik e-pochta manzilidir.
    Ixtiyoriy ravishda muddat tugash vaqtini belgilash mumkin, shunda taxallus belgilangan davrdan keyin avtomatik oʻchirib qoʻyiladi, suiisteʼmol qilingan yoki tarqalib ketgan manzillarni samarali tarzda chiqarib tashlaydi.", + "spam_score_reset": "Server standartiga qaytarish", + "spamfilter": "Spam filtri", + "spamfilter_behavior": "Baholash", + "spamfilter_bl": "Qora roʻyxat", + "spamfilter_bl_desc": "Qora roʻyxatdagi e-pochta manzillari har doim spam sifatida tasniflanadi va rad etiladi. Rad etilgan xat karantinga nusxalanmaydi. Joker belgilar ishlatilishi mumkin. Filtr faqat toʻgʻridan-toʻgʻri taxalluslarga (bitta maqsad pochta qutisiga ega taxalluslarga) qoʻllaniladi, hammasini-qabul qilish taxalluslari va pochta qutisining oʻzi istisno qilinadi.", + "spamfilter_default_score": "Standart qiymatlar", + "spamfilter_green": "Yashil: bu xabar spam emas", + "spamfilter_hint": "Birinchi qiymat \"past spam bahosi\"ni tavsiflaydi, ikkinchisi \"yuqori spam bahosi\"ni ifodalaydi.", + "spamfilter_red": "Qizil: bu xabar spam va server tomonidan rad etiladi", + "spamfilter_table_action": "Amal", + "spamfilter_table_add": "Element qoʻshish", + "spamfilter_table_domain_policy": "n/a (domen siyosati)", + "spamfilter_table_empty": "Koʻrsatish uchun ma'lumot yoʻq", + "spamfilter_table_remove": "olib tashlash", + "spamfilter_table_rule": "Qoida", + "spamfilter_wl": "Oq roʻyxat", + "spamfilter_wl_desc": "Oq roʻyxatdagi e-pochta manzillari hech qachon spam sifatida tasniflanmaydi. Joker belgilar ishlatilishi mumkin. Filtr faqat toʻgʻridan-toʻgʻri taxalluslarga (bitta maqsad pochta qutisiga ega taxalluslarga) qoʻllaniladi, hammasini-qabul qilish taxalluslari va pochta qutisining oʻzi istisno qilinadi.", + "spamfilter_yellow": "Sariq: bu xabar spam boʻlishi mumkin, spam sifatida belgilanadi va Junk papkangizga koʻchiriladi", + "status": "Holat", + "sync_jobs": "Sinxronizatsiya vazifalari", + "syncjob_check_log": "Jurnalni tekshirish", + "syncjob_last_run_result": "Oxirgi ishga tushirilgan natija", + "syncjob_EX_OK": "Muvaffaqiyatli", + "syncjob_EXIT_CONNECTION_FAILURE": "Ulanish muammosi", + "syncjob_EXIT_TLS_FAILURE": "Shifrlangan ulanish bilan muammo", + "syncjob_EXIT_AUTHENTICATION_FAILURE": "Autentifikatsiya muammosi", + "syncjob_EXIT_OVERQUOTA": "Maqsad pochta qutisi kvotadan oshib ketdi", + "syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Masofaviy serverga ulanib boʻlmadi", + "syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Notoʻgʻri foydalanuvchi nomi yoki parol", + "tag_handling": "Teglangan xat uchun ishlovni oʻrnatish", + "tag_help_example": "Teglangan e-pochta manzili misoli: me+Facebook@example.org", + "tag_help_explain": "Subpapkada: tegga qarab nomlangan yangi subpapka INBOX ostida yaratiladi (\"INBOX/Facebook\").
    \r\nMavzuda: teg nomi xatning mavzusiga oldindan qoʻshiladi, misol: \"[Facebook] Mening yangiliklarim\".", + "tag_in_none": "Hech narsa qilmaslik", + "tag_in_subfolder": "Subpapkada", + "tag_in_subject": "Mavzuda", + "text": "Matn", + "tfa_info": "Ikki faktorli autentifikatsiya hisobingizni himoya qilishga yordam beradi. Uni yoqsangiz, ikki faktorli autentifikatsiyani qoʻllab-quvvatlamaydigan ilovalar yoki xizmatlarga (masalan, pochta mijozlari) kirish uchun ilova parollari kerak boʻladi.", + "title": "Sarlavha", + "tls_enforce_in": "Kiruvchi TLS ni majburlash", + "tls_enforce_out": "Chiquvchi TLS ni majburlash", + "tls_policy": "Shifrlash siyosati", + "tls_policy_warning": "Diqqat: Agar shifrlangan pochta transferini majburlashga qaror qilsangiz, e-pochtalarni yoʻqotishingiz mumkin.
    Siyosatga mos kelmaydigan xabarlar pochta tizimi tomonidan qatʼiy muvaffaqiyatsizlik bilan qaytariladi.
    Bu variant sizning asosiy e-pochta manzilingiz (kirish nomingiz), taxallus domenlaridan olingan barcha manzillar va faqat ushbu pochta qutisi maqsad sifatida boʻlgan taxallus manzillariga qoʻllaniladi.", + "user_settings": "Foydalanuvchi sozlamalari", + "username": "Foydalanuvchi nomi", + "value": "Qiymat", + "verify": "Tasdiqlash", + "waiting": "Kutilmoqda", + "week": "hafta", + "weekly": "Haftalik", + "weeks": "hafta", + "with_app_password": "ilova paroli bilan", + "year": "yil", + "years": "yil" + }, + "warning": { + "cannot_delete_self": "Tizimga kirgan foydalanuvchini oʻchirib boʻlmaydi", + "domain_added_sogo_failed": "Domen qoʻshildi, ammo SOGoni qayta ishga tushirib boʻlmadi, iltimos server jurnallaringizni tekshiring.", + "dovecot_restart_failed": "Dovecot qayta ishga tushirib boʻlmadi, iltimos jurnallarni tekshiring", + "fuzzy_learn_error": "Fuzzy hash oʻrgatish xatosi: %s", + "hash_not_found": "Hash topilmadi yoki allaqachon oʻchirilgan", + "ip_invalid": "Notoʻgʻri IP oʻtkazib yuborildi: %s", + "is_not_primary_alias": "Asosiy boʻlmagan taxallus oʻtkazib yuborildi: %s", + "no_active_admin": "Oxirgi faol administratorni oʻchirib boʻlmaydi", + "quota_exceeded_scope": "Domen kvotasi oshib ketdi: Bu domen doirasida faqat cheksiz pochta qutilari yaratilishi mumkin.", + "session_token": "Forma tokeni noto'g'ri: Token mos kelmadi", + "session_ua": "Forma tokeni noto'g'ri: User-Agent tekshiruvi xatosi" + } +} diff --git a/data/web/lang/lang.vi-vn.json b/data/web/lang/lang.vi-vn.json new file mode 100644 index 000000000..42813fd25 --- /dev/null +++ b/data/web/lang/lang.vi-vn.json @@ -0,0 +1,715 @@ +{ + "acl": { + "alias_domains": "Thêm tên miền bí danh", + "app_passwds": "Quản lý mật khẩu ứng dụng", + "bcc_maps": "Ánh xạ BCC", + "delimiter_action": "Hành động phân cách", + "domain_desc": "Thay đổi mô tả tên miền", + "domain_relayhost": "Thay đổi máy chủ chuyển tiếp cho tên miền", + "eas_reset": "Đặt lại thiết bị EAS", + "extend_sender_acl": "Cho phép mở rộng ACL người gửi bằng địa chỉ bên ngoài", + "filters": "Bộ lọc", + "login_as": "Đăng nhập với tư cách người dùng hộp thư", + "mailbox_relayhost": "Thay đổi máy chủ chuyển tiếp cho hộp thư", + "prohibited": "Bị cấm bởi ACL", + "protocol_access": "Thay đổi quyền truy cập giao thức", + "pushover": "Thông báo đẩy", + "pw_reset": "Cho phép đặt lại mật khẩu người dùng mailcow", + "quarantine": "Hành động cách ly", + "quarantine_attachments": "Cách ly tệp đính kèm", + "quarantine_category": "Thay đổi danh mục thông báo cách ly", + "quarantine_notification": "Thay đổi thông báo cách ly", + "ratelimit": "Giới hạn tốc độ", + "recipient_maps": "Ánh xạ người nhận", + "smtp_ip_access": "Thay đổi máy chủ được phép cho SMTP", + "sogo_access": "Cho phép quản lý truy cập SOGo", + "sogo_profile_reset": "Đặt lại hồ sơ SOGo", + "spam_alias": "Bí danh tạm thời", + "spam_policy": "Danh sách chặn/Danh sách cho phép", + "spam_score": "Điểm thư rác", + "syncjobs": "Công việc đồng bộ", + "tls_policy": "Chính sách TLS", + "unlimited_quota": "Hạn ngạch không giới hạn cho hộp thư" + }, + "add": { + "activate_filter_warn": "Tất cả các bộ lọc khác sẽ bị vô hiệu hóa khi tùy chọn kích hoạt được chọn.", + "active": "Đang hoạt động", + "add": "Thêm", + "add_domain_only": "Chỉ thêm tên miền", + "add_domain_restart": "Thêm tên miền và khởi động lại SOGo", + "alias_address": "Địa chỉ bí danh", + "alias_address_info": "Địa chỉ email đầy đủ hoặc @example.com để bắt tất cả thư cho một tên miền (phân cách bằng dấu phẩy). Chỉ áp dụng cho tên miền mailcow.", + "alias_domain": "Tên miền bí danh", + "alias_domain_info": "Chỉ cho phép tên miền hợp lệ (phân cách bằng dấu phẩy).", + "app_name": "Tên ứng dụng", + "app_password": "Thêm mật khẩu ứng dụng", + "app_passwd_protocols": "Các giao thức được phép cho mật khẩu ứng dụng", + "automap": "Thử tự động ánh xạ thư mục (\"Mục đã gửi\", \"Đã gửi\" => \"Đã gửi\" v.v.)", + "backup_mx_options": "Tùy chọn chuyển tiếp", + "bcc_dest_format": "Địa chỉ BCC phải là một địa chỉ email hợp lệ duy nhất.
    Nếu bạn cần gửi bản sao đến nhiều địa chỉ, hãy tạo một bí danh và sử dụng nó ở đây.", + "comment_info": "Bình luận riêng tư không hiển thị với người dùng, trong khi bình luận công khai được hiển thị dưới dạng chú thích khi di chuột qua trong tổng quan người dùng", + "custom_params": "Tham số tùy chỉnh", + "custom_params_hint": "Đúng: --param=xy, sai: --param xy", + "delete1": "Xóa từ nguồn khi hoàn thành", + "delete2": "Xóa thư ở đích không có ở nguồn", + "delete2duplicates": "Xóa các bản sao ở đích", + "description": "Mô tả", + "destination": "Đích", + "disable_login": "Không cho phép đăng nhập (vẫn nhận được thư đến)", + "domain": "Tên miền", + "domain_matches_hostname": "Tên miền %s khớp với tên máy chủ", + "domain_quota_m": "Tổng hạn ngạch tên miền (MiB)", + "dry": "Mô phỏng đồng bộ hóa", + "enc_method": "Phương thức mã hóa", + "exclude": "Loại trừ đối tượng (biểu thức chính quy)", + "full_name": "Tên đầy đủ", + "gal": "Danh sách địa chỉ toàn cục", + "gal_info": "GAL chứa tất cả các đối tượng của một tên miền và không thể được chỉnh sửa bởi bất kỳ người dùng nào. Thông tin rảnh/bận trong SOGo sẽ bị thiếu nếu vô hiệu hóa! Khởi động lại SOGo để áp dụng thay đổi.", + "generate": "Tạo", + "goto_ham": "\"Học là thư bình thường", + "goto_null": "Loại bỏ thư một cách thầm lặng", + "goto_spam": "Học là thư rác", + "hostname": "Máy chủ", + "inactive": "Không hoạt động", + "internal": "Nội bộ", + "internal_info": "Bí danh nội bộ chỉ có thể truy cập từ tên miền sở hữu hoặc tên miền bí danh.", + "kind": "Loại", + "mailbox_quota_def": "Hạn ngạch hộp thư mặc định", + "mailbox_quota_m": "Hạn ngạch tối đa mỗi hộp thư (MiB)", + "mailbox_username": "Tên người dùng (phần bên trái của địa chỉ email)", + "max_aliases": "Số lượng bí danh tối đa có thể tạo", + "max_mailboxes": "Số lượng hộp thư tối đa có thể tạo", + "mins_interval": "Khoảng thời gian kiểm tra (phút)", + "multiple_bookings": "Đặt chỗ nhiều lần", + "nexthop": "Bước nhảy tiếp theo", + "password": "Mật khẩu", + "password_repeat": "Xác nhận mật khẩu (nhập lại)", + "port": "Cổng", + "post_domain_add": "Container SOGo, \\\"sogo-mailcow\\\", cần được khởi động lại sau khi thêm tên miền mới!

    Ngoài ra, cấu hình DNS của tên miền cần được xem xét. Khi cấu hình DNS được phê duyệt, khởi động lại \\\"acme-mailcow\\\" để tự động tạo chứng chỉ cho tên miền mới của bạn (autoconfig.<domain>, autodiscover.<domain>).
    Bước này là tùy chọn và sẽ được thử lại sau mỗi 24 giờ.", + "private_comment": "Bình luận riêng tư", + "public_comment": "Bình luận công khai", + "quota_mb": "Hạn ngạch (MiB)", + "relay_all": "Chuyển tiếp tất cả người nhận", + "relay_all_info": "↪ Nếu bạn chọn không chuyển tiếp tất cả người nhận, bạn sẽ cần thêm một hộp thư (\"ẩn\") cho từng người nhận cần được chuyển tiếp.", + "relay_domain": "Chuyển tiếp tên miền này", + "relay_transport_info": "
    Thông tin
    Bạn có thể định nghĩa ánh xạ vận chuyển cho đích tùy chỉnh cho tên miền này. Nếu không được đặt, tra cứu MX sẽ được thực hiện.", + "relay_unknown_only": "Chỉ chuyển tiếp hộp thư không tồn tại. Hộp thư hiện có sẽ được gửi cục bộ.", + "relayhost_wrapped_tls_info": "Vui lòng không sử dụng các cổng được bọc TLS (thường được sử dụng trên cổng 465).
    \nSử dụng bất kỳ cổng không được bọc nào và sử dụng STARTTLS. Chính sách TLS để thực thi TLS có thể được tạo trong \"Ánh xạ chính sách TLS\".", + "select": "Vui lòng chọn...", + "select_domain": "Vui lòng chọn tên miền trước", + "sieve_desc": "Mô tả ngắn", + "sieve_type": "Loại bộ lọc", + "skipcrossduplicates": "Bỏ qua tin nhắn trùng lặp giữa các thư mục (ai đến trước được phục vụ trước)", + "subscribeall": "Đăng ký tất cả thư mục", + "syncjob": "Thêm công việc đồng bộ", + "syncjob_hint": "Lưu ý rằng mật khẩu cần được lưu dưới dạng văn bản thuần!", + "tags": "Thẻ", + "target_address": "Địa chỉ chuyển tiếp", + "target_address_info": "Địa chỉ email đầy đủ (phân cách bằng dấu phẩy).", + "target_domain": "Tên miền đích", + "timeout1": "Thời gian chờ kết nối đến máy chủ từ xa", + "timeout2": "Thời gian chờ kết nối đến máy chủ cục bộ", + "username": "Tên người dùng", + "validate": "Xác thực", + "validation_success": "Xác thực thành công", + "sender_allowed": "Cho phép gửi dưới bí danh này" + }, + "admin": { + "access": "Truy cập", + "action": "Hành động", + "activate_api": "Kích hoạt API", + "activate_send": "Kích hoạt nút gửi", + "active": "Đang hoạt động", + "active_rspamd_settings_map": "Ánh xạ cài đặt đang hoạt động", + "add": "Thêm", + "add_admin": "Thêm quản trị viên", + "add_domain_admin": "Thêm quản trị viên tên miền", + "add_forwarding_host": "Thêm máy chủ chuyển tiếp", + "add_relayhost": "Thêm vận chuyển phụ thuộc người gửi", + "add_relayhost_hint": "Vui lòng lưu ý rằng dữ liệu xác thực, nếu có, sẽ được lưu trữ dưới dạng văn bản thuần.", + "add_row": "Thêm hàng", + "add_settings_rule": "Thêm quy tắc cài đặt", + "add_transport": "Thêm vận chuyển", + "add_transports_hint": "Vui lòng lưu ý rằng dữ liệu xác thực, nếu có, sẽ được lưu trữ dưới dạng văn bản thuần.", + "additional_rows": " hàng bổ sung đã được thêm", + "admin": "Quản trị viên", + "admin_details": "Chỉnh sửa chi tiết quản trị viên", + "admin_domains": "Gán tên miền", + "admins": "Những quản trị viên", + "admins_ldap": "Quản trị viên LDAP", + "admin_quicklink": "Ẩn liên kết nhanh đến trang đăng nhập quản trị", + "advanced_settings": "Cài đặt nâng cao", + "allowed_methods": "Phương thức cho phép kiểm soát truy cập", + "allowed_origins": "Nguồn gốc cho phép kiểm soát truy cập", + "api_allow_from": "Cho phép truy cập API từ các IP/ký hiệu mạng CIDR này", + "api_info": "API đang được phát triển. Tài liệu có thể được tìm thấy tại /api", + "api_key": "Khóa API", + "api_read_only": "Truy cập chỉ đọc", + "api_read_write": "Truy cập đọc-ghi", + "api_skip_ip_check": "Bỏ qua kiểm tra IP cho API", + "app_hide": "Ẩn khi đăng nhập", + "app_links": "Liên kết ứng dụng", + "app_name": "Tên ứng dụng", + "apps_name": "Tên \"Ứng dụng mailcow\"", + "arrival_time": "Thời gian đến (giờ máy chủ)", + "authed_user": "Người dùng đã xác thực", + "ays": "Bạn có chắc chắn muốn tiếp tục không?", + "ban_list_info": "Xem danh sách IP bị cấm bên dưới: mạng (thời gian cấm còn lại) - [hành động].
    IP trong hàng đợi bỏ cấm sẽ được xóa khỏi danh sách cấm hoạt động trong vài giây.
    Nhãn đỏ cho biết lệnh cấm vĩnh viễn đang hoạt động bởi danh sách từ chối.", + "change_logo": "Thay đổi logo", + "logo_normal_label": "Bình thường", + "logo_dark_label": "Đảo ngược cho chế độ tối", + "configuration": "Cấu hình", + "convert_html_to_text": "Chuyển đổi HTML thành văn bản thuần", + "copy_to_clipboard": "Văn bản đã được sao chép vào bộ nhớ tạm!", + "cors_settings": "Cài đặt CORS", + "credentials_transport_warning": "Cảnh báo: Thêm một mục ánh xạ vận chuyển mới sẽ cập nhật thông tin xác thực cho tất cả các mục có cột bước nhảy tiếp theo khớp.", + "customer_id": "ID Khách hàng", + "customize": "Tùy chỉnh", + "login_page": "Trang đăng nhập", + "destination": "Đích đến", + "dkim_add_key": "Thêm khóa ARC/DKIM", + "dkim_domains_selector": "Bộ chọn", + "dkim_domains_wo_keys": "Chọn tên miền thiếu khóa", + "dkim_from": "Từ", + "dkim_from_title": "Tên miền nguồn để sao chép dữ liệu", + "dkim_key_length": "Độ dài khóa DKIM (bits)", + "dkim_key_missing": "Thiếu khóa", + "dkim_key_unused": "Khóa không được sử dụng", + "dkim_key_valid": "Khóa hợp lệ", + "dkim_keys": "Khóa ARC/DKIM", + "dkim_overwrite_key": "Ghi đè khóa DKIM hiện có", + "dkim_private_key": "Khóa riêng tư", + "dkim_to": "Đến", + "dkim_to_title": "Tên miền đích - sẽ bị ghi đè", + "domain": "Tên miền", + "domain_admin": "Quản trị viên tên miền", + "domain_admins": "Các quản trị viên tên miền", + "domainadmin_quicklink": "Ẩn liên kết nhanh đến trang đăng nhập quản trị viên tên miền", + "domain_s": "Tên miền/s", + "duplicate": "Nhân bản", + "duplicate_dkim": "Nhân bản bản ghi DKIM", + "edit": "Chỉnh sửa", + "empty": "Không có kết quả", + "excludes": "Loại trừ những người nhận này", + "f2b_ban_time": "Thời gian cấm (giây)", + "f2b_ban_time_increment": "Thời gian cấm tăng dần với mỗi lần cấm", + "f2b_blacklist": "Mạng/máy chủ bị từ chối", + "f2b_filter": "Bộ lọc biểu thức chính quy", + "f2b_list_info": "Một máy chủ hoặc mạng bị từ chối sẽ luôn có quyền ưu tiên cao hơn một thực thể trong danh sách cho phép. Cập nhật danh sách sẽ mất vài giây để được áp dụng.", + "f2b_manage_external": "Quản lý Fail2Ban từ bên ngoài", + "f2b_manage_external_info": "Fail2ban sẽ vẫn duy trì danh sách cấm, nhưng sẽ không chủ động đặt quy tắc để chặn lưu lượng. Sử dụng danh sách cấm được tạo bên dưới để chặn lưu lượng từ bên ngoài.", + "f2b_max_attempts": "Số lần thử tối đa", + "f2b_max_ban_time": "Thời gian cấm tối đa (giây)", + "f2b_netban_ipv4": "Kích thước mạng con IPv4 để áp dụng lệnh cấm (8-32)", + "f2b_netban_ipv6": "Kích thước mạng con IPv6 để áp dụng lệnh cấm (8-128", + "f2b_parameters": "Tham số Fail2ban", + "f2b_regex_info": "Nhật ký được xem xét: SOGo, Postfix, Dovecot, PHP-FPM.", + "f2b_retry_window": "Cửa sổ thử lại (giây) cho số lần thử tối đa", + "f2b_whitelist": "Mạng/máy chủ được cho phép", + "filter": "Bộ lọc", + "filter_table": "Bảng bộ lọc", + "force_sso_text": "Nếu nhà cung cấp OIDC bên ngoài được cấu hình, tùy chọn này sẽ ẩn biểu mẫu đăng nhập mailcow mặc định và chỉ hiển thị nút Đăng nhập một lần", + "force_sso": "Vô hiệu hóa đăng nhập mailcow và chỉ hiển thị Đăng nhập một lần", + "forwarding_hosts": "Máy chủ chuyển tiếp", + "forwarding_hosts_add_hint": "Bạn có thể chỉ định địa chỉ IPv4/IPv6, mạng theo ký hiệu CIDR, tên máy chủ (sẽ được phân giải thành địa chỉ IP), hoặc tên miền (sẽ được phân giải thành địa chỉ IP bằng cách truy vấn bản ghi SPF hoặc, nếu không có, bản ghi MX).", + "forwarding_hosts_hint": "Tin nhắn đến được chấp nhận vô điều kiện từ bất kỳ máy chủ nào được liệt kê ở đây. Các máy chủ này sau đó không bị kiểm tra với DNSBL hoặc danh sách xám. Thư rác nhận từ chúng không bao giờ bị từ chối, nhưng có thể được chuyển vào thư mục Rác. Công dụng phổ biến nhất cho việc này là chỉ định các máy chủ thư mà bạn đã thiết lập quy tắc chuyển tiếp email đến cho máy chủ mailcow của bạn.", + "from": "Từ", + "generate": "tạo", + "guid": "GUID - ID phiên bản duy nhất", + "guid_and_license": "GUID & Giấy phép", + "hash_remove_info": "Xóa một giá trị băm giới hạn tốc độ (nếu vẫn tồn tại) sẽ đặt lại hoàn toàn bộ đếm của nó.mailcow_password, được ánh xạ trong Keycloak.", + "iam_basedn": "DN cơ sở", + "iam_client_id": "ID khách hàng", + "iam_client_secret": "Bí mật khách hàng", + "iam_client_scopes": "Phạm vi khách hàng", + "iam_default_template": "Mẫu mặc định", + "iam_default_template_description": "Nếu không có mẫu nào được gán cho người dùng, mẫu mặc định sẽ được sử dụng để tạo hộp thư, nhưng không dùng để cập nhật hộp thư.", + "iam_description": "Cấu hình Nhà cung cấp bên ngoài cho Xác thực
    Hộp thư của người dùng sẽ được tự động tạo khi họ đăng nhập lần đầu, với điều kiện ánh xạ thuộc tính đã được thiết lập.", + "iam_extra_permission": "Để các cài đặt sau hoạt động, ứng dụng khách mailcow trong Keycloak cần một Tài khoản dịch vụ và quyền xem người dùng.", + "iam_host": "Máy chủ", + "iam_host_info": "Nhập một hoặc nhiều máy chủ LDAP, phân cách bằng dấu phẩy.", + "iam_import_users": "Nhập người dùng", + "iam_login_provisioning": "Tự động tạo người dùng khi đăng nhập", + "iam_mapping": "Ánh xạ thuộc tính", + "iam_bindpass": "Mật khẩu ràng buộc", + "iam_periodic_full_sync": "Đồng bộ hóa đầy đủ định kỳ", + "iam_port": "Cổng", + "iam_realm": "Vùng", + "iam_redirect_url": "URL chuyển hướng", + "iam_rest_flow": "Luồng mật khẩu thư", + "iam_server_url": "URL máy chủ", + "iam_sso": "Đăng nhập một lần", + "iam_sync_interval": "Khoảng thời gian đồng bộ / nhập (phút)", + "iam_test_connection": "Kiểm tra kết nối", + "iam_token_url": "Điểm cuối token", + "iam_userinfo_url": "Điểm cuối thông tin người dùng", + "iam_username_field": "Trường tên người dùng", + "iam_binddn": "DN ràng buộc", + "iam_use_ssl": "Sử dụng SSL", + "iam_use_ssl_info": "Nếu bật SSL và cổng được đặt là 389, nó sẽ tự động được ghi đè để sử dụng cổng 636.", + "iam_use_tls": "Sử dụng StartTLS", + "iam_use_tls_info": "Nếu bật TLS, bạn phải sử dụng cổng mặc định cho máy chủ LDAP của bạn (389). Không thể sử dụng các cổng SSL.", + "iam_version": "Phiên bản", + "ignore_ssl_error": "Bỏ qua lỗi SSL", + "import": "Nhập", + "import_private_key": "Nhập khóa riêng tư", + "in_use_by": "Đang được sử dụng bởi", + "inactive": "Không hoạt động", + "include_exclude": "Bao gồm/Loại trừ", + "include_exclude_info": "Mặc định - khi không có lựa chọn - tất cả hộp thư được đề cập", + "includes": "Bao gồm những người nhận này", + "ip_check": "Kiểm tra IP", + "ip_check_disabled": "Kiểm tra IP đã bị vô hiệu hóa. Bạn có thể bật nó trong
    Hệ thống > Cấu hình > Tùy chọn > Tùy chỉnh", + "ip_check_opt_in": "Đăng ký tham gia sử dụng dịch vụ bên thứ ba dùng ipv4.mailcow.emailipv6.mailcow.email để phân giải địa chỉ IP bên ngoài.", + "is_mx_based": "Dựa trên MX", + "last_applied": "Áp dụng lần cuối", + "license_info": "Giấy phép tuy không bắt buộc nhưng giúp phát triển thêm.
    Đăng ký GUID của bạn tại đây hoặc mua hỗ trợ cho cài đặt mailcow của bạn.", + "link": "Liên kết", + "loading": "Vui lòng đợi...", + "login_time": "Thời gian đăng nhập", + "logo_info": "Hình ảnh của bạn sẽ được điều chỉnh theo chiều cao 40px cho thanh điều hướng trên cùng và chiều rộng tối đa 250px cho trang bắt đầu. Khuyến nghị sử dụng đồ họa có thể thay đổi kích thước.", + "lookup_mx": "Đích đến là một biểu thức chính quy để so khớp với tên MX (.*.google.com để định tuyến tất cả thư nhắm đến MX kết thúc bằng google.com qua bước nhảy này)", + "main_name": "Tên \"Giao diện mailcow\"", + "merged_vars_hint": "Các hàng bị mờ đã được hợp nhất từ vars.(local.)inc.php và không thể sửa đổi.", + "message": "Tin nhắn", + "message_size": "Kích thước tin nhắn", + "nexthop": "Bước nhảy tiếp theo", + "needs_restart": "cần khởi động lại", + "no_active_bans": "Không có lệnh cấm đang hoạt động", + "no_new_rows": "Không có hàng nào khác", + "no_record": "Không có bản ghi", + "oauth2_apps": "Ứng dụng OAuth2", + "oauth2_add_client": "Thêm ứng dụng khách OAuth2", + "oauth2_client_id": "ID ứng dụng khách", + "oauth2_client_secret": "Bí mật ứng dụng khách", + "oauth2_info": "Triển khai OAuth2 hỗ trợ loại cấp phép \"Mã ủy quyền\" và cấp token làm mới.
    \nMáy chủ cũng tự động cấp token làm mới mới sau khi token làm mới đã được sử dụng.

    \n• Phạm vi mặc định là profile. Chỉ người dùng hộp thư mới có thể được xác thực qua OAuth2. Nếu tham số phạm vi bị bỏ qua, nó sẽ trở về profile.
    \n• Tham số state phải được gửi bởi ứng dụng khách như một phần của yêu cầu ủy quyền.

    \nĐường dẫn cho các yêu cầu đến API OAuth2:
    \n
      \n
    • Điểm cuối ủy quyền: /oauth/authorize
    • \n
    • Điểm cuối token: /oauth/token
    • \n
    • Trang tài nguyên: /oauth/profile
    • \n
    \nTạo lại bí mật ứng dụng khách sẽ không làm hết hạn các mã ủy quyền hiện có, nhưng chúng sẽ không thể làm mới token của họ.

    \nThu hồi token ứng dụng khách sẽ gây ra việc chấm dứt ngay lập tức tất cả các phiên hoạt động. Tất cả ứng dụng khách cần xác thực lại.", + "oauth2_redirect_uri": "URI chuyển hướng", + "oauth2_renew_secret": "Tạo bí mật ứng dụng khách mới", + "oauth2_revoke_tokens": "Thu hồi tất cả token ứng dụng khách", + "optional": "tùy chọn", + "options": "Các tùy chọn", + "password": "Mật khẩu", + "password_length": "Độ dài mật khẩu", + "password_policy": "Chính sách mật khẩu", + "password_policy_chars": "Phải chứa ít nhất một ký tự chữ cái", + "password_policy_length": "Độ dài mật khẩu tối thiểu là %d", + "password_policy_lowerupper": "Phải chứa ký tự viết thường và viết hoa", + "password_policy_numbers": "Phải chứa ít nhất một số", + "password_policy_special_chars": "Phải chứa ký tự đặc biệt", + "password_repeat": "Xác nhận mật khẩu (nhập lại)", + "password_reset_info": "Nếu không cung cấp email khôi phục, chức năng này không thể được sử dụng.", + "password_reset_settings": "Cài đặt khôi phục mật khẩu", + "password_reset_tmpl_html": "Mẫu HTML", + "password_reset_tmpl_text": "Mẫu văn bản", + "password_settings": "Cài đặt mật khẩu", + "priority": "Độ ưu tiên", + "private_key": "Khóa riêng tư", + "quarantine": "Cách ly", + "quarantine_bcc": "Gửi một bản sao của tất cả thông báo (BCC) đến người nhận này:
    Để trống để vô hiệu hóa. Thư không ký, không kiểm tra. Chỉ nên gửi nội bộ.", + "quarantine_exclude_domains": "Loại trừ tên miền và tên miền bí danh", + "quarantine_max_age": "Tuổi tối đa theo ngày
    Giá trị phải bằng hoặc lớn hơn 1 ngày.", + "quarantine_max_score": "Bỏ thông báo nếu điểm thư rác của một thư cao hơn giá trị này:
    Mặc định là 9999.0", + "quarantine_max_size": "Kích thước tối đa tính bằng MiB (các phần tử lớn hơn sẽ bị loại bỏ):
    0 không có nghĩa là không giới hạn.", + "quarantine_notification_html": "Mẫu email thông báo:
    Để trống để khôi phục mẫu mặc định.", + "quarantine_notification_sender": "Người gửi email thông báo", + "quarantine_notification_subject": "Chủ đề email thông báo", + "quarantine_redirect": "Chuyển hướng tất cả thông báo đến người nhận này:
    Để trống để vô hiệu hóa. Thư không ký, không kiểm tra. Chỉ nên gửi nội bộ.", + "quarantine_release_format": "Định dạng của các mục được phát hành", + "quarantine_release_format_att": "Dưới dạng tệp đính kèm", + "quarantine_release_format_raw": "Bản gốc không sửa đổi", + "quarantine_retention_size": "ưu giữ cho mỗi hộp thư:
    0 có nghĩa là không hoạt động.", + "quicklink_text": "Hiển thị hoặc ẩn liên kết nhanh đến các trang đăng nhập khác dưới biểu mẫu đăng nhập", + "quota_notification_html": "Mẫu email thông báo:
    Để trống để khôi phục mẫu mặc định.", + "quota_notification_sender": "Người gửi email thông báo", + "quota_notification_subject": "Chủ đề email thông báo", + "quota_notifications": "Thông báo hạn ngạch", + "quota_notifications_info": "Thông báo hạn ngạch được gửi cho người dùng một lần khi vượt quá 80% và một lần khi vượt quá 95% mức sử dụng.", + "quota_notifications_vars": "{{percent}} bằng hạn ngạch hiện tại của người dùng
    {{username}} là tên hộp thư", + "queue_unban": "bỏ cấm", + "r_active": "Hạn chế đang hoạt động", + "r_inactive": "Hạn chế không hoạt động", + "r_info": "Các phần tử bị mờ/vô hiệu hóa trong danh sách hạn chế đang hoạt động không được mailcow công nhận là hạn chế hợp lệ và không thể di chuyển. Các hạn chế không xác định sẽ vẫn được đặt theo thứ tự xuất hiện.
    Bạn có thể thêm phần tử mới trong inc/vars.local.inc.php để có thể bật/tắt chúng.", + "rate_name": "Tên tỷ lệ", + "recipients": "Người nhận", + "refresh": "Làm mới", + "regen_api_key": "Tạo lại khóa API", + "regex_maps": "Ánh xạ biểu thức chính quy", + "relay_from": "Địa chỉ \"Từ:\"", + "relay_rcpt": "Địa chỉ \"Đến:\"", + "relay_run": "Chạy thử", + "relayhosts": "Vận chuyển phụ thuộc người gửi", + "relayhosts_hint": "Xác định vận chuyển phụ thuộc người gửi để có thể chọn chúng trong hộp thoại cấu hình tên miền.
    \nDịch vụ vận chuyển luôn là \"smtp:\" và do đó sẽ thử TLS khi được cung cấp. TLS được bọc (SMTPS) không được hỗ trợ. Cài đặt chính sách TLS gửi đi riêng của người dùng được tính đến.
    \nẢnh hưởng đến các tên miền được chọn bao gồm cả tên miền bí danh.", + "remove": "Xóa", + "remove_row": "Xóa hàng", + "reset_default": "Đặt lại về mặc định", + "reset_limit": "Xóa giá trị băm", + "reset_password_vars": "{{link}} Liên kết đặt lại mật khẩu đã tạo
    {{username}} Tên hộp thư của người dùng yêu cầu đặt lại mật khẩu
    {{username2}} Tên hộp thư khôi phục
    {{date}} Ngày yêu cầu đặt lại mật khẩu được thực hiện
    {{token_lifetime}} Thời gian sống của token tính bằng phút
    {{hostname}} Tên máy chủ mailcow", + "restore_template": "Để trống để khôi phục mẫu mặc định.", + "routing": "Định tuyến", + "rsetting_add_rule": "Thêm quy tắc", + "rsetting_content": "Nội dung quy tắc", + "rsetting_desc": "Mô tả ngắn", + "rsetting_no_selection": "Vui lòng chọn một quy tắc", + "rsetting_none": "Không có quy tắc nào", + "rsettings_insert_preset": "Chèn mẫu ví dụ \"%s\"", + "rsettings_preset_1": "Vô hiệu hóa tất cả trừ DKIM và giới hạn tốc độ cho người dùng đã xác thực", + "rsettings_preset_2": "Quản trị viên bưu điện muốn thư rác", + "rsettings_preset_3": "Chỉ cho phép người gửi cụ thể cho một hộp thư (ví dụ: sử dụng như hộp thư nội bộ)", + "rsettings_preset_4": "Vô hiệu hóa Rspamd cho một tên miền", + "rspamd_com_settings": "Tên cài đặt sẽ được tự động tạo, vui lòng xem các mẫu ví dụ bên dưới. Để biết thêm chi tiết, xem Tài liệu Rspamd", + "rspamd_global_filters": "Ánh xạ bộ lọc toàn cục", + "rspamd_global_filters_agree": "Tôi sẽ cẩn thận!", + "rspamd_global_filters_info": "Ánh xạ bộ lọc toàn cục chứa các loại danh sách từ chối và cho phép toàn cục khác nhau.", + "rspamd_global_filters_regex": "Tên của chúng giải thích mục đích sử dụng. Tất cả nội dung phải chứa biểu thức chính quy hợp lệ theo định dạng \"/mẫu/tùy chọn\" (ví dụ: /.+@domain.tld/i).
    \nMặc dù kiểm tra cơ bản được thực hiện trên mỗi dòng của regex, chức năng của Rspamd có thể bị hỏng nếu nó không đọc được cú pháp chính xác.
    \nRspamd sẽ cố gắng đọc nội dung ánh xạ khi thay đổi. Nếu bạn gặp vấn đề, khởi động lại Rspamd để bắt buộc tải lại ánh xạ.
    Các phần tử trong danh sách từ chối được loại trừ khỏi cách ly.", + "rspamd_settings_map": "Ánh xạ cài đặt Rspamd", + "sal_level": "Cấp độ Moo", + "save": "Lưu thay đổi", + "search_domain_da": "Tìm kiếm tên miền", + "send": "Gửi", + "sender": "Người gửi", + "service": "Dịch vụ", + "service_id": "ID Dịch vụ", + "source": "Nguồn", + "spamfilter": "Bộ lọc thư rác", + "subject": "Chủ đề", + "success": "Thành công", + "sys_mails": "Thư hệ thống", + "task": "Nhiệm vụ", + "text": "Văn bản", + "time": "Thời gian", + "title": "Tiêu đề", + "title_name": "Tiêu đề trang web \"Giao diện mailcow\"", + "to_top": "Về đầu trang", + "transport_dest_format": "Regex hoặc cú pháp: example.org, .example.org, *, box@example.org (nhiều giá trị có thể được phân cách bằng dấu phẩy)", + "transport_maps": "Ánh xạ vận chuyển", + "transport_test_rcpt_info": "• Sử dụng null@hosted.mailcow.de để kiểm tra chuyển tiếp đến đích nước ngoài.", + "transports_hint": "• Một mục ánh xạ vận chuyển ghi đè một ánh xạ vận chuyển phụ thuộc người gửi.
    \n• Vận chuyển dựa trên MX được ưu tiên sử dụng.
    \n• Cài đặt chính sách TLS gửi đi cho từng người dùng bị bỏ qua và chỉ có thể được thực thi bởi các mục ánh xạ chính sách TLS.
    \n• Dịch vụ vận chuyển cho các vận chuyển đã định nghĩa luôn là \"smtp:\" và do đó sẽ thử TLS khi được cung cấp. TLS được bọc (SMTPS) không được hỗ trợ.
    \n• Địa chỉ khớp với \"/localhost$/\" sẽ luôn được vận chuyển qua \"local:\", do đó đích \"*\" sẽ không áp dụng cho những địa chỉ đó.
    \n• Để xác định thông tin xác thực cho bước nhảy tiếp theo ví dụ \"[host]:25\", Postfix luôn truy vấn \"host\" trước khi tìm kiếm \"[host]:25\". Hành vi này khiến không thể sử dụng \"host\" và \"[host]:25\" cùng một lúc.", + "ui_footer": "Chân trang (cho phép HTML)", + "ui_header_announcement": "Thông báo", + "ui_header_announcement_active": "Đặt thông báo hoạt động", + "ui_header_announcement_content": "Văn bản (cho phép HTML)", + "ui_header_announcement_help": "Thông báo hiển thị cho tất cả người dùng đã đăng nhập và trên màn hình đăng nhập của giao diện người dùng.", + "ui_header_announcement_select": "Chọn loại thông báo", + "ui_header_announcement_type": "Loại", + "ui_header_announcement_type_danger": "Rất quan trọng", + "ui_header_announcement_type_info": "Thông tin", + "ui_header_announcement_type_warning": "Quan trọng", + "ui_texts": "Nhãn và văn bản giao diện người dùng", + "unban_pending": "đang chờ bỏ cấm", + "unchanged_if_empty": "Nếu không thay đổi hãy để trống", + "upload": "Tải lên", + "username": "Tên người dùng", + "user_link": "Liên kết người dùng", + "user_quicklink": "Ẩn liên kết nhanh đến trang đăng nhập người dùng", + "validate_license_now": "Xác thực GUID với máy chủ giấy phép", + "verify": "Xác minh", + "yes": "✓" + }, + "danger": { + "access_denied": "Truy cập bị từ chối hoặc dữ liệu biểu mẫu không hợp lệ", + "alias_domain_invalid": "Tên miền bí danh %s không hợp lệ", + "alias_empty": "Địa chỉ bí danh không được để trống", + "alias_goto_identical": "Địa chỉ bí danh và địa chỉ chuyển tiếp không được giống nhau", + "alias_invalid": "Địa chỉ bí danh %s không hợp lệ", + "aliasd_targetd_identical": "Tên miền bí danh không được giống với tên miền đích: %s", + "aliases_in_use": "Số bí danh tối đa phải lớn hơn hoặc bằng %d", + "app_name_empty": "Tên ứng dụng không được để trống", + "app_passwd_id_invalid": "ID mật khẩu ứng dụng %s không hợp lệ", + "authsource_in_use": "Nhà cung cấp danh tính không thể thay đổi hoặc xóa vì hiện đang được sử dụng bởi một hoặc nhiều người dùng.", + "bcc_empty": "Đích BCC không được để trống", + "bcc_exists": "Một ánh xạ BCC %s đã tồn tại cho loại %s", + "bcc_must_be_email": "Đích BCC %s không phải là địa chỉ email hợp lệ", + "comment_too_long": "Bình luận quá dài, cho phép tối đa 160 ký tự", + "cors_invalid_method": "Phương thức Allow-Method được chỉ định không hợp lệ", + "cors_invalid_origin": "Allow-Origin được chỉ định không hợp lệ", + "defquota_empty": "Hạn ngạch mặc định cho mỗi hộp thư không được là 0.", + "demo_mode_enabled": "Chế độ Demo đang được bật", + "description_invalid": "Mô tả tài nguyên cho %s không hợp lệ", + "dkim_domain_or_sel_exists": "Một khóa DKIM cho \"%s\" đã tồn tại và sẽ không bị ghi đè", + "dkim_domain_or_sel_invalid": "Tên miền hoặc bộ chọn DKIM không hợp lệ: %s", + "domain_cannot_match_hostname": "Tên miền không thể trùng với tên máy chủ", + "domain_exists": "Tên miền %s đã tồn tại", + "domain_invalid": "Tên miền trống hoặc không hợp lệ", + "domain_not_empty": "Không thể xóa tên miền %s không trống", + "domain_not_found": "Không tìm thấy tên miền %s", + "domain_quota_m_in_use": "Hạn ngạch tên miền phải lớn hơn hoặc bằng %s MiB", + "extended_sender_acl_denied": "thiếu ACL để đặt địa chỉ người gửi bên ngoài", + "extra_acl_invalid": "Địa chỉ người gửi bên ngoài \"%s\" không hợp lệ", + "extra_acl_invalid_domain": "Người gửi bên ngoài \"%s\" sử dụng tên miền không hợp lệ", + "fido2_verification_failed": "Xác minh FIDO2 thất bại: %s", + "file_open_error": "Không thể mở tệp để ghi", + "filter_type": "Loại bộ lọc không đúng", + "from_invalid": "Người gửi không được để trống", + "generic_server_error": "Đã xảy ra lỗi máy chủ không mong đợi. Vui lòng liên hệ quản trị viên của bạn.", + "global_filter_write_error": "Không thể ghi tệp bộ lọc: %s", + "global_map_invalid": "ID ánh xạ toàn cục %s không hợp lệ", + "global_map_write_error": "Không thể ghi ID ánh xạ toàn cục %s: %s", + "goto_empty": "Một địa chỉ bí danh phải chứa ít nhất một địa chỉ chuyển tiếp hợp lệ", + "goto_invalid": "Địa chỉ chuyển tiếp %s không hợp lệ", + "ham_learn_error": "Lỗi học thư hợp lệ: %s", + "iam_test_connection": "Kết nối thất bại", + "imagick_exception": "Lỗi: Ngoại lệ Imagick khi đọc hình ảnh", + "img_dimensions_exceeded": "Hình ảnh vượt quá kích thước tối đa cho phép", + "img_invalid": "Không thể xác thực tệp hình ảnh", + "img_size_exceeded": "Hình ảnh vượt quá kích thước tệp tối đa", + "img_tmp_missing": "Không thể xác thực tệp hình ảnh: Không tìm thấy tệp tạm thời", + "invalid_bcc_map_type": "Loại ánh xạ BCC không hợp lệ", + "invalid_destination": "Định dạng đích \"%s\" không hợp lệ", + "invalid_filter_type": "Loại bộ lọc không hợp lệ", + "invalid_host": "Máy chủ được chỉ định không hợp lệ: %s", + "invalid_mime_type": "Kiểu MIME không hợp lệ", + "invalid_nexthop": "Định dạng bước nhảy tiếp theo không hợp lệ", + "invalid_nexthop_authenticated": "Bước nhảy tiếp theo tồn tại với thông tin xác thực khác, vui lòng cập nhật thông tin xác thực hiện có cho bước nhảy này trước.", + "invalid_recipient_map_new": "Người nhận mới được chỉ định không hợp lệ: %s", + "invalid_recipient_map_old": "Người nhận gốc được chỉ định không hợp lệ: %s", + "invalid_reset_token": "Token đặt lại không hợp lệ", + "ip_list_empty": "Danh sách IP được phép không được để trống", + "is_alias": "%s đã được biết đến như một địa chỉ bí danh", + "is_alias_or_mailbox": "%s đã được biết đến như một bí danh, một hộp thư hoặc một địa chỉ bí danh được mở rộng từ một tên miền bí danh.", + "is_spam_alias": "%s đã được biết đến như một địa chỉ bí danh tạm thời (địa chỉ bí danh thư rác)", + "last_key": "Không thể xóa khóa cuối cùng, vui lòng vô hiệu hóa TFA thay thế.", + "login_failed": "Đăng nhập không thành công", + "mailbox_defquota_exceeds_mailbox_maxquota": "Hạn ngạch mặc định vượt quá giới hạn hạn ngạch tối đa", + "mailbox_invalid": "Tên hộp thư không hợp lệ", + "mailbox_quota_exceeded": "Hạn ngạch vượt quá giới hạn tên miền (tối đa %d MiB)", + "mailbox_quota_exceeds_domain_quota": "Hạn ngạch tối đa vượt quá giới hạn hạn ngạch tên miền", + "mailbox_quota_left_exceeded": "Không đủ dung lượng còn lại (dung lượng còn lại: %d MiB)", + "mailboxes_in_use": "Số hộp thư tối đa phải lớn hơn hoặc bằng %d", + "malformed_username": "Tên người dùng không đúng định dạng", + "map_content_empty": "Nội dung ánh xạ không được để trống", + "max_age_invalid": "Tuổi tối đa %s không hợp lệ", + "max_alias_exceeded": "Vượt quá số bí danh tối đa", + "max_mailbox_exceeded": "Vượt quá số hộp thư tối đa (%d trên %d)", + "max_quota_in_use": "Hạn ngạch hộp thư phải lớn hơn hoặc bằng %d MiB", + "maxquota_empty": "Hạn ngạch tối đa cho mỗi hộp thư không được là 0.", + "mode_invalid": "Chế độ %s không hợp lệ", + "mx_invalid": "Bản ghi MX %s không hợp lệ", + "mysql_error": "Lỗi MySQL: %s", + "network_host_invalid": "Mạng hoặc máy chủ không hợp lệ: %s", + "next_hop_interferes": "%s xung đột với bước nhảy tiếp theo %s", + "next_hop_interferes_any": "Một bước nhảy tiếp theo hiện có xung đột với %s", + "nginx_reload_failed": "Tải lại Nginx thất bại: %s", + "no_user_defined": "Không có người dùng được định nghĩa", + "object_exists": "Đối tượng %s đã tồn tại", + "object_is_not_numeric": "Giá trị %s không phải là số", + "password_complexity": "Mật khẩu không đáp ứng chính sách", + "password_empty": "Mật khẩu không được để trống", + "password_mismatch": "Mật khẩu xác nhận không khớp", + "password_reset_invalid_user": "Không tìm thấy hộp thư hoặc không có email khôi phục được thiết lập", + "password_reset_na": "Tính năng khôi phục mật khẩu hiện không khả dụng. Vui lòng liên hệ quản trị viên của bạn.", + "policy_list_from_exists": "Một bản ghi với tên đã cho tồn tại", + "policy_list_from_invalid": "Bản ghi có định dạng không hợp lệ", + "private_key_error": "Lỗi khóa riêng tư: %s", + "pushover_credentials_missing": "Thiếu token hoặc khóa Pushover", + "pushover_key": "Khóa Pushover có định dạng không đúng", + "pushover_token": "Token Pushover có định dạng không đúng", + "quota_not_0_not_numeric": "Hạn ngạch phải là số và >= 0", + "recipient_map_entry_exists": "Mục ánh xạ người nhận \"%s\" đã tồn tại", + "recovery_email_failed": "Không thể gửi email khôi phục. Vui lòng liên hệ quản trị viên của bạn.", + "redis_error": "Lỗi Redis: %s", + "relayhost_invalid": "Mục ánh xạ %s không hợp lệ", + "release_send_failed": "Không thể phát hành thư: %s", + "required_data_missing": "Thiếu dữ liệu bắt buộc %s", + "reset_f2b_regex": "Không thể đặt lại bộ lọc biểu thức chính quy kịp thời, vui lòng thử lại hoặc đợi thêm vài giây và tải lại trang web.", + "reset_token_limit_exceeded": "Đã vượt quá giới hạn token đặt lại. Vui lòng thử lại sau.", + "resource_invalid": "Tên tài nguyên %s không hợp lệ", + "rl_timeframe": "Khung thời gian giới hạn tốc độ không chính xác", + "rspamd_ui_pw_length": "Mật khẩu giao diện Rspamd phải có ít nhất 6 ký tự", + "script_empty": "Tập lệnh không được để trống", + "sender_acl_invalid": "Giá trị ACL người gửi %s không hợp lệ", + "set_acl_failed": "Không thể thiết lập ACL", + "settings_map_invalid": "ID ánh xạ cài đặt %s không hợp lệ", + "sieve_error": "Lỗi phân tích cú pháp Sieve: %s", + "spam_learn_error": "Lỗi học thư rác: %s", + "subject_empty": "Tiêu đề không được để trống", + "target_domain_invalid": "Tên miền đích %s không hợp lệ", + "targetd_not_found": "Không tìm thấy tên miền đích %s", + "targetd_relay_domain": "Tên miền đích %s là tên miền chuyển tiếp", + "template_exists": "Mẫu %s đã tồn tại", + "template_id_invalid": "ID mẫu %s không hợp lệ", + "template_name_invalid": "Tên mẫu không hợp lệ", + "temp_error": "Lỗi tạm thời", + "text_empty": "Văn bản không được để trống", + "tfa_token_invalid": "Token xác thực hai yếu tố không hợp lệ", + "tls_policy_map_dest_invalid": "Đích chính sách TLS không hợp lệ", + "tls_policy_map_entry_exists": "Mục ánh xạ chính sách TLS \"%s\" đã tồn tại", + "tls_policy_map_parameter_invalid": "Tham số chính sách không hợp lệ", + "to_invalid": "Người nhận không được để trống", + "totp_verification_failed": "Xác thực TOTP thất bại", + "transport_dest_exists": "Đích vận chuyển \"%s\" đã tồn tại", + "webauthn_verification_failed": "Xác thực WebAuthn thất bại: %s", + "webauthn_authenticator_failed": "Không tìm thấy trình xác thực đã chọn", + "webauthn_publickey_failed": "Không có khóa công khai nào được lưu cho trình xác thực đã chọn", + "webauthn_username_failed": "Trình xác thực đã chọn thuộc về tài khoản khác", + "unknown": "Đã xảy ra lỗi không xác định", + "unknown_tfa_method": "Phương thức xác thực hai yếu tố không xác định", + "unlimited_quota_acl": "Hạn ngạch không giới hạn bị cấm bởi ACL", + "username_invalid": "Tên người dùng %s không thể sử dụng được", + "validity_missing": "Vui lòng gán thời hạn hiệu lực", + "value_missing": "Vui lòng cung cấp tất cả các giá trị", + "version_invalid": "Phiên bản %s không hợp lệ", + "yotp_verification_failed": "Xác thực Yubico OTP thất bại: %s", + "tfa_removal_blocked": "Xác thực hai yếu tố không thể bị xóa vì đây là yêu cầu bắt buộc đối với tài khoản của bạn.", + "quarantine_category_invalid": "Danh mục cách ly phải là một trong các loại sau : add_header, reject, all." + }, + "datatables": { + "collapse_all": "Thu gọn tất cả", + "emptyTable": "Không có dữ liệu trong bảng", + "expand_all": "Mở rộng tất cả", + "info": "Hiển thị từ _START_ đến _END_ của _TOTAL_ mục", + "infoEmpty": "Hiển thị 0 đến 0 của 0 mục", + "infoFiltered": "(được lọc từ tổng số _MAX_ mục)", + "lengthMenu": "Hiển thị _MENU_ mục", + "loadingRecords": "Đang tải...", + "processing": "Vui lòng đợi...", + "search": "Tìm kiếm:", + "zeroRecords": "Không tìm thấy bản ghi phù hợp", + "paginate": { + "first": "Đầu", + "last": "Cuối", + "next": "Tiếp", + "previous": "Trước" + }, + "aria": { + "sortAscending": ": kích hoạt để sắp xếp cột tăng dần", + "sortDescending": ": kích hoạt để sắp xếp cột giảm dần" + }, + "decimal": ".", + "thousands": "," + }, + "debug": { + "architecture": "Kiến trúc", + "chart_this_server": "Biểu đồ (máy chủ này)", + "containers_info": "Thông tin container", + "container_running": "Đang chạy", + "container_disabled": "Container đã dừng hoặc bị vô hiệu hóa", + "container_stopped": "Đã dừng", + "cores": "Nhân CPU", + "current_time": "Thời gian hệ thống", + "disk_usage": "Sử dụng ổ đĩa", + "docs": "Tài liệu", + "error_show_ip": "Không thể phân giải địa chỉ IP công khai", + "external_logs": "Nhật ký ngoài", + "history_all_servers": "Lịch sử (tất cả máy chủ)", + "in_memory_logs": "Nhật ký trong bộ nhớ", + "last_modified": "Sửa đổi lần cuối", + "log_info": "

    mailcow nhật ký trong bộ nhớ được thu thập trong danh sách Redis và được cắt giảm xuống LOG_LINES (%d) mỗi phút để giảm tải.\n
    Nhật ký trong bộ nhớ không nhằm mục đích lưu trữ lâu dài. Tất cả các ứng dụng ghi nhật ký trong bộ nhớ cũng ghi vào Docker daemon và do đó vào trình điều khiển ghi nhật ký mặc định.r\n
    Loại nhật ký trong bộ nhớ nên được sử dụng để gỡ lỗi các vấn đề nhỏ với các container.

    \n

    Nhật ký bên ngoài được thu thập thông qua API của ứng dụng đã cho.

    \n

    Nhật ký tĩnh chủ yếu là nhật ký hoạt động, không được ghi vào Dockerd nhưng vẫn cần được lưu trữ lâu dài (ngoại trừ nhật ký API).

    ", + "login_time": "Thời gian", + "logs": "Nhật ký", + "memory": "Bộ nhớ", + "online_users": "Người dùng trực tuyến", + "restart_container": "Khởi động lại", + "service": "Dịch vụ", + "show_ip": "Hiển thị IP công khai", + "size": "Kích thước", + "started_at": "Bắt đầu lúc", + "started_on": "Bắt đầu vào", + "static_logs": "Nhật ký tĩnh", + "success": "Thành công", + "system_containers": "Hệ thống & Container", + "timezone": "Múi giờ", + "uptime": "Thời gian hoạt động", + "update_available": "Có bản cập nhật mới", + "no_update_available": "Hệ thống đang ở phiên bản mới nhất", + "update_failed": "Không thể kiểm tra cập nhật", + "username": "Tên người dùng", + "wip": "Đang trong quá trình phát triển" + }, + "diagnostics": { + "cname_from_a": "Giá trị được lấy từ bản ghi A/AAAA. Điều này được hỗ trợ miễn là bản ghi trỏ đến tài nguyên chính xác.", + "dns_records": "Bản ghi DNS", + "dns_records_24hours": "Xin lưu ý rằng các thay đổi được thực hiện đối với DNS có thể mất tới 24 giờ để phản ánh chính xác trạng thái hiện tại của chúng trên trang này. Trang này nhằm giúp bạn dễ dàng xem cách cấu hình bản ghi DNS và kiểm tra xem tất cả bản ghi của bạn có được lưu trữ chính xác trong DNS hay không.", + "dns_records_data": "Dữ liệu chính xác", + "dns_records_docs": "Vui lòng tham khảo thêm tài liệu hướng dẫn.", + "dns_records_name": "Tên", + "dns_records_status": "Trạng thái hiện tại", + "dns_records_type": "Loại", + "optional": "Bản ghi này là tùy chọn." + }, + "edit": { + "acl": "ACL (Quyền hạn)", + "active": "Hoạt động", + "admin": "Chỉnh sửa quản trị viên", + "advanced_settings": "Cài đặt nâng cao", + "alias": "Chỉnh sửa bí danh", + "allow_from_smtp": "Chỉ cho phép các IP sau sử dụng SMTP", + "allow_from_smtp_info": "Để trống để cho phép tất cả người gửi.
    Địa chỉ và mạng IPv4/IPv6.", + "allowed_protocols": "Các giao thức được phép truy cập trực tiếp của người dùng (không ảnh hưởng đến giao thức mật khẩu ứng dụng)", + "app_name": "Tên ứng dụng", + "app_passwd": "Mật khẩu ứng dụng", + "app_passwd_protocols": "Các giao thức được phép cho mật khẩu ứng dụng", + "automap": "Thử tự động ánh xạ thư mục (\"Mục đã gửi\", \"Đã gửi\" => \"Đã gửi\" v.v.)", + "backup_mx_options": "Tùy chọn chuyển tiếp", + "bcc_dest_format": "Đích BCC phải là một địa chỉ email hợp lệ duy nhất.
    Nếu bạn cần gửi bản sao đến nhiều địa chỉ, hãy tạo một bí danh và sử dụng nó ở đây.", + "client_id": "ID khách hàng", + "client_secret": "Khóa bí mật khách hàng", + "comment_info": "Bình luận riêng tư không hiển thị với người dùng, trong khi bình luận công khai được hiển thị dưới dạng chú thích khi di chuột qua trong tổng quan người dùng", + "created_on": "Được tạo vào", + "custom_attributes": "Thuộc tính tùy chỉnh", + "delete1": "Xóa từ nguồn khi hoàn thành", + "delete2": "Xóa thư ở đích không có ở nguồn", + "delete2duplicates": "Xóa các bản sao ở đích", + "delete_ays": "Vui lòng xác nhận quá trình xóa.", + "description": "Mô tả", + "disable_login": "Không cho phép đăng nhập (vẫn nhận được thư đến)", + "domain": "Chỉnh sửa tên miền", + "domain_admin": "Chỉnh sửa quản trị viên tên miền", + "domain_footer": "Chân trang toàn tên miền", + "domain_footer_html": "Chân trang HTML", + "domain_footer_info": "Chân trang toàn tên miền được thêm vào tất cả các email gửi đi liên quan đến một địa chỉ trong tên miền này.
    Các biến sau có thể được sử dụng cho chân trang:", + "domain_footer_info_vars": { + "auth_user": "{= auth_user =} - Tên người dùng đã xác thực được chỉ định bởi MTA", + "from_user": "{= from_user =} - Phần người dùng của phong bì, ví dụ \"moo@mailcow.tld\" sẽ trả về \"moo\"", + "from_name": "{= from_name =} - Tên từ phong bì, ví dụ \"Mailcow <moo@mailcow.tld>\" sẽ trả về \"Mailcow\"", + "from_addr": "{= from_addr =} - Phần địa chỉ của phong bì", + "from_domain": "{= from_domain =} - Phần tên miền của phong bì", + "custom": "{= foo =} - Nếu hộp thư có thuộc tính tùy chỉnh \"foo\" với giá trị \"bar\" sẽ trả về \"bar\"" + }, + "domain_footer_plain": "Chân trang văn bản thuần", + "domain_footer_skip_replies": "Bỏ qua chân trang trong email trả lời", + "domain_quota": "Hạn ngạch tên miền", + "domains": "Các tên miền", + "dont_check_sender_acl": "Tắt kiểm tra người gửi cho tên miền %s (+ tên miền bí danh)", + "edit_alias_domain": "Chỉnh sửa tên miền bí danh", + "encryption": "Mã hóa", + "exclude": "Loại trừ đối tượng (biểu thức chính quy)", + "extended_sender_acl": "Địa chỉ người gửi bên ngoài", + "extended_sender_acl_info": "Khóa tên miền DKIM nên được nhập vào, nếu có sẵn.
    \n Nhớ thêm máy chủ này vào bản ghi SPF TXT tương ứng.
    \n Khi một tên miền hoặc tên miền bí danh được thêm vào máy chủ này, mà trùng với một địa chỉ bên ngoài, địa chỉ bên ngoài sẽ bị xóa.
    \n Sử dụng @domain.tld để cho phép gửi dưới dạng *@domain.tld.", + "force_pw_update": "Bắt buộc cập nhật mật khẩu trong lần đăng nhập tiếp theo", + "force_pw_update_info": "Người dùng này sẽ chỉ có thể đăng nhập vào %s. Mật khẩu ứng dụng vẫn có thể sử dụng được.", + "footer_exclude": "Loại trừ khỏi chân trang", + "full_name": "Tên đầy đủ", + "gal": "Danh sách địa chỉ toàn cục", + "gal_info": "GAL chứa tất cả các đối tượng của một tên miền và không thể được chỉnh sửa bởi bất kỳ người dùng nào. Thông tin rảnh/bận trong SOGo sẽ bị thiếu nếu vô hiệu hóa! Khởi động lại SOGo để áp dụng thay đổi.", + "generate": "tạo", + "grant_types": "Các loại cấp quyền", + "hostname": "Tên máy chủ", + "inactive": "Không hoạt động", + "internal": "Nội bộ", + "internal_info": "Bí danh nội bộ chỉ có thể truy cập từ tên miền sở hữu hoặc tên miền bí danh.", + "kind": "Loại", + "last_modified": "Sửa đổi lần cuối", + "lookup_mx": "Đích là một biểu thức chính quy để khớp với tên MX (.*.google.com để định tuyến tất cả thư nhắm đến MX kết thúc bằng google.com qua bước nhảy này)", + "sender_allowed": "Cho phép gửi đi bằng bí danh", + "sender_allowed_info": "Nếu bị vô hiệu hóa, bí danh này chỉ có thể nhận thư. Sử dụng ACL của người gửi để ghi đè và cấp quyền gửi cho các hộp thư cụ thể.", + "mailbox": "Chỉnh sửa hộp thư", + "mailbox_quota_def": "Hạn mức hộp thư mặc định", + "mailbox_relayhost_info": "Chỉ áp dụng cho hộp thư và các bí danh trực tiếp, thao tác này sẽ ghi đè lên máy chủ chuyển tiếp tên miền.", + "mailbox_rename": "Đổi tên hộp thư", + "mailbox_rename_agree": "Tôi đã tạo bản sao lưu.", + "mailbox_rename_warning": "QUAN TRỌNG! Hãy tạo bản sao lưu trước khi đổi tên hộp thư.", + "mailbox_rename_alias": "Tạo tự động bí danh", + "mailbox_rename_title": "Tên mới của hộp thư cục bộ", + "max_aliases": "Số lượng Bí danh tối đa", + "max_mailboxes": "Số lượng hộp thư tối đa có thể có", + "max_quota": "Dung lượng tối đa cho mỗi hộp thư (MiB)" + } +} diff --git a/data/web/lang/lang.zh-cn.json b/data/web/lang/lang.zh-cn.json index 3c46eb8a4..fe225a5df 100644 --- a/data/web/lang/lang.zh-cn.json +++ b/data/web/lang/lang.zh-cn.json @@ -24,7 +24,7 @@ "sogo_access": "允许管理 SOGo 访问权限", "sogo_profile_reset": "重置 SOGo 个人资料", "spam_alias": "临时别名", - "spam_policy": "黑名单/白名单", + "spam_policy": "阻止名单/允许名单", "spam_score": "垃圾邮件分数", "syncjobs": "同步任务", "tls_policy": "TLS 策略", @@ -109,7 +109,9 @@ "username": "用户名", "validate": "验证", "validation_success": "验证成功", - "dry": "模拟同步(Dry run)" + "dry": "模拟同步(Dry run)", + "internal_info": "内部的别名只能在域内部或者别名域内部访问。", + "internal": "内部的" }, "admin": { "access": "权限管理", @@ -147,7 +149,7 @@ "arrival_time": "到达时间 (服务器时间)", "authed_user": "已认证用户", "ays": "确定继续操作?", - "ban_list_info": "以下为被封禁的 IP 列表: 网络 (剩余封禁时间) - [操作]
    被取消封禁的 IP 将会在几秒之内从封禁列表中移除
    红色标签表示因黑名单而导致的永久封禁。", + "ban_list_info": "以下为被封禁的 IP 列表: 网络 (剩余封禁时间) - [操作]
    被取消封禁的 IP 将会在几秒之内从封禁列表中移除
    红色标签表示因阻止名单而导致的永久封禁。", "change_logo": "更改 Logo", "configuration": "配置", "convert_html_to_text": "将 HTML 转换为纯文本内容", @@ -179,16 +181,16 @@ "empty": "结果为空", "excludes": "除了", "f2b_ban_time": "封禁时间 (秒)", - "f2b_blacklist": "网络/主机黑名单", + "f2b_blacklist": "网络/主机阻止名单", "f2b_filter": "正则表达式过滤器", - "f2b_list_info": "黑名单的优先级总是高于白名单。 列表更新将会在几秒之后完成。", + "f2b_list_info": "阻止名单的优先级总是高于允许名单。 列表更新将会在几秒之后完成。", "f2b_max_attempts": "最多尝试次数", "f2b_netban_ipv4": "应用封禁的 IPv4 子网大小 (8-32)", "f2b_netban_ipv6": "应用封禁的 IPv6 子网大小 (8-128)", "f2b_parameters": "Fail2ban 参数", "f2b_regex_info": "将会过滤这些应用的日志: SOGo,Postfix,Dovecot 和 PHP-FPM。", "f2b_retry_window": "最多尝试次数重试窗口 (秒)", - "f2b_whitelist": "网络/主机白名单", + "f2b_whitelist": "网络/主机允许名单", "filter_table": "筛选表格", "forwarding_hosts": "转发主机", "forwarding_hosts_add_hint": "你可以指定 IPv4/IPv6 地址、CIDR 表示的网络、主机名 (解析为 IP 地址),或者邮箱域名 (查询 SPF 记录或 MX 记录并解析为 IP 地址)。", @@ -296,8 +298,8 @@ "rspamd_com_settings": "设置名称将会自动生成,请看参考下方的示例预设。查看Rspamd 文档以了解更多的细节", "rspamd_global_filters": "全局过滤规则", "rspamd_global_filters_agree": "我会小心谨慎的!", - "rspamd_global_filters_info": "全局过滤规则包含了不同类型的全局黑名单和白名单。", - "rspamd_global_filters_regex": "它们的名字解释了它们的用途。所有内容必须包含 \"/pattern/options\" 格式的合法表达式 (例如 /.+@domain\\.tld/i)。
    \r\n 因为仅对正则表达式执行了基本的检查,Rspamd 的功能仍可能因正则表达式语法问题出现错误。
    \r\n Rspamd 会在规则更改后读取其内容。 如果你遇到了问题,重启 Rspamd 以强制重载规则。
    黑名单中的项目会被系统排除。", + "rspamd_global_filters_info": "全局过滤规则包含了不同类型的全局阻止名单和允许名单。", + "rspamd_global_filters_regex": "它们的名字解释了它们的用途。所有内容必须包含 \"/pattern/options\" 格式的合法表达式 (例如 /.+@domain\\.tld/i)。
    \n 因为仅对正则表达式执行了基本的检查,Rspamd 的功能仍可能因正则表达式语法问题出现错误。
    \n Rspamd 会在规则更改后读取其内容。 如果你遇到了问题,重启 Rspamd 以强制重载规则。
    阻止名单中的项目会被系统排除。", "rspamd_settings_map": "Rspamd 设置", "sal_level": "Moo 等级", "save": "保存更改", @@ -405,7 +407,10 @@ "user_quicklink": "隐藏指向用户登陆页面的快捷链接", "admin_quicklink": "隐藏指向管理员登陆页面的快捷链接", "force_sso": "强制要求单点登录(SSO)", - "user_link": "自定义链接" + "user_link": "自定义链接", + "app_hide": "在登入界面隐藏", + "needs_restart": "需要重启", + "iam_use_tls_info": "如果使用了 TLS,必须使用 LDAP 服务器的默认端口(389)。SSL 的端口不能使用。" }, "danger": { "access_denied": "访问被拒绝或者表单数据无效", @@ -546,7 +551,11 @@ "generic_server_error": "服务器错误。请联系您的管理员。", "authsource_in_use": "由于当前有一个或多个用户正在使用该身份提供者(IDP),因此无法更改或删除。", "iam_test_connection": "连接失败", - "required_data_missing": "缺少需要的 %s 数据" + "required_data_missing": "缺少需要的 %s 数据", + "max_age_invalid": "最大有效时间 %s 无效", + "mode_invalid": "模式 %s 无效", + "mx_invalid": "MX 记录 %s 无效", + "version_invalid": "版本 %s 无效" }, "debug": { "chart_this_server": "图表 (此服务器)", @@ -573,13 +582,13 @@ "username": "用户名", "container_disabled": "容器已被停止或禁用", "container_running": "运行中", - "cores": "核心数", + "cores": "核", "memory": "内存", "error_show_ip": "无法解析公网IP地址", "show_ip": "显示公网IP", "update_available": "有可用更新", "update_failed": "无法检查更新", - "architecture": "结构", + "architecture": "架构", "container_stopped": "已停止", "current_time": "系统时间", "timezone": "时区", @@ -732,7 +741,20 @@ "domain_footer_skip_replies": "在回信中忽略 footer", "footer_exclude": "从 footer 中排除", "last_modified": "上次修改时间", - "pushover_sound": "声音" + "pushover_sound": "声音", + "internal": "内部的", + "internal_info": "内部的别名只能在域内部或者别名域内部访问。", + "mta_sts": "邮件传输代理严格传输安全协议(MTA-STS)", + "mta_sts_version": "版本", + "mta_sts_info": "MTA-STS 是一项 MTA 标准,它强制要求 MTA 间传输必须使用 TLS 和有效的证书。
    当因没有 DNSSEC 而无法使用 DANE 时,该标准将被启用。
    注意:若收件域支持基于 DNSSEC 的 DANE 协议,则系统将始终优先采用 DANE —— MTA-STS 仅作为备用机制存在。", + "mta_sts_version_info": "定义 MTA-STS 标准的版本——当前仅 STSv1 为有效版本。", + "mta_sts_mode": "模式", + "mta_sts_mode_info": "提供三种可选模式:
    • 测试模式——仅监控策略执行情况,违反策略不会产生实际影响。
    • 强制模式——严格执行策略,拒绝所有未使用有效 TLS 加密的连接。
    • 禁用模式——发布策略但不生效。
    ", + "mta_sts_max_age": "最长有效期", + "mta_sts_max_age_info": "接收方邮件服务器可缓存该策略的时长(秒),超出后需重新获取策略。", + "mta_sts_mx": "MX 服务器", + "mta_sts_mx_info": "仅允许向明确列出的邮件服务器发送邮件;发送方 MTA 会验证 DNS MX 记录的主机名是否与策略列表匹配,并仅允许携带有效 TLS 证书的投递(可防范中间人攻击)。", + "mta_sts_mx_notice": "可配置多个 MX 服务器(以逗号分隔)。" }, "fido2": { "confirm": "确认", @@ -802,7 +824,8 @@ "login_linkstext": "不是正确的登陆页面?", "login_usertext": "以用户身份登陆", "login_domainadmintext": "以域管理员身份登陆", - "login_admintext": "以管理员身份登陆" + "login_admintext": "以管理员身份登陆", + "email": "邮箱地址" }, "mailbox": { "action": "操作", @@ -908,7 +931,7 @@ "recipient_map_new": "新收件人", "recipient_map_new_info": "收件人映射的目标必须为合法的邮件地址或域名。", "recipient_map_old": "原收件人", - "recipient_map_old_info": "原收件人必须为合法的邮箱地址。", + "recipient_map_old_info": "原收件人必须为合法的邮箱地址或域名。", "recipient_maps": "收件人映射", "relay_all": "中继所有收件人", "remove": "删除", @@ -978,7 +1001,8 @@ "relay_unknown": "转发未知信箱", "templates": "模板", "template": "模板", - "iam": "身份提供者(IDP)" + "iam": "身份提供者(IDP)", + "internal": "内部的" }, "oauth2": { "access_denied": "请作为邮箱所有者登录以使用 OAuth2 授权。", @@ -1010,7 +1034,7 @@ "notified": "已发送通知", "qhandler_success": "已成功向系统发送请求,现在你可以关闭这个窗口了。", "qid": "Rspamd 队列ID(QID)", - "qinfo": "隔离系统会把已被拒绝接收的邮件以及作为拷贝发送到垃圾箱的邮件保存到数据库中 (发件人会知道)。\r\n
    \"学习为垃圾并删除\" 会根据贝叶斯定理将消息作为垃圾学习并计算其模糊特征以拒绝未来收到相似消息。\r\n
    请注意,这取决于你的系统资源,学习多个消息可能会花费较长时间。
    黑名单中项目会被隔离系统排除。", + "qinfo": "隔离系统会把已被拒绝接收的邮件以及作为拷贝发送到垃圾箱的邮件保存到数据库中 (发件人会知道)。\n
    \"学习为垃圾并删除\" 会根据贝叶斯定理将消息作为垃圾学习并计算其模糊特征以拒绝未来收到相似消息。\n
    请注意,这取决于你的系统资源,学习多个消息可能会花费较长时间。
    阻止名单中项目会被隔离系统排除。", "qitem": "隔离项目", "quarantine": "隔离", "quick_actions": "操作", @@ -1297,12 +1321,12 @@ "sogo_profile_reset": "重置 SOGo 个人资料", "sogo_profile_reset_help": "此操作会不可恢复地删除用户的 SOGo 个人资料并删除所有联系人和日历数据。", "sogo_profile_reset_now": "立即重置个人资料", - "spam_aliases": "临时邮箱别名", + "spam_aliases": "垃圾邮件别名", "spam_score_reset": "重置为服务器默认值", "spamfilter": "垃圾邮件过滤器", "spamfilter_behavior": "分数", - "spamfilter_bl": "黑名单", - "spamfilter_bl_desc": "黑名单中地址总是会被标记为垃圾邮件。被拒绝的邮件不会进入隔离区。此处可以使用通配符 \"*\"。此过滤器也会应用到直接别名 (只指向一个目标邮箱),但不会应用到\"接收所有\"别名和邮箱地址本身。", + "spamfilter_bl": "阻止名单", + "spamfilter_bl_desc": "阻止名单中地址总是会被标记为垃圾邮件。被拒绝的邮件不会进入隔离区。此处可以使用通配符 \"*\"。此过滤器也会应用到直接别名 (只指向一个目标邮箱),但不会应用到\"接收所有\"别名和邮箱地址本身。", "spamfilter_default_score": "默认值", "spamfilter_green": "绿色: 此消息不是垃圾邮件", "spamfilter_hint": "第一个值表示\"低垃圾邮件分数\",第二个值表示\"高垃圾邮件分数\"。", @@ -1313,8 +1337,8 @@ "spamfilter_table_empty": "数据为空", "spamfilter_table_remove": "删除", "spamfilter_table_rule": "规则", - "spamfilter_wl": "白名单", - "spamfilter_wl_desc": "白名单中地址永远不会被标记为垃圾邮件。此处可以使用通配符 \"*\"。此过滤器也会应用到直接别名 (只指向一个目标邮箱),但不会应用到\"接收所有\"别名和邮箱地址本身。", + "spamfilter_wl": "允许名单", + "spamfilter_wl_desc": "允许名单中地址永远不会被标记为垃圾邮件。此处可以使用通配符 \"*\"。此过滤器也会应用到直接别名 (只指向一个目标邮箱),但不会应用到\"接收所有\"别名和邮箱地址本身。", "spamfilter_yellow": "黄色: 此为垃圾邮件,会被标记为垃圾邮件并且移入垃圾邮件文件夹", "status": "状态", "sync_jobs": "同步任务", @@ -1357,7 +1381,10 @@ "protocols": "协议", "authentication": "认证", "tfa_info": "两步验证有助于保护您的账户安全。启用后,对于不支持两步验证的应用程序或服务(例如邮件客户端),需要使用应用专用密码进行登录。", - "overview": "概览" + "overview": "概览", + "expire_never": "永不过期", + "forever": "永久", + "spam_aliases_info": "垃圾邮件别名是一种临时电子邮件地址,可用于保护真实电子邮件地址。
    还可以选择设置过期时间,以便在设定的时间后自动停用别名,从而有效地销毁被滥用或泄露的地址。" }, "warning": { "cannot_delete_self": "不能删除已登录的用户", diff --git a/data/web/lang/lang.zh-tw.json b/data/web/lang/lang.zh-tw.json index 592efa809..e7310be7a 100644 --- a/data/web/lang/lang.zh-tw.json +++ b/data/web/lang/lang.zh-tw.json @@ -24,7 +24,7 @@ "sogo_access": "管理 SOGo 存取權", "sogo_profile_reset": "重設 SOGo 個人資料", "spam_alias": "臨時別名", - "spam_policy": "黑名單/白名單", + "spam_policy": "封鎖名單/允許名單", "spam_score": "垃圾郵件分數", "syncjobs": "同步任務", "tls_policy": "TLS 規則", @@ -1180,7 +1180,7 @@ "no_last_login": "沒有最後 UI 登入訊息", "no_record": "沒有紀錄", "open_logs": "開啟日誌", - "open_webmail_sso": "网络邮件", + "open_webmail_sso": "網頁信箱", "password": "密碼", "password_now": "目前密碼 (確認更改)", "password_repeat": "密碼 (再次輸入)", diff --git a/data/web/mobileconfig.php b/data/web/mobileconfig.php index 44aaa30ae..c57c48857 100644 --- a/data/web/mobileconfig.php +++ b/data/web/mobileconfig.php @@ -34,15 +34,15 @@ catch(PDOException $e) { if (isset($_GET['only_email'])) { $onlyEmailAccount = true; - $description = 'IMAP'; + $description = 'IMAP'; } else { $onlyEmailAccount = false; - $description = 'IMAP, CalDAV, CardDAV'; + $description = 'IMAP, CalDAV, CardDAV'; } if (isset($_GET['app_password'])) { $app_password = true; $description .= ' with application password'; - + if (strpos($_SERVER['HTTP_USER_AGENT'], 'iPad') !== FALSE) $platform = 'iPad'; elseif (strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') !== FALSE) @@ -51,8 +51,9 @@ if (isset($_GET['app_password'])) { $platform = 'Mac'; else $platform = $_SERVER['HTTP_USER_AGENT']; - - $password = bin2hex(openssl_random_pseudo_bytes(16)); + + $password = password_generate(); + $attr = array( 'app_name' => $platform, 'app_passwd' => $password, @@ -64,6 +65,7 @@ if (isset($_GET['app_password'])) { $attr['protocols'][] = 'dav_access'; } app_passwd("add", $attr); + $password = htmlspecialchars($password, ENT_NOQUOTES); } else { $app_password = false; } diff --git a/data/web/mta-sts.php b/data/web/mta-sts.php index 51c39eb2f..0c6f1a248 100644 --- a/data/web/mta-sts.php +++ b/data/web/mta-sts.php @@ -6,7 +6,31 @@ if (!isset($_SERVER['HTTP_HOST']) || strpos($_SERVER['HTTP_HOST'], 'mta-sts.') ! exit; } -$domain = str_replace('mta-sts.', '', $_SERVER['HTTP_HOST']); +$host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']); +$domain = idn_to_ascii(strtolower(str_replace('mta-sts.', '', $host)), 0, INTL_IDNA_VARIANT_UTS46); + +// Validate domain or return 404 on error +if ($domain === false || empty($domain)) { + http_response_code(404); + exit; +} + +// Check if domain is an alias domain and resolve to target domain +try { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $alias_row = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($alias_row !== false && !empty($alias_row['target_domain'])) { + // This is an alias domain, use the target domain for MTA-STS lookup + $domain = $alias_row['target_domain']; + } +} catch (PDOException $e) { + // On database error, return 404 + http_response_code(404); + exit; +} + $mta_sts = mailbox('get', 'mta_sts', $domain); if (count($mta_sts) == 0 || diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php index 00709fe5f..07456a7d1 100644 --- a/data/web/sogo-auth.php +++ b/data/web/sogo-auth.php @@ -12,18 +12,21 @@ $session_var_pass = 'sogo-sso-pass'; if (isset($_SERVER['PHP_AUTH_USER'])) { // load prerequisites only when required require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; + $username = $_SERVER['PHP_AUTH_USER']; $password = $_SERVER['PHP_AUTH_PW']; - $is_eas = false; - $is_dav = false; + + // Determine service type for protocol access check + $service = 'NONE'; $original_uri = isset($_SERVER['HTTP_X_ORIGINAL_URI']) ? $_SERVER['HTTP_X_ORIGINAL_URI'] : ''; if (preg_match('/^(\/SOGo|)\/dav.*/', $original_uri) === 1) { - $is_dav = true; + $service = 'DAV'; } elseif (preg_match('/^(\/SOGo|)\/Microsoft-Server-ActiveSync.*/', $original_uri) === 1) { - $is_eas = true; + $service = 'EAS'; } - $login_check = check_login($username, $password, array('dav' => $is_dav, 'eas' => $is_eas)); + + $login_check = check_login($username, $password, array('service' => $service)); if ($login_check === 'user') { header("X-User: $username"); header("X-Auth: Basic ".base64_encode("$username:$password")); @@ -47,6 +50,11 @@ elseif (isset($_GET['login'])) { (($_SESSION['acl']['login_as'] == "1" && $ALLOW_ADMIN_EMAIL_LOGIN !== 0) || ($is_dual === false && $login == $_SESSION['mailcow_cc_username']))) { if (filter_var($login, FILTER_VALIDATE_EMAIL)) { if (user_get_alias_details($login) !== false) { + // Block SOGo access if pending actions (2FA setup, password update) + if (!empty($_SESSION['pending_tfa_setup']) || !empty($_SESSION['pending_pw_update'])) { + header("Location: /"); + exit; + } // register username in session $_SESSION[$session_var_user_allowed][] = $login; // set dual login @@ -57,14 +65,13 @@ elseif (isset($_GET['login'])) { $_SESSION['mailcow_cc_role'] = "user"; } // update sasl logs - $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV'; $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES ('SSO', 0, :username, :remote_addr)"); $stmt->execute(array( ':username' => $login, ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']) )); // redirect to sogo (sogo will get the correct credentials via nginx auth_request - header("Location: /SOGo/so/{$login}"); + header("Location: /SOGo/so/"); exit; } } @@ -81,10 +88,7 @@ elseif (isset($_SERVER['HTTP_X_ORIGINAL_URI']) && strcasecmp(substr($_SERVER['HT } require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php'; - // extract email address from "/SOGo/so/user@domain/xy" - $url_parts = explode("/", $_SERVER['HTTP_X_ORIGINAL_URI']); $email_list = array( - $url_parts[3], // Requested mailbox ($_SESSION['mailcow_cc_username'] ?? ''), // Current user ($_SESSION["dual-login"]["username"] ?? ''), // Dual login user ); @@ -95,7 +99,8 @@ elseif (isset($_SERVER['HTTP_X_ORIGINAL_URI']) && strcasecmp(substr($_SERVER['HT filter_var($email, FILTER_VALIDATE_EMAIL) && is_array($_SESSION[$session_var_user_allowed]) && in_array($email, $_SESSION[$session_var_user_allowed]) && - !$_SESSION['pending_pw_update'] + !$_SESSION['pending_pw_update'] && + !$_SESSION['pending_tfa_setup'] ) { $username = $email; $password = file_get_contents("/etc/sogo-sso/sogo-sso.pass"); diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig index a8d0f6f39..9e20b08a5 100644 --- a/data/web/templates/base.twig +++ b/data/web/templates/base.twig @@ -11,8 +11,8 @@