diff --git a/.github/workflows/check_prs_if_on_staging.yml b/.github/workflows/check_prs_if_on_staging.yml index 3e779a448..7f840c568 100644 --- a/.github/workflows/check_prs_if_on_staging.yml +++ b/.github/workflows/check_prs_if_on_staging.yml @@ -10,7 +10,7 @@ jobs: if: github.event.pull_request.base.ref != 'staging' #check if the target branch is not staging steps: - name: Send message - uses: thollander/actions-comment-pull-request@v2.5.0 + uses: thollander/actions-comment-pull-request@v3.0.1 with: GITHUB_TOKEN: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }} message: | diff --git a/.gitignore b/.gitignore index 43bc74e0e..a0558e6dd 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ data/conf/sogo/custom-theme.js data/conf/sogo/plist_ldap data/conf/sogo/plist_ldap.sh data/conf/sogo/sieve.creds +data/conf/sogo/cron.creds data/conf/sogo/sogo-full.svg data/gitea/ data/gogs/ diff --git a/data/Dockerfiles/dockerapi/main.py b/data/Dockerfiles/dockerapi/main.py index fca61bb02..6f7a6042c 100644 --- a/data/Dockerfiles/dockerapi/main.py +++ b/data/Dockerfiles/dockerapi/main.py @@ -90,7 +90,7 @@ async def get_container(container_id : str): if container._id == container_id: container_info = await container.show() return Response(content=json.dumps(container_info, indent=4), media_type="application/json") - + res = { "type": "danger", "msg": "no container found" @@ -130,7 +130,7 @@ async def get_containers(): async def post_containers(container_id : str, post_action : str, request: Request): global dockerapi - try : + try: request_json = await request.json() except Exception as err: request_json = {} @@ -191,7 +191,7 @@ async def post_container_update_stats(container_id : str): stats = json.loads(await dockerapi.redis_client.get(container_id + '_stats')) return Response(content=json.dumps(stats, indent=4), media_type="application/json") - + # PubSub Handler async def handle_pubsub_messages(channel: aioredis.client.PubSub): @@ -244,7 +244,7 @@ async def handle_pubsub_messages(channel: aioredis.client.PubSub): dockerapi.logger.error("Unknwon PubSub recieved - %s" % json.dumps(data_json)) else: dockerapi.logger.error("Unknwon PubSub recieved - %s" % json.dumps(data_json)) - + await asyncio.sleep(0.0) except asyncio.TimeoutError: pass diff --git a/data/Dockerfiles/dockerapi/modules/DockerApi.py b/data/Dockerfiles/dockerapi/modules/DockerApi.py index 560199091..4701cbf51 100644 --- a/data/Dockerfiles/dockerapi/modules/DockerApi.py +++ b/data/Dockerfiles/dockerapi/modules/DockerApi.py @@ -159,7 +159,7 @@ class DockerApi: postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix') # todo: check each exit code res = { 'type': 'success', 'msg': 'Scheduled immediate delivery'} - return Response(content=json.dumps(res, indent=4), media_type="application/json") + return Response(content=json.dumps(res, indent=4), media_type="application/json") # api call: container_post - post_action: exec - cmd: mailq - task: list def container_post__exec__mailq__list(self, request_json, **kwargs): if 'container_id' in kwargs: @@ -318,7 +318,7 @@ class DockerApi: if 'username' in request_json and 'script_name' in request_json: for container in self.sync_docker_client.containers.list(filters=filters): - cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"] + cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"] sieve_return = container.exec_run(cmd) return self.exec_run_handler('utf8_text_only', sieve_return) # api call: container_post - post_action: exec - cmd: maildir - task: cleanup @@ -342,6 +342,30 @@ class DockerApi: cmd = ["/bin/bash", "-c", cmd_vmail] maildir_cleanup = container.exec_run(cmd, user='vmail') return self.exec_run_handler('generic', maildir_cleanup) + # api call: container_post - post_action: exec - cmd: maildir - task: move + def container_post__exec__maildir__move(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'old_maildir' in request_json and 'new_maildir' in request_json: + for container in self.sync_docker_client.containers.list(filters=filters): + vmail_name = request_json['old_maildir'].replace("'", "'\\''") + new_vmail_name = request_json['new_maildir'].replace("'", "'\\''") + cmd_vmail = f"if [[ -d '/var/vmail/{vmail_name}' ]]; then /bin/mv '/var/vmail/{vmail_name}' '/var/vmail/{new_vmail_name}'; fi" + + index_name = request_json['old_maildir'].split("/") + new_index_name = request_json['new_maildir'].split("/") + if len(index_name) > 1 and len(new_index_name) > 1: + index_name = index_name[1].replace("'", "'\\''") + "@" + index_name[0].replace("'", "'\\''") + new_index_name = new_index_name[1].replace("'", "'\\''") + "@" + new_index_name[0].replace("'", "'\\''") + cmd_vmail_index = f"if [[ -d '/var/vmail_index/{index_name}' ]]; then /bin/mv '/var/vmail_index/{index_name}' '/var/vmail_index/{new_index_name}_index'; fi" + cmd = ["/bin/bash", "-c", cmd_vmail + " && " + cmd_vmail_index] + else: + cmd = ["/bin/bash", "-c", cmd_vmail] + maildir_move = container.exec_run(cmd, user='vmail') + return self.exec_run_handler('generic', maildir_move) # api call: container_post - post_action: exec - cmd: rspamd - task: worker_password def container_post__exec__rspamd__worker_password(self, request_json, **kwargs): if 'container_id' in kwargs: @@ -374,6 +398,121 @@ class DockerApi: self.logger.error('failed changing Rspamd password') res = { 'type': 'danger', 'msg': 'command did not complete' } return Response(content=json.dumps(res, indent=4), media_type="application/json") + # api call: container_post - post_action: exec - cmd: sogo - task: rename + def container_post__exec__sogo__rename_user(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + if 'old_username' in request_json and 'new_username' in request_json: + for container in self.sync_docker_client.containers.list(filters=filters): + old_username = request_json['old_username'].replace("'", "'\\''") + new_username = request_json['new_username'].replace("'", "'\\''") + + sogo_return = container.exec_run(["/bin/bash", "-c", f"sogo-tool rename-user '{old_username}' '{new_username}'"], user='sogo') + return self.exec_run_handler('generic', sogo_return) + # api call: container_post - post_action: exec - cmd: doveadm - task: get_acl + def container_post__exec__doveadm__get_acl(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(filters=filters): + id = request_json['id'].replace("'", "'\\''") + + shared_folders = container.exec_run(["/bin/bash", "-c", f"doveadm mailbox list -u '{id}'"]) + shared_folders = shared_folders.output.decode('utf-8') + shared_folders = shared_folders.splitlines() + + formatted_acls = [] + mailbox_seen = [] + for shared_folder in shared_folders: + if "Shared" not in shared_folder: + mailbox = shared_folder.replace("'", "'\\''") + if mailbox in mailbox_seen: + continue + + acls = container.exec_run(["/bin/bash", "-c", f"doveadm acl get -u '{id}' '{mailbox}'"]) + acls = acls.output.decode('utf-8').strip().splitlines() + if len(acls) >= 2: + for acl in acls[1:]: + user_id, rights = acl.split(maxsplit=1) + user_id = user_id.split('=')[1] + mailbox_seen.append(mailbox) + formatted_acls.append({ 'user': id, 'id': user_id, 'mailbox': mailbox, 'rights': rights.split() }) + elif "Shared" in shared_folder and "/" in shared_folder: + shared_folder = shared_folder.split("/") + if len(shared_folder) < 3: + continue + + user = shared_folder[1].replace("'", "'\\''") + mailbox = '/'.join(shared_folder[2:]).replace("'", "'\\''") + if mailbox in mailbox_seen: + continue + + acls = container.exec_run(["/bin/bash", "-c", f"doveadm acl get -u '{user}' '{mailbox}'"]) + acls = acls.output.decode('utf-8').strip().splitlines() + if len(acls) >= 2: + for acl in acls[1:]: + user_id, rights = acl.split(maxsplit=1) + user_id = user_id.split('=')[1].replace("'", "'\\''") + if user_id == id and mailbox not in mailbox_seen: + mailbox_seen.append(mailbox) + formatted_acls.append({ 'user': user, 'id': id, 'mailbox': mailbox, 'rights': rights.split() }) + + return Response(content=json.dumps(formatted_acls, indent=4), media_type="application/json") + # api call: container_post - post_action: exec - cmd: doveadm - task: delete_acl + def container_post__exec__doveadm__delete_acl(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(filters=filters): + user = request_json['user'].replace("'", "'\\''") + mailbox = request_json['mailbox'].replace("'", "'\\''") + id = request_json['id'].replace("'", "'\\''") + + if user and mailbox and id: + acl_delete_return = container.exec_run(["/bin/bash", "-c", f"doveadm acl delete -u '{user}' '{mailbox}' 'user={id}'"]) + return self.exec_run_handler('generic', acl_delete_return) + # api call: container_post - post_action: exec - cmd: doveadm - task: set_acl + def container_post__exec__doveadm__set_acl(self, request_json, **kwargs): + if 'container_id' in kwargs: + filters = {"id": kwargs['container_id']} + elif 'container_name' in kwargs: + filters = {"name": kwargs['container_name']} + + for container in self.sync_docker_client.containers.list(filters=filters): + user = request_json['user'].replace("'", "'\\''") + mailbox = request_json['mailbox'].replace("'", "'\\''") + id = request_json['id'].replace("'", "'\\''") + rights = "" + + available_rights = [ + "admin", + "create", + "delete", + "expunge", + "insert", + "lookup", + "post", + "read", + "write", + "write-deleted", + "write-seen" + ] + for right in request_json['rights']: + right = right.replace("'", "'\\''").lower() + if right in available_rights: + rights += right + " " + + if user and mailbox and id and rights: + acl_set_return = container.exec_run(["/bin/bash", "-c", f"doveadm acl set -u '{user}' '{mailbox}' 'user={id}' {rights}"]) + return self.exec_run_handler('generic', acl_set_return) + # Collect host stats async def get_host_stats(self, wait=5): @@ -462,7 +601,7 @@ class DockerApi: except: pass return ''.join(total_data) - + try : socket = container.exec_run([shell_cmd], stdin=True, socket=True, user=user).output._sock if not cmd.endswith("\n"): diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index 7d9623a63..c394bac5e 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -114,15 +114,15 @@ if [[ "${FLATCURVE_EXPERIMENTAL}" =~ ^([yY][eE][sS]|[yY]) ]]; then echo -e "\e[33mActivating Flatcurve as FTS Backend...\e[0m" echo -e "\e[33mDepending on your previous setup a full reindex might be needed... \e[0m" echo -e "\e[34mVisit https://docs.mailcow.email/manual-guides/Dovecot/u_e-dovecot-fts/#fts-related-dovecot-commands to learn how to reindex\e[0m" -echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify fts fts_flatcurve listescape replication' > /etc/dovecot/mail_plugins +echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify fts fts_flatcurve listescape replication lazy_expunge' > /etc/dovecot/mail_plugins echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify mail_log fts fts_flatcurve listescape replication' > /etc/dovecot/mail_plugins_imap echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl fts fts_flatcurve notify listescape replication' > /etc/dovecot/mail_plugins_lmtp elif [[ "${SKIP_SOLR}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then -echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify listescape replication' > /etc/dovecot/mail_plugins +echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify listescape replication lazy_expunge' > /etc/dovecot/mail_plugins echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify listescape replication mail_log' > /etc/dovecot/mail_plugins_imap echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl notify listescape replication' > /etc/dovecot/mail_plugins_lmtp else -echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify fts fts_solr listescape replication' > /etc/dovecot/mail_plugins +echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify fts fts_solr listescape replication lazy_expunge' > /etc/dovecot/mail_plugins echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify mail_log fts fts_solr listescape replication' > /etc/dovecot/mail_plugins_imap echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl fts fts_solr notify listescape replication' > /etc/dovecot/mail_plugins_lmtp fi @@ -254,6 +254,8 @@ 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 if [[ "${MASTER}" =~ ^([nN][oO]|[nN])+$ ]]; then # Toggling MASTER will result in a rebuild of containers, so the quota script will be recreated @@ -281,6 +283,17 @@ else chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem fi +# Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20) +if grep -qE 'ssl_min_protocol\s*=\s*(TLSv1|TLSv1\.1)\s*$' /etc/dovecot/dovecot.conf /etc/dovecot/extra.conf; then + sed -i '/\[openssl_init\]/a ssl_conf = ssl_configuration' /etc/ssl/openssl.cnf + + echo "[ssl_configuration]" >> /etc/ssl/openssl.cnf + echo "system_default = tls_system_default" >> /etc/ssl/openssl.cnf + echo "[tls_system_default]" >> /etc/ssl/openssl.cnf + echo "MinProtocol = TLSv1" >> /etc/ssl/openssl.cnf + echo "CipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf +fi + # Compile sieve scripts sievec /var/vmail/sieve/global_sieve_before.sieve sievec /var/vmail/sieve/global_sieve_after.sieve diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index 0ac722b22..e9ebe071c 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -1,17 +1,17 @@ -FROM php:8.2-fpm-alpine3.18 +FROM php:8.2-fpm-alpine3.20 LABEL maintainer = "The Infrastructure Company GmbH " # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?.*)$ -ARG APCU_PECL_VERSION=5.1.23 +ARG APCU_PECL_VERSION=5.1.24 # renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?.*)$ ARG IMAGICK_PECL_VERSION=3.7.0 # renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?.*)$ -ARG MAILPARSE_PECL_VERSION=3.1.6 +ARG MAILPARSE_PECL_VERSION=3.1.8 # renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?.*)$ ARG MEMCACHED_PECL_VERSION=3.2.0 # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?.*)$ -ARG REDIS_PECL_VERSION=6.0.2 +ARG REDIS_PECL_VERSION=6.1.0 # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?.*)$ ARG COMPOSER_VERSION=2.6.6 diff --git a/data/Dockerfiles/postfix/docker-entrypoint.sh b/data/Dockerfiles/postfix/docker-entrypoint.sh index c97b12844..7b6c5d4aa 100755 --- a/data/Dockerfiles/postfix/docker-entrypoint.sh +++ b/data/Dockerfiles/postfix/docker-entrypoint.sh @@ -12,4 +12,15 @@ if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf fi +# Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20) +if grep -qE '\!SSLv2|\!SSLv3|>=TLSv1(\.[0-1])?$' /opt/postfix/conf/main.cf /opt/postfix/conf/extra.cf; then + sed -i '/\[openssl_init\]/a ssl_conf = ssl_configuration' /etc/ssl/openssl.cnf + + echo "[ssl_configuration]" >> /etc/ssl/openssl.cnf + echo "system_default = tls_system_default" >> /etc/ssl/openssl.cnf + echo "[tls_system_default]" >> /etc/ssl/openssl.cnf + echo "MinProtocol = TLSv1" >> /etc/ssl/openssl.cnf + echo "CipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf +fi + exec "$@" diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile index 06c8f5ebc..152874b42 100644 --- a/data/Dockerfiles/rspamd/Dockerfile +++ b/data/Dockerfiles/rspamd/Dockerfile @@ -1,8 +1,8 @@ FROM debian:bookworm-slim -LABEL maintainer = "The Infrastructure Company GmbH " +LABEL maintainer="The Infrastructure Company GmbH " ARG DEBIAN_FRONTEND=noninteractive -ARG RSPAMD_VER=rspamd_3.9.1-1~82f43560f +ARG RSPAMD_VER=rspamd_3.10.2-1~b8a232043 ARG CODENAME=bookworm ENV LC_ALL=C diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index 7b8b1c717..78da39bec 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -33,13 +33,14 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \ && gosu nobody true \ && mkdir /usr/share/doc/sogo \ && touch /usr/share/doc/sogo/empty.sh \ - && apt-key adv --keyserver keys.openpgp.org --recv-key 74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9 \ + && wget http://www.axis.cz/linux/debian/axis-archive-keyring.deb -O /tmp/axis-archive-keyring.deb \ + && apt install -y /tmp/axis-archive-keyring.deb \ && echo "deb [trusted=yes] ${SOGO_DEBIAN_REPOSITORY} ${DEBIAN_VERSION} sogo-v5" > /etc/apt/sources.list.d/sogo.list \ && apt-get update && apt-get install -y --no-install-recommends \ sogo \ sogo-activesync \ && apt-get autoclean \ - && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/sogo.list \ + && rm -rf /var/lib/apt/lists/* \ && touch /etc/default/locale COPY ./bootstrap-sogo.sh /bootstrap-sogo.sh diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index 6a87f2ecb..6721204cb 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -170,6 +170,8 @@ smtputf8_enable = no submission_smtpd_tls_mandatory_protocols = >=TLSv1.2 smtps_smtpd_tls_mandatory_protocols = >=TLSv1.2 parent_domain_matches_subdomains = debug_peer_list,fast_flush_domains,mynetworks,qmqpd_authorized_clients +# This Option is added to correctly set the X-Original-To Header when mails are send to lmtp (dovecot) +lmtp_destination_recipient_limit=1 # DO NOT EDIT ANYTHING BELOW # # Overrides # diff --git a/data/conf/postfix/master.cf b/data/conf/postfix/master.cf index df91a3900..d5114df28 100644 --- a/data/conf/postfix/master.cf +++ b/data/conf/postfix/master.cf @@ -105,7 +105,7 @@ retry unix - - n - - error discard unix - - n - - discard local unix - n n - - local virtual unix - n n - - virtual -lmtp unix - - n - - lmtp +lmtp unix - - n - - lmtp flags=O anvil unix - - n - 1 anvil scache unix - - n - 1 scache maildrop unix - n n - - pipe flags=DRhu diff --git a/data/conf/postfix/postscreen_access.cidr b/data/conf/postfix/postscreen_access.cidr index f77d4e9ce..7a1cbf6ae 100644 --- a/data/conf/postfix/postscreen_access.cidr +++ b/data/conf/postfix/postscreen_access.cidr @@ -1,6 +1,6 @@ -# Whitelist generated by Postwhite v3.4 on Sun Sep 1 00:19:07 UTC 2024 +# Whitelist generated by Postwhite v3.4 on Fri Nov 1 00:18:49 UTC 2024 # https://github.com/stevejenkins/postwhite/ -# 1994 total rules +# 2013 total rules 2a00:1450:4000::/36 permit 2a01:111:f400::/48 permit 2a01:111:f403:8000::/50 permit @@ -21,7 +21,6 @@ 8.25.196.0/23 permit 8.39.54.0/23 permit 8.40.222.0/23 permit -10.162.0.0/16 permit 12.130.86.238 permit 13.110.208.0/21 permit 13.110.209.0/24 permit @@ -32,11 +31,10 @@ 15.200.21.50 permit 15.200.44.248 permit 15.200.201.185 permit -17.41.0.0/16 permit 17.57.155.0/24 permit 17.57.156.0/24 permit 17.58.0.0/16 permit -17.142.0.0/15 permit +17.143.234.140/30 permit 18.156.89.250 permit 18.157.243.190 permit 18.194.95.56 permit @@ -115,11 +113,15 @@ 40.92.0.0/16 permit 40.107.0.0/16 permit 40.112.65.63 permit +40.233.64.216 permit +40.233.83.78 permit +40.233.88.28 permit 43.228.184.0/22 permit 44.206.138.57 permit 44.217.45.156 permit 44.236.56.93 permit 44.238.220.251 permit +45.14.148.0/22 permit 46.19.170.16 permit 46.226.48.0/21 permit 46.228.36.37 permit @@ -181,7 +183,9 @@ 50.18.126.162 permit 50.31.32.0/19 permit 50.31.36.205 permit -50.56.130.220/30 permit +50.56.130.220 permit +50.56.130.221 permit +50.56.130.222 permit 52.1.14.157 permit 52.5.230.59 permit 52.27.5.72 permit @@ -208,12 +212,12 @@ 52.96.223.2 permit 52.96.228.130 permit 52.96.229.242 permit -52.100.0.0/14 permit +52.100.0.0/15 permit +52.102.0.0/16 permit 52.103.0.0/17 permit 52.119.213.144/28 permit 52.185.106.240/28 permit 52.200.59.0/24 permit -52.205.61.79 permit 52.207.191.216 permit 52.222.62.51 permit 52.222.73.83 permit @@ -225,7 +229,6 @@ 52.236.28.240/28 permit 54.90.148.255 permit 54.165.19.38 permit -54.172.97.247 permit 54.174.52.0/24 permit 54.174.57.0/24 permit 54.174.59.0/24 permit @@ -242,16 +245,12 @@ 54.244.54.130 permit 54.244.242.0/24 permit 54.255.61.23 permit +57.103.64.0/18 permit 62.13.128.0/24 permit -62.13.128.196 permit 62.13.129.128/25 permit -62.13.136.0/22 permit -62.13.140.0/22 permit -62.13.144.0/22 permit -62.13.148.0/23 permit -62.13.150.0/23 permit -62.13.152.0/23 permit -62.13.159.196 permit +62.13.136.0/21 permit +62.13.144.0/21 permit +62.13.152.0/21 permit 62.17.146.128/26 permit 62.179.121.0/24 permit 62.201.172.0/27 permit @@ -273,7 +272,6 @@ 64.127.115.252 permit 64.132.88.0/23 permit 64.132.92.0/24 permit -64.147.123.128/27 permit 64.207.219.7 permit 64.207.219.8 permit 64.207.219.9 permit @@ -1318,7 +1316,9 @@ 129.41.77.70 permit 129.41.169.249 permit 129.80.5.164 permit +129.80.64.36 permit 129.80.67.121 permit +129.80.145.156 permit 129.145.74.12 permit 129.146.88.28 permit 129.146.147.105 permit @@ -1329,6 +1329,9 @@ 129.153.168.146 permit 129.153.190.200 permit 129.153.194.228 permit +129.154.255.129 permit +129.158.56.255 permit +129.159.22.159 permit 129.159.87.137 permit 129.213.195.191 permit 130.61.9.72 permit @@ -1352,6 +1355,7 @@ 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.178.49 permit 136.143.182.0/23 permit 136.143.184.0/24 permit @@ -1373,6 +1377,7 @@ 139.138.58.119 permit 139.167.79.86 permit 139.180.17.0/24 permit +140.238.148.191 permit 141.148.159.229 permit 141.193.32.0/23 permit 141.193.184.32/27 permit @@ -1381,6 +1386,7 @@ 141.193.185.32/27 permit 141.193.185.64/26 permit 141.193.185.128/25 permit +143.47.120.152 permit 143.55.224.0/21 permit 143.55.232.0/22 permit 143.55.236.0/22 permit @@ -1394,7 +1400,10 @@ 144.178.38.0/24 permit 145.253.228.160/29 permit 145.253.239.128/29 permit -146.20.14.104/30 permit +146.20.14.104 permit +146.20.14.105 permit +146.20.14.106 permit +146.20.14.107 permit 146.20.112.0/26 permit 146.20.113.0/24 permit 146.20.191.0/24 permit @@ -1413,10 +1422,14 @@ 149.72.248.236 permit 149.97.173.180 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.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 @@ -1497,6 +1510,7 @@ 167.220.67.232/29 permit 168.138.5.36 permit 168.138.73.51 permit +168.138.77.31 permit 168.245.0.0/17 permit 168.245.12.252 permit 168.245.46.9 permit @@ -1519,6 +1533,7 @@ 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 173.194.0.0/16 permit @@ -1537,7 +1552,6 @@ 174.36.114.148/30 permit 174.36.114.152/29 permit 174.37.67.28/30 permit -174.129.203.189 permit 175.41.215.51 permit 176.32.105.0/24 permit 176.32.127.0/24 permit @@ -1610,6 +1624,8 @@ 188.172.128.0/20 permit 192.0.64.0/18 permit 192.18.139.154 permit +192.18.145.36 permit +192.18.152.58 permit 192.30.252.0/22 permit 192.161.144.0/20 permit 192.162.87.0/24 permit @@ -1677,6 +1693,7 @@ 199.122.123.0/24 permit 199.127.232.0/22 permit 199.255.192.0/22 permit +202.12.124.128/27 permit 202.129.242.0/23 permit 202.165.102.47 permit 202.177.148.100 permit @@ -1729,7 +1746,9 @@ 204.92.114.204/31 permit 204.141.32.0/23 permit 204.141.42.0/23 permit -204.220.160.0/20 permit +204.220.160.0/21 permit +204.220.168.0/21 permit +204.220.176.0/20 permit 204.232.168.0/24 permit 205.139.110.0/24 permit 205.201.128.0/20 permit diff --git a/data/conf/rspamd/local.d/mime_types.conf b/data/conf/rspamd/local.d/mime_types.conf index 08a08b699..83561862f 100644 --- a/data/conf/rspamd/local.d/mime_types.conf +++ b/data/conf/rspamd/local.d/mime_types.conf @@ -1,27 +1,45 @@ +############################################################################### +# This list is added/merged with defined defaults in LUA module: +# https://github.com/rspamd/rspamd/blob/master/src/plugins/lua/mime_types.lua +############################################################################### + # Extensions that are treated as 'bad' # Number is score multiply factor bad_extensions = { - scr = 20, - lnk = 20, - exe = 20, - msi = 1, - msp = 1, - msu = 1, - jar = 2, - com = 20, - bat = 4, - cmd = 4, - ps1 = 4, - ace = 4, - arj = 4, + apk = 4, + appx = 4, + appxbundle = 4, + bat = 8, cab = 20, + cmd = 8, + com = 20, + diagcfg = 4, + diagpack = 4, + dmg = 8, + ex = 20, + ex_ = 20, + exe = 20, + img = 4, + jar = 8, + jnlp = 8, + js = 8, + jse = 8, + lnk = 20, + mjs = 8, + msi = 4, + msix = 4, + msixbundle = 4, + ps1 = 8, + scr = 20, + sct = 20, + vb = 20, + vbe = 20, vbs = 20, - hta = 4, - shs = 4, - wsc = 4, - wsf = 4, - iso = 8, - img = 8 + vhd = 4, + py = 4, + reg = 8, + scf = 8, + vhdx = 4, }; # Extensions that are particularly penalized for archives @@ -30,18 +48,14 @@ bad_archive_extensions = { docx = 0.5, xlsx = 0.5, pdf = 1.0, - jar = 3, - js = 0.5, - vbs = 20, - exe = 20 + jar = 12, + jnlp = 12, + bat = 12, + cmd = 12, }; # Used to detect another archive in archive archive_extensions = { - zip = 1, - arj = 1, - rar = 1, - ace = 1, - 7z = 1, - cab = 1 -}; + tar = 1, + gz = 1, +}; \ No newline at end of file diff --git a/data/conf/rspamd/local.d/options.inc b/data/conf/rspamd/local.d/options.inc index 105bbdcd8..99197ff55 100644 --- a/data/conf/rspamd/local.d/options.inc +++ b/data/conf/rspamd/local.d/options.inc @@ -2,6 +2,7 @@ dns { enable_dnssec = true; } map_watch_interval = 30s; +task_timeout = 30s; disable_monitoring = true; # In case a task times out (like DNS lookup), soft reject the message # instead of silently accepting the message without further processing. diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 31e158709..1b5b577f8 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -1250,7 +1250,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':active' => $active )); - + if (isset($_data['acl'])) { $_data['acl'] = (array)$_data['acl']; $_data['spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0; @@ -1282,14 +1282,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $_data['quarantine_attachments'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_attachments']); $_data['quarantine_notification'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_notification']); $_data['quarantine_category'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_category']); - $_data['app_passwds'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_app_passwds']); - $_data['pw_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_pw_reset']); + $_data['app_passwds'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_app_passwds']); + $_data['pw_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_pw_reset']); } try { - $stmt = $pdo->prepare("INSERT INTO `user_acl` + $stmt = $pdo->prepare("INSERT INTO `user_acl` (`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`, - `pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`, `pw_reset`) + `pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`, `pw_reset`) VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset, :pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds, :pw_reset) "); $stmt->execute(array( @@ -1329,7 +1329,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'rl_value' => $_data['rl_value'] ), $_extra); } - + try { update_sogo_static_view($username); } catch (PDOException $e) { @@ -1346,7 +1346,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); break; case 'mailbox_from_template': - $stmt = $pdo->prepare("SELECT * FROM `templates` + $stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `template` = :template AND type = 'mailbox'"); $stmt->execute(array( ":template" => $_data['template'] @@ -1359,16 +1359,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'msg' => 'template_missing' ); return false; - } - + } + $attribute_hash = sha1(json_encode($mbox_template_data["attributes"])); - $mbox_template_data = json_decode($mbox_template_data["attributes"], true); + $mbox_template_data = json_decode($mbox_template_data["attributes"], true); $mbox_template_data['domain'] = $_data['domain']; $mbox_template_data['local_part'] = $_data['local_part']; $mbox_template_data['authsource'] = $_data['authsource']; $mbox_template_data['attribute_hash'] = $attribute_hash; $mbox_template_data['quota'] = intval($mbox_template_data['quota'] / 1048576); - + $mailbox_attributes = array('acl' => array()); foreach ($mbox_template_data as $key => $value){ switch (true) { @@ -1529,7 +1529,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - + // check attributes $attr = array(); $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); @@ -1619,7 +1619,7 @@ 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; - } + } else { $attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']); $attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); @@ -2171,7 +2171,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - + // check if param is whitelisted if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){ // bad option @@ -2864,11 +2864,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { // check name if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){ // keep template name of Default template - $_data["template"] = $is_now["template"]; + $_data["template"] = $is_now["template"]; } else { - $_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"]; - } + $_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"]; + } // check attributes $attr = array(); $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); @@ -2895,10 +2895,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ":id" => $id , ":template" => $_data["template"] , ":attributes" => json_encode($attr) - )); + )); } - + $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -3266,7 +3266,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } catch (Exception $e) { } } - + $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), @@ -3285,8 +3285,199 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } return true; break; + case 'mailbox_rename': + $domain = $_data['domain']; + $old_local_part = $_data['old_local_part']; + $old_username = $old_local_part . "@" . $domain; + $new_local_part = $_data['new_local_part']; + $new_username = $new_local_part . "@" . $domain; + $create_alias = intval($_data['create_alias']); + + if (!filter_var($old_username, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('username_invalid', $old_username) + ); + return false; + } + if (!filter_var($new_username, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('username_invalid', $new_username) + ); + return false; + } + + $is_now = mailbox('get', 'mailbox_details', $old_username); + if (empty($is_now)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $is_now['domain'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + + // get imap acls + try { + $exec_fields = array( + 'cmd' => 'doveadm', + 'task' => 'get_acl', + 'id' => $old_username + ); + $imap_acls = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); + // delete imap acls + foreach ($imap_acls as $imap_acl) { + $exec_fields = array( + 'cmd' => 'doveadm', + 'task' => 'delete_acl', + 'user' => $imap_acl['user'], + 'mailbox' => $imap_acl['mailbox'], + 'id' => $imap_acl['id'] + ); + docker('post', 'dovecot-mailcow', 'exec', $exec_fields); + } + } catch (Exception $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => $e->getMessage() + ); + return false; + } + + // rename username in sql + try { + $pdo->beginTransaction(); + $pdo->exec('SET FOREIGN_KEY_CHECKS = 0'); + + // Update username in mailbox table + $pdo->prepare('UPDATE mailbox SET username = :new_username, local_part = :new_local_part WHERE username = :old_username') + ->execute([ + ':new_username' => $new_username, + ':new_local_part' => $new_local_part, + ':old_username' => $old_username + ]); + + $pdo->prepare("UPDATE alias SET address = :new_username, goto = :new_username2 WHERE address = :old_username") + ->execute([ + ':new_username' => $new_username, + ':new_username2' => $new_username, + ':old_username' => $old_username + ]); + + // Update the username in all related tables + $tables = [ + 'tags_mailbox' => ['username'], + 'sieve_filters' => ['username'], + 'app_passwd' => ['mailbox'], + 'user_acl' => ['username'], + 'da_acl' => ['username'], + 'quota2' => ['username'], + 'quota2replica' => ['username'], + 'pushover' => ['username'], + 'alias' => ['goto'], + "imapsync" => ['user2'], + 'bcc_maps' => ['local_dest', 'bcc_dest'], + 'recipient_maps' => ['old_dest', 'new_dest'], + 'sender_acl' => ['logged_in_as', 'send_as'] + ]; + foreach ($tables as $table => $columns) { + foreach ($columns as $column) { + $stmt = $pdo->prepare("UPDATE $table SET $column = :new_username WHERE $column = :old_username") + ->execute([ + ':new_username' => $new_username, + ':old_username' => $old_username + ]); + } + } + + // Update c_uid, c_name and mail in _sogo_static_view table + $pdo->prepare("UPDATE _sogo_static_view SET c_uid = :new_username, c_name = :new_username2, mail = :new_username3 WHERE c_uid = :old_username") + ->execute([ + ':new_username' => $new_username, + ':new_username2' => $new_username, + ':new_username3' => $new_username, + ':old_username' => $old_username + ]); + + // Re-enable foreign key checks + $pdo->exec('SET FOREIGN_KEY_CHECKS = 1'); + $pdo->commit(); + } catch (PDOException $e) { + // Rollback the transaction if something goes wrong + $pdo->rollBack(); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => $e->getMessage() + ); + return false; + } + + // move maildir + $exec_fields = array( + 'cmd' => 'maildir', + 'task' => 'move', + 'old_maildir' => $domain . '/' . $old_local_part, + 'new_maildir' => $domain . '/' . $new_local_part + ); + docker('post', 'dovecot-mailcow', 'exec', $exec_fields); + + // rename username in sogo + $exec_fields = array( + 'cmd' => 'sogo', + 'task' => 'rename_user', + 'old_username' => $old_username, + 'new_username' => $new_username + ); + docker('post', 'sogo-mailcow', 'exec', $exec_fields); + + // set imap acls + foreach ($imap_acls as $imap_acl) { + $user_id = ($imap_acl['id'] == $old_username) ? $new_username : $imap_acl['id']; + $user = ($imap_acl['user'] == $old_username) ? $new_username : $imap_acl['user']; + $exec_fields = array( + 'cmd' => 'doveadm', + 'task' => 'set_acl', + 'user' => $user, + 'mailbox' => $imap_acl['mailbox'], + 'id' => $user_id, + 'rights' => $imap_acl['rights'] + ); + docker('post', 'dovecot-mailcow', 'exec', $exec_fields); + } + + // create alias + if ($create_alias == 1) { + mailbox("add", "alias", array( + "address" => $old_username, + "goto" => $new_username, + "active" => 1, + "sogo_visible" => 1, + "private_comment" => sprintf($lang['success']['mailbox_renamed'], $old_username, $new_username) + )); + } + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_renamed', $old_username, $new_username) + ); + break; case 'mailbox_from_template': - $stmt = $pdo->prepare("SELECT * FROM `templates` + $stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `template` = :template AND type = 'mailbox'"); $stmt->execute(array( ":template" => $_data['template'] @@ -3336,7 +3527,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { break; } } - + $mailbox_attributes['quota'] = intval($mailbox_attributes['quota'] / 1048576); $result = mailbox('edit', 'mailbox', $mailbox_attributes); if ($result === false) return $result; @@ -3385,11 +3576,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { // check name if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){ // keep template name of Default template - $_data["template"] = $is_now["template"]; + $_data["template"] = $is_now["template"]; } else { - $_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"]; - } + $_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"]; + } // check attributes $attr = array(); $attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0; @@ -3409,11 +3600,11 @@ 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; - } - else { + } + else { foreach ($is_now as $key => $value){ $attr[$key] = $is_now[$key]; - } + } } if (isset($_data['acl'])) { $_data['acl'] = (array)$_data['acl']; @@ -3432,10 +3623,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0; $attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0; $attr['acl_pw_reset'] = (in_array('pw_reset', $_data['acl'])) ? 1 : 0; - } else { + } else { foreach ($is_now as $key => $value){ $attr[$key] = $is_now[$key]; - } + } } @@ -3447,7 +3638,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ":id" => $id , ":template" => $_data["template"] , ":attributes" => json_encode($attr) - )); + )); } @@ -3476,7 +3667,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $is_now = mailbox('get', 'mailbox_details', $mailbox); + $is_now = mailbox('get', 'mailbox_details', $mailbox); if(!empty($is_now)){ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $is_now['domain'])) { $_SESSION['return'][] = array( @@ -3503,15 +3694,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt->execute(array( ":username" => $mailbox, ":custom_attributes" => json_encode($attributes) - )); - + )); + $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('mailbox_modified', $mailbox) ); } - + return true; break; case 'resource': @@ -3593,7 +3784,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); } break; - case 'domain_wide_footer': + case 'domain_wide_footer': if (!is_array($_data['domains'])) { $domains = array(); $domains[] = $_data['domains']; @@ -3846,7 +4037,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { // prepend domain to array $params = array(); - foreach ($tags as $key => $val){ + foreach ($tags as $key => $val){ array_push($params, '%'.$_data.'%'); array_push($params, '%'.$val.'%'); } @@ -3855,7 +4046,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while($row = array_shift($rows)) { - if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], explode('@', $row['username'])[1])) + if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], explode('@', $row['username'])[1])) $mailboxes[] = $row['username']; } } @@ -4410,7 +4601,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { while($row = array_shift($rows)) { if ($_SESSION['mailcow_cc_role'] == "admin") $domains[] = $row['domain']; - elseif (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['domain'])) + elseif (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['domain'])) $domains[] = $row['domain']; } } else { @@ -4570,19 +4761,19 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } $_data = (isset($_data)) ? intval($_data) : null; - if (isset($_data)){ - $stmt = $pdo->prepare("SELECT * FROM `templates` + if (isset($_data)){ + $stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `id` = :id AND type = :type"); $stmt->execute(array( ":id" => $_data, ":type" => "domain" )); $row = $stmt->fetch(PDO::FETCH_ASSOC); - + if (empty($row)){ return false; } - + $row["attributes"] = json_decode($row["attributes"], true); return $row; } @@ -4590,11 +4781,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'domain'"); $stmt->execute(); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - + if (empty($rows)){ return false; } - + foreach($rows as $key => $row){ $rows[$key]["attributes"] = json_decode($row["attributes"], true); } @@ -4770,19 +4961,19 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } $_data = (isset($_data)) ? intval($_data) : null; - if (isset($_data)){ - $stmt = $pdo->prepare("SELECT * FROM `templates` + if (isset($_data)){ + $stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `id` = :id AND type = :type"); $stmt->execute(array( ":id" => $_data, ":type" => "mailbox" )); $row = $stmt->fetch(PDO::FETCH_ASSOC); - + if (empty($row)){ return false; } - + $row["attributes"] = json_decode($row["attributes"], true); return $row; } @@ -5224,7 +5415,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $ids = $_data['ids']; } - + foreach ($ids as $id) { // delete template $stmt = $pdo->prepare("DELETE FROM `templates` @@ -5537,7 +5728,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - + try { update_sogo_static_view($username); }catch (PDOException $e) { @@ -5572,7 +5763,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $ids = $_data['ids']; } - + foreach ($ids as $id) { // delete template $stmt = $pdo->prepare("DELETE FROM `templates` @@ -5581,7 +5772,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ":id" => $id, ":type" => "mailbox", ":template" => "Default" - )); + )); } $_SESSION['return'][] = array( @@ -5655,7 +5846,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); } break; - case 'tags_domain': + case 'tags_domain': if (!is_array($_data['domain'])) { $domains = array(); $domains[] = $_data['domain']; @@ -5668,7 +5859,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $wasModified = false; - foreach ($domains as $domain) { + foreach ($domains as $domain) { if (!is_valid_domain_name($domain)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -5685,7 +5876,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - + foreach($tags as $tag){ // delete tag $wasModified = true; @@ -5740,7 +5931,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { // delete tags foreach($tags as $tag){ $wasModified = true; - + $stmt = $pdo->prepare("DELETE FROM `tags_mailbox` WHERE `username` = :username AND `tag_name` = :tag_name"); $stmt->execute(array( ':username' => $username, @@ -5759,7 +5950,7 @@ 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')) && getenv('SKIP_SOGO') != "y") { try { update_sogo_static_view(); }catch (PDOException $e) { @@ -5770,6 +5961,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); } } - + return true; } diff --git a/data/web/inc/lib/composer.lock b/data/web/inc/lib/composer.lock index f0659ee80..fb1c3903b 100644 --- a/data/web/inc/lib/composer.lock +++ b/data/web/inc/lib/composer.lock @@ -1896,16 +1896,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { @@ -1914,7 +1914,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -1943,7 +1943,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" }, "funding": [ { @@ -1959,7 +1959,7 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/polyfill-ctype", @@ -2203,6 +2203,82 @@ ], "time": "2024-01-29T20:11:03+00:00" }, + { + "name": "symfony/polyfill-php81", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, { "name": "symfony/translation", "version": "v6.4.3", @@ -2517,34 +2593,37 @@ }, { "name": "twig/twig", - "version": "v3.4.3", + "version": "v3.14.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58" + "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/c38fd6b0b7f370c198db91ffd02e23b517426b58", - "reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72", + "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3" + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php81": "^1.29" }, "require-dev": { - "psr/container": "^1.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], "psr-4": { "Twig\\": "src/" } @@ -2577,7 +2656,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.4.3" + "source": "https://github.com/twigphp/Twig/tree/v3.14.0" }, "funding": [ { @@ -2589,7 +2668,7 @@ "type": "tidelift" } ], - "time": "2022-09-28T08:42:51+00:00" + "time": "2024-09-09T17:55:12+00:00" } ], "packages-dev": [], diff --git a/data/web/inc/lib/vendor/composer/autoload_classmap.php b/data/web/inc/lib/vendor/composer/autoload_classmap.php index 5490b88d8..8e4b7d313 100644 --- a/data/web/inc/lib/vendor/composer/autoload_classmap.php +++ b/data/web/inc/lib/vendor/composer/autoload_classmap.php @@ -7,8 +7,9 @@ $baseDir = dirname($vendorDir); 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', 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', diff --git a/data/web/inc/lib/vendor/composer/autoload_files.php b/data/web/inc/lib/vendor/composer/autoload_files.php index bac0d960a..c426e71df 100644 --- a/data/web/inc/lib/vendor/composer/autoload_files.php +++ b/data/web/inc/lib/vendor/composer/autoload_files.php @@ -14,8 +14,14 @@ return array( '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', 'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php', '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', + '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.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', + 'c7baa00073ee9c61edf148c51917cfb4' => $vendorDir . '/twig/twig/src/Resources/escaper.php', + 'f844ccf1d25df8663951193c3fc307c8' => $vendorDir . '/twig/twig/src/Resources/string_loader.php', ); diff --git a/data/web/inc/lib/vendor/composer/autoload_psr4.php b/data/web/inc/lib/vendor/composer/autoload_psr4.php index 9b7aef887..eca85b846 100644 --- a/data/web/inc/lib/vendor/composer/autoload_psr4.php +++ b/data/web/inc/lib/vendor/composer/autoload_psr4.php @@ -8,6 +8,7 @@ $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'), diff --git a/data/web/inc/lib/vendor/composer/autoload_static.php b/data/web/inc/lib/vendor/composer/autoload_static.php index 7564e9725..5375b0bea 100644 --- a/data/web/inc/lib/vendor/composer/autoload_static.php +++ b/data/web/inc/lib/vendor/composer/autoload_static.php @@ -15,20 +15,27 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', 'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php', '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.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', + 'c7baa00073ee9c61edf148c51917cfb4' => __DIR__ . '/..' . '/twig/twig/src/Resources/escaper.php', + 'f844ccf1d25df8663951193c3fc307c8' => __DIR__ . '/..' . '/twig/twig/src/Resources/string_loader.php', ); 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, @@ -37,11 +44,11 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b 'Symfony\\Component\\Translation\\' => 30, 'Stevenmaguire\\OAuth2\\Client\\' => 28, ), - 'R' => + 'R' => array ( 'RobThree\\Auth\\' => 14, ), - 'P' => + 'P' => array ( 'Psr\\SimpleCache\\' => 16, 'Psr\\Log\\' => 8, @@ -52,39 +59,39 @@ 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\\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, ), - 'C' => + 'C' => array ( 'Carbon\\Doctrine\\' => 16, 'Carbon\\' => 7, @@ -92,137 +99,141 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b ); public static $prefixDirsPsr4 = array ( - 'Twig\\' => + 'Twig\\' => array ( 0 => __DIR__ . '/..' . '/twig/twig/src', ), - 'Tightenco\\Collect\\' => + 'Tightenco\\Collect\\' => array ( 0 => __DIR__ . '/..' . '/tightenco/collect/src/Collect', ), - 'Symfony\\Polyfill\\Php80\\' => + 'Symfony\\Polyfill\\Php81\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php81', + ), + '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\\' => + '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\\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\\' => + 'Carbon\\Doctrine\\' => array ( 0 => __DIR__ . '/..' . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine', ), - 'Carbon\\' => + 'Carbon\\' => array ( 0 => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon', ), ); public static $prefixesPsr0 = array ( - 'O' => + 'O' => array ( - 'OAuth2' => + 'OAuth2' => array ( 0 => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src', ), @@ -231,8 +242,9 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b public static $classMap = array ( 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CURLStringFile' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', - 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', diff --git a/data/web/inc/lib/vendor/composer/installed.json b/data/web/inc/lib/vendor/composer/installed.json index a9a3966f5..26e8bc42b 100644 --- a/data/web/inc/lib/vendor/composer/installed.json +++ b/data/web/inc/lib/vendor/composer/installed.json @@ -1961,27 +1961,27 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.4.0", - "version_normalized": "3.4.0.0", + "version": "v3.5.0", + "version_normalized": "3.5.0.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { "php": ">=8.1" }, - "time": "2023-05-23T14:45:45+00:00", + "time": "2024-04-18T09:32:20+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -2011,7 +2011,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" }, "funding": [ { @@ -2280,6 +2280,85 @@ ], "install-path": "../symfony/polyfill-php80" }, + { + "name": "symfony/polyfill-php81", + "version": "v1.31.0", + "version_normalized": "1.31.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "time": "2024-09-09T11:45:10+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php81" + }, { "name": "symfony/translation", "version": "v6.4.3", @@ -2606,37 +2685,40 @@ }, { "name": "twig/twig", - "version": "v3.4.3", - "version_normalized": "3.4.3.0", + "version": "v3.14.0", + "version_normalized": "3.14.0.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58" + "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/c38fd6b0b7f370c198db91ffd02e23b517426b58", - "reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72", + "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3" + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php81": "^1.29" }, "require-dev": { - "psr/container": "^1.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, - "time": "2022-09-28T08:42:51+00:00", + "time": "2024-09-09T17:55:12+00:00", "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "installation-source": "dist", "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], "psr-4": { "Twig\\": "src/" } @@ -2669,7 +2751,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.4.3" + "source": "https://github.com/twigphp/Twig/tree/v3.14.0" }, "funding": [ { diff --git a/data/web/inc/lib/vendor/composer/installed.php b/data/web/inc/lib/vendor/composer/installed.php index fed449fa2..074ec9f80 100644 --- a/data/web/inc/lib/vendor/composer/installed.php +++ b/data/web/inc/lib/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => '__root__', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '40146839efb3754b2db4045f0111178ffd1883c5', + 'reference' => '220fdbb168792c07493db330d898b345cc902055', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -13,7 +13,7 @@ '__root__' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '40146839efb3754b2db4045f0111178ffd1883c5', + 'reference' => '220fdbb168792c07493db330d898b345cc902055', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -308,9 +308,9 @@ 'dev_requirement' => false, ), 'symfony/deprecation-contracts' => array( - 'pretty_version' => 'v3.4.0', - 'version' => '3.4.0.0', - 'reference' => '7c3aff79d10325257a001fcf92d991f24fc967cf', + 'pretty_version' => 'v3.5.0', + 'version' => '3.5.0.0', + 'reference' => '0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'aliases' => array(), @@ -343,6 +343,15 @@ 'aliases' => array(), 'dev_requirement' => false, ), + 'symfony/polyfill-php81' => array( + 'pretty_version' => 'v1.31.0', + 'version' => '1.31.0.0', + 'reference' => '4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php81', + 'aliases' => array(), + 'dev_requirement' => false, + ), 'symfony/translation' => array( 'pretty_version' => 'v6.4.3', 'version' => '6.4.3.0', @@ -386,9 +395,9 @@ 'dev_requirement' => false, ), 'twig/twig' => array( - 'pretty_version' => 'v3.4.3', - 'version' => '3.4.3.0', - 'reference' => 'c38fd6b0b7f370c198db91ffd02e23b517426b58', + 'pretty_version' => 'v3.14.0', + 'version' => '3.14.0.0', + 'reference' => '126b2c97818dbff0cdf3fbfc881aedb3d40aae72', 'type' => 'library', 'install_path' => __DIR__ . '/../twig/twig', 'aliases' => array(), diff --git a/data/web/inc/lib/vendor/symfony/deprecation-contracts/composer.json b/data/web/inc/lib/vendor/symfony/deprecation-contracts/composer.json index c6d02d874..ceb6c0796 100644 --- a/data/web/inc/lib/vendor/symfony/deprecation-contracts/composer.json +++ b/data/web/inc/lib/vendor/symfony/deprecation-contracts/composer.json @@ -25,7 +25,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php81/LICENSE b/data/web/inc/lib/vendor/symfony/polyfill-php81/LICENSE new file mode 100644 index 000000000..99c6bdf35 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php81/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2021-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php81/Php81.php b/data/web/inc/lib/vendor/symfony/polyfill-php81/Php81.php new file mode 100644 index 000000000..f0507b765 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php81/Php81.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php81; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class Php81 +{ + public static function array_is_list(array $array): bool + { + if ([] === $array || $array === array_values($array)) { + return true; + } + + $nextKey = -1; + + foreach ($array as $k => $v) { + if ($k !== ++$nextKey) { + return false; + } + } + + return true; + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php81/README.md b/data/web/inc/lib/vendor/symfony/polyfill-php81/README.md new file mode 100644 index 000000000..c07ef7820 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php81/README.md @@ -0,0 +1,18 @@ +Symfony Polyfill / Php81 +======================== + +This component provides features added to PHP 8.1 core: + +- [`array_is_list`](https://php.net/array_is_list) +- [`enum_exists`](https://php.net/enum-exists) +- [`MYSQLI_REFRESH_REPLICA`](https://php.net/mysqli.constants#constantmysqli-refresh-replica) constant +- [`ReturnTypeWillChange`](https://wiki.php.net/rfc/internal_method_return_types) +- [`CURLStringFile`](https://php.net/CURLStringFile) (but only if PHP >= 7.4 is used) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php b/data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php new file mode 100644 index 000000000..5ff93fcaf --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID >= 70400 && extension_loaded('curl')) { + /** + * @property string $data + */ + class CURLStringFile extends CURLFile + { + private $data; + + public function __construct(string $data, string $postname, string $mime = 'application/octet-stream') + { + $this->data = $data; + parent::__construct('data://application/octet-stream;base64,'.base64_encode($data), $mime, $postname); + } + + public function __set(string $name, $value): void + { + if ('data' !== $name) { + $this->$name = $value; + + return; + } + + if (is_object($value) ? !method_exists($value, '__toString') : !is_scalar($value)) { + throw new TypeError('Cannot assign '.gettype($value).' to property CURLStringFile::$data of type string'); + } + + $this->name = 'data://application/octet-stream;base64,'.base64_encode($value); + } + + public function __isset(string $name): bool + { + return isset($this->$name); + } + + public function &__get(string $name) + { + return $this->$name; + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php b/data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php new file mode 100644 index 000000000..cb7720a8d --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80100) { + #[Attribute(Attribute::TARGET_METHOD)] + final class ReturnTypeWillChange + { + public function __construct() + { + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php81/bootstrap.php b/data/web/inc/lib/vendor/symfony/polyfill-php81/bootstrap.php new file mode 100644 index 000000000..9f872e02f --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php81/bootstrap.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php81 as p; + +if (\PHP_VERSION_ID >= 80100) { + return; +} + +if (defined('MYSQLI_REFRESH_SLAVE') && !defined('MYSQLI_REFRESH_REPLICA')) { + define('MYSQLI_REFRESH_REPLICA', 64); +} + +if (!function_exists('array_is_list')) { + function array_is_list(array $array): bool { return p\Php81::array_is_list($array); } +} + +if (!function_exists('enum_exists')) { + function enum_exists(string $enum, bool $autoload = true): bool { return $autoload && class_exists($enum) && false; } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php81/composer.json b/data/web/inc/lib/vendor/symfony/polyfill-php81/composer.json new file mode 100644 index 000000000..28b6408ea --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php81/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/polyfill-php81", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php81\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/.editorconfig b/data/web/inc/lib/vendor/twig/twig/.editorconfig deleted file mode 100644 index 270f1d1b7..000000000 --- a/data/web/inc/lib/vendor/twig/twig/.editorconfig +++ /dev/null @@ -1,18 +0,0 @@ -; top-most EditorConfig file -root = true - -; Unix-style newlines -[*] -end_of_line = LF - -[*.php] -indent_style = space -indent_size = 4 - -[*.test] -indent_style = space -indent_size = 4 - -[*.rst] -indent_style = space -indent_size = 4 diff --git a/data/web/inc/lib/vendor/twig/twig/.gitattributes b/data/web/inc/lib/vendor/twig/twig/.gitattributes deleted file mode 100644 index 06bc36713..000000000 --- a/data/web/inc/lib/vendor/twig/twig/.gitattributes +++ /dev/null @@ -1,4 +0,0 @@ -/doc/ export-ignore -/extra/ export-ignore -/tests/ export-ignore -/phpunit.xml.dist export-ignore diff --git a/data/web/inc/lib/vendor/twig/twig/.github/workflows/ci.yml b/data/web/inc/lib/vendor/twig/twig/.github/workflows/ci.yml deleted file mode 100644 index 65c2c4077..000000000 --- a/data/web/inc/lib/vendor/twig/twig/.github/workflows/ci.yml +++ /dev/null @@ -1,149 +0,0 @@ -name: "CI" - -on: - pull_request: - push: - branches: - - '3.x' - -env: - SYMFONY_PHPUNIT_DISABLE_RESULT_CACHE: 1 - -permissions: - contents: read - -jobs: - tests: - name: "PHP ${{ matrix.php-version }}" - - runs-on: 'ubuntu-latest' - - continue-on-error: ${{ matrix.experimental }} - - strategy: - matrix: - php-version: - - '7.2.5' - - '7.3' - - '7.4' - - '8.0' - - '8.1' - experimental: [false] - - steps: - - name: "Checkout code" - uses: actions/checkout@v4 - - - name: "Install PHP with extensions" - uses: shivammathur/setup-php@v2 - with: - coverage: "none" - php-version: ${{ matrix.php-version }} - ini-values: memory_limit=-1 - - - name: "Add PHPUnit matcher" - run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - - run: composer install - - - name: "Install PHPUnit" - run: vendor/bin/simple-phpunit install - - - name: "PHPUnit version" - run: vendor/bin/simple-phpunit --version - - - name: "Run tests" - run: vendor/bin/simple-phpunit - - extension-tests: - needs: - - 'tests' - - name: "${{ matrix.extension }} with PHP ${{ matrix.php-version }}" - - runs-on: 'ubuntu-latest' - - continue-on-error: true - - strategy: - matrix: - php-version: - - '7.2.5' - - '7.3' - - '7.4' - - '8.0' - - '8.1' - extension: - - 'extra/cache-extra' - - 'extra/cssinliner-extra' - - 'extra/html-extra' - - 'extra/inky-extra' - - 'extra/intl-extra' - - 'extra/markdown-extra' - - 'extra/string-extra' - - 'extra/twig-extra-bundle' - experimental: [false] - - steps: - - name: "Checkout code" - uses: actions/checkout@v4 - - - name: "Install PHP with extensions" - uses: shivammathur/setup-php@v2 - with: - coverage: "none" - php-version: ${{ matrix.php-version }} - ini-values: memory_limit=-1 - - - name: "Add PHPUnit matcher" - run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - - run: composer install - - - name: "Install PHPUnit" - run: vendor/bin/simple-phpunit install - - - name: "PHPUnit version" - run: vendor/bin/simple-phpunit --version - - - name: "Composer install" - working-directory: ${{ matrix.extension}} - run: composer install - - - name: "Run tests" - working-directory: ${{ matrix.extension}} - run: ../../vendor/bin/simple-phpunit - -# -# Drupal does not support Twig 3 now! -# -# integration-tests: -# needs: -# - 'tests' -# -# name: "Integration tests with PHP ${{ matrix.php-version }}" -# -# runs-on: 'ubuntu-20.04' -# -# continue-on-error: true -# -# strategy: -# matrix: -# php-version: -# - '7.3' -# -# steps: -# - name: "Checkout code" -# uses: actions/checkout@v2 -# -# - name: "Install PHP with extensions" -# uses: shivammathur/setup-php@2 -# with: -# coverage: "none" -# extensions: "gd, pdo_sqlite" -# php-version: ${{ matrix.php-version }} -# ini-values: memory_limit=-1 -# tools: composer:v2 -# -# - run: bash ./tests/drupal_test.sh -# shell: "bash" diff --git a/data/web/inc/lib/vendor/twig/twig/.github/workflows/documentation.yml b/data/web/inc/lib/vendor/twig/twig/.github/workflows/documentation.yml deleted file mode 100644 index 5caa8a33f..000000000 --- a/data/web/inc/lib/vendor/twig/twig/.github/workflows/documentation.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: "Documentation" - -on: - pull_request: - push: - branches: - - '2.x' - - '3.x' - -permissions: - contents: read - -jobs: - build: - name: "Build" - - runs-on: ubuntu-latest - - steps: - - name: "Checkout code" - uses: actions/checkout@v4 - - - name: "Set-up PHP" - uses: shivammathur/setup-php@v2 - with: - php-version: 8.1 - coverage: none - tools: "composer:v2" - - - name: Get composer cache directory - id: composercache - working-directory: doc/_build - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Cache dependencies - uses: actions/cache@v3 - with: - path: ${{ steps.composercache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: "Install dependencies" - working-directory: doc/_build - run: composer install --prefer-dist --no-progress - - - name: "Build the docs" - working-directory: doc/_build - run: php build.php --disable-cache - - doctor-rst: - name: "DOCtor-RST" - - runs-on: ubuntu-latest - - steps: - - name: "Checkout code" - uses: actions/checkout@v4 - - - name: "Run DOCtor-RST" - uses: docker://oskarstark/doctor-rst - with: - args: --short - env: - DOCS_DIR: 'doc/' diff --git a/data/web/inc/lib/vendor/twig/twig/.gitignore b/data/web/inc/lib/vendor/twig/twig/.gitignore deleted file mode 100644 index b197246ba..000000000 --- a/data/web/inc/lib/vendor/twig/twig/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/doc/_build/vendor -/doc/_build/output -/composer.lock -/phpunit.xml -/vendor -.phpunit.result.cache diff --git a/data/web/inc/lib/vendor/twig/twig/.php-cs-fixer.dist.php b/data/web/inc/lib/vendor/twig/twig/.php-cs-fixer.dist.php deleted file mode 100644 index b07ac7fca..000000000 --- a/data/web/inc/lib/vendor/twig/twig/.php-cs-fixer.dist.php +++ /dev/null @@ -1,20 +0,0 @@ -setRules([ - '@Symfony' => true, - '@Symfony:risky' => true, - '@PHPUnit75Migration:risky' => true, - 'php_unit_dedicate_assert' => ['target' => '5.6'], - 'array_syntax' => ['syntax' => 'short'], - 'php_unit_fqcn_annotation' => true, - 'no_unreachable_default_argument_value' => false, - 'braces' => ['allow_single_line_closure' => true], - 'heredoc_to_nowdoc' => false, - 'ordered_imports' => true, - 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], - 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'all'], - ]) - ->setRiskyAllowed(true) - ->setFinder((new PhpCsFixer\Finder())->in(__DIR__)) -; diff --git a/data/web/inc/lib/vendor/twig/twig/CHANGELOG b/data/web/inc/lib/vendor/twig/twig/CHANGELOG index 379387644..44c79b133 100644 --- a/data/web/inc/lib/vendor/twig/twig/CHANGELOG +++ b/data/web/inc/lib/vendor/twig/twig/CHANGELOG @@ -1,3 +1,178 @@ +# 3.14.0 (2024-09-09) + + * Fix a security issue when an included sandboxed template has been loaded before without the sandbox context + * Add the possibility to reset globals via `Environment::resetGlobals()` + * Deprecate `Environment::mergeGlobals()` + +# 3.13.0 (2024-09-07) + + * Add the `types` tag (experimental) + * Deprecate the `Twig\Test\NodeTestCase::getTests()` data provider, override `provideTests()` instead. + * Mark `Twig\Test\NodeTestCase::getEnvironment()` as final, override `createEnvironment()` instead. + * Deprecate `Twig\Test\NodeTestCase::getVariableGetter()`, call `createVariableGetter()` instead. + * Deprecate `Twig\Test\NodeTestCase::getAttributeGetter()`, call `createAttributeGetter()` instead. + * Deprecate not overriding `Twig\Test\IntegrationTestCase::getFixturesDirectory()`, this method will be abstract in 4.0 + * Marked `Twig\Test\IntegrationTestCase::getTests()` and `getLegacyTests()` as final + +# 3.12.0 (2024-08-29) + + * Deprecate the fact that the `extends` and `use` tags are always allowed in a sandboxed template. + This behavior will change in 4.0 where these tags will need to be explicitly allowed like any other tag. + * Deprecate the "tag" constructor argument of the "Twig\Node\Node" class as the tag is now automatically set by the Parser when needed + * Fix precedence of two-word tests when the first word is a valid test + * Deprecate the `spaceless` filter + * Deprecate some internal methods from `Parser`: `getBlockStack()`, `hasBlock()`, `getBlock()`, `hasMacro()`, `hasTraits()`, `getParent()` + * Deprecate passing `null` to `Twig\Parser::setParent()` + * Update `Node::__toString()` to include the node tag if set + * Add support for integers in methods of `Twig\Node\Node` that take a Node name + * Deprecate not passing a `BodyNode` instance as the body of a `ModuleNode` or `MacroNode` constructor + * Deprecate returning "null" from "TokenParserInterface::parse()". + * Deprecate `OptimizerNodeVisitor::OPTIMIZE_TEXT_NODES` + * Fix performance regression when `use_yield` is `false` (which is the default) + * Improve compatibility when `use_yield` is `false` (as extensions still using `echo` will work as is) + * Accept colons (`:`) in addition to equals (`=`) to separate argument names and values in named arguments + * Add the `html_cva` function (in the HTML extra package) + * Add support for named arguments to the `block` and `attribute` functions + * Throw a SyntaxError exception at compile time when a Twig callable has not the minimum number of required arguments + * Add a `CallableArgumentsExtractor` class + * Deprecate passing a name to `FunctionExpression`, `FilterExpression`, and `TestExpression`; + pass a `TwigFunction`, `TwigFilter`, or `TestFilter` instead + * Deprecate all Twig callable attributes on `FunctionExpression`, `FilterExpression`, and `TestExpression` + * Deprecate the `filter` node of `FilterExpression` + * Add the notion of Twig callables (functions, filters, and tests) + * Bump minimum PHP version to 8.0 + * Fix integration tests when a test has more than one data/expect section and deprecations + * Add the `enum_cases` function + +# 3.11.0 (2024-08-08) + + * Deprecate `OptimizerNodeVisitor::OPTIMIZE_RAW_FILTER` + * Add `Twig\Cache\ChainCache` and `Twig\Cache\ReadOnlyFilesystemCache` + * Add the possibility to deprecate attributes and nodes on `Node` + * Add the possibility to add a package and a version to the `deprecated` tag + * Add the possibility to add a package for filter/function/test deprecations + * Mark `ConstantExpression` as being `@final` + * Add the `find` filter + * Fix optimizer mode validation in `OptimizerNodeVisitor` + * Add the possibility to yield from a generator in `PrintNode` + * Add the `shuffle` filter + * Add the `singular` and `plural` filters in `StringExtension` + * Deprecate the second argument of `Twig\Node\Expression\CallExpression::compileArguments()` + * Deprecate `Twig\ExpressionParser\parseHashExpression()` in favor of + `Twig\ExpressionParser::parseMappingExpression()` + * Deprecate `Twig\ExpressionParser\parseArrayExpression()` in favor of + `Twig\ExpressionParser::parseSequenceExpression()` + * Add `sequence` and `mapping` tests + * Deprecate `Twig\Node\Expression\NameExpression::isSimple()` and + `Twig\Node\Expression\NameExpression::isSpecial()` + +# 3.10.3 (2024-05-16) + + * Fix missing ; in generated code + +# 3.10.2 (2024-05-14) + + * Fix support for the deprecated escaper signature + +# 3.10.1 (2024-05-12) + + * Fix BC break on escaper extension + * Fix constant return type + +# 3.10.0 (2024-05-11) + + * Make `CoreExtension::formatDate`, `CoreExtension::convertDate`, and + `CoreExtension::formatNumber` part of the public API + * Add `needs_charset` option for filters and functions + * Extract the escaping logic from the `EscaperExtension` class to a new + `EscaperRuntime` class. + + The following methods from ``Twig\\Extension\\EscaperExtension`` are + deprecated: ``setEscaper()``, ``getEscapers()``, ``setSafeClasses``, + ``addSafeClasses()``. Use the same methods on the + ``Twig\\Runtime\\EscaperRuntime`` class instead. + * Fix capturing output from extensions that still use echo + * Fix a PHP warning in the Lexer on malformed templates + * Fix blocks not available under some circumstances + * Synchronize source context in templates when setting a Node on a Node + +# 3.9.3 (2024-04-18) + + * Add missing `twig_escape_filter_is_safe` deprecated function + * Fix yield usage with CaptureNode + * Add missing unwrap call when using a TemplateWrapper instance internally + * Ensure Lexer is initialized early on + +# 3.9.2 (2024-04-17) + + * Fix usage of display_end hook + +# 3.9.1 (2024-04-17) + + * Fix missing `$blocks` variable in `CaptureNode` + +# 3.9.0 (2024-04-16) + + * Add support for PHP 8.4 + * Deprecate AbstractNodeVisitor + * Deprecate passing Template to Environment::resolveTemplate(), Environment::load(), and Template::loadTemplate() + * Add a new "yield" mode for output generation; + Node implementations that use "echo" or "print" should use "yield" instead; + all Node implementations should be flagged with `#[YieldReady]` once they've been made ready for "yield"; + the "use_yield" Environment option can be turned on when all nodes have been made `#[YieldReady]`; + "yield" will be the only strategy supported in the next major version + * Add return type for Symfony 7 compatibility + * Fix premature loop exit in Security Policy lookup of allowed methods/properties + * Deprecate all internal extension functions in favor of methods on the extension classes + * Mark all extension functions as @internal + * Add SourcePolicyInterface to selectively enable the Sandbox based on a template's Source + * Throw a proper Twig exception when using cycle on an empty array + +# 3.8.0 (2023-11-21) + + * Catch errors thrown during template rendering + * Fix IntlExtension::formatDateTime use of date formatter prototype + * Fix premature loop exit in Security Policy lookup of allowed methods/properties + * Remove NumberFormatter::TYPE_CURRENCY (deprecated in PHP 8.3) + * Restore return type annotations + * Allow Symfony 7 packages to be installed + * Deprecate `twig_test_iterable` function. Use the native `is_iterable` instead. + +# 3.7.1 (2023-08-28) + + * Fix some phpdocs + +# 3.7.0 (2023-07-26) + + * Add support for the ...spread operator on arrays and hashes + +# 3.6.1 (2023-06-08) + + * Suppress some native return type deprecation messages + +# 3.6.0 (2023-05-03) + + * Allow psr/container 2.0 + * Add the new PHP 8.0 IntlDateFormatter::RELATIVE_* constants for date formatting + * Make the Lexer initialize itself lazily + +# 3.5.1 (2023-02-08) + + * Arrow functions passed to the "reduce" filter now accept the current key as a third argument + * Restores the leniency of the matches twig comparison + * Fix error messages in sandboxed mode for "has some" and "has every" + +# 3.5.0 (2022-12-27) + + * Make Twig\ExpressionParser non-internal + * Add "has some" and "has every" operators + * Add Compile::reset() + * Throw a better runtime error when the "matches" regexp is not valid + * Add "twig *_names" intl functions + * Fix optimizing closures callbacks + * Add a better exception when getting an undefined constant via `constant` + * Fix `if` nodes when outside of a block and with an empty body + # 3.4.3 (2022-09-28) * Fix a security issue on filesystem loader (possibility to load a template outside a configured directory) @@ -141,7 +316,7 @@ * removed Parser::isReservedMacroName() * removed SanboxedPrintNode * removed Node::setTemplateName() - * made classes maked as "@final" final + * made classes marked as "@final" final * removed InitRuntimeInterface, ExistsLoaderInterface, and SourceContextLoaderInterface * removed the "spaceless" tag * removed Twig\Environment::getBaseTemplateClass() and Twig\Environment::setBaseTemplateClass() diff --git a/data/web/inc/lib/vendor/twig/twig/LICENSE b/data/web/inc/lib/vendor/twig/twig/LICENSE index 8711927f6..fd8234e51 100644 --- a/data/web/inc/lib/vendor/twig/twig/LICENSE +++ b/data/web/inc/lib/vendor/twig/twig/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009-2022 by the Twig Team. +Copyright (c) 2009-present by the Twig Team. All rights reserved. diff --git a/data/web/inc/lib/vendor/twig/twig/README.rst b/data/web/inc/lib/vendor/twig/twig/README.rst index fbe7e9a9f..7bf8c673e 100644 --- a/data/web/inc/lib/vendor/twig/twig/README.rst +++ b/data/web/inc/lib/vendor/twig/twig/README.rst @@ -11,7 +11,7 @@ Sponsors .. raw:: html - + Blackfire.io diff --git a/data/web/inc/lib/vendor/twig/twig/composer.json b/data/web/inc/lib/vendor/twig/twig/composer.json index 33e46405c..e0c3e6c6c 100644 --- a/data/web/inc/lib/vendor/twig/twig/composer.json +++ b/data/web/inc/lib/vendor/twig/twig/composer.json @@ -24,15 +24,23 @@ } ], "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-ctype": "^1.8" + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php81": "^1.29" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0", - "psr/container": "^1.0" + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0", + "psr/container": "^1.0|^2.0" }, "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], "psr-4" : { "Twig\\" : "src/" } @@ -41,10 +49,5 @@ "psr-4" : { "Twig\\Tests\\" : "tests/" } - }, - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/AbstractTwigCallable.php b/data/web/inc/lib/vendor/twig/twig/src/AbstractTwigCallable.php new file mode 100644 index 000000000..f67184300 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/AbstractTwigCallable.php @@ -0,0 +1,136 @@ + + */ +abstract class AbstractTwigCallable implements TwigCallableInterface +{ + protected $options; + + private $name; + private $dynamicName; + private $callable; + private $arguments; + + public function __construct(string $name, $callable = null, array $options = []) + { + $this->name = $this->dynamicName = $name; + $this->callable = $callable; + $this->arguments = []; + $this->options = array_merge([ + 'needs_environment' => false, + 'needs_context' => false, + 'needs_charset' => false, + 'is_variadic' => false, + 'deprecated' => false, + 'deprecating_package' => '', + 'alternative' => null, + ], $options); + } + + public function __toString(): string + { + return \sprintf('%s(%s)', static::class, $this->name); + } + + public function getName(): string + { + return $this->name; + } + + public function getDynamicName(): string + { + return $this->dynamicName; + } + + public function getCallable() + { + return $this->callable; + } + + public function getNodeClass(): string + { + return $this->options['node_class']; + } + + public function needsCharset(): bool + { + return $this->options['needs_charset']; + } + + public function needsEnvironment(): bool + { + return $this->options['needs_environment']; + } + + public function needsContext(): bool + { + return $this->options['needs_context']; + } + + public function withDynamicArguments(string $name, string $dynamicName, array $arguments): self + { + $new = clone $this; + $new->name = $name; + $new->dynamicName = $dynamicName; + $new->arguments = $arguments; + + return $new; + } + + /** + * @deprecated since Twig 3.12, use withDynamicArguments() instead + */ + public function setArguments(array $arguments): void + { + trigger_deprecation('twig/twig', '3.12', 'The "%s::setArguments()" method is deprecated, use "%s::withDynamicArguments()" instead.', static::class, static::class); + + $this->arguments = $arguments; + } + + public function getArguments(): array + { + return $this->arguments; + } + + public function isVariadic(): bool + { + return $this->options['is_variadic']; + } + + public function isDeprecated(): bool + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatingPackage(): string + { + return $this->options['deprecating_package']; + } + + public function getDeprecatedVersion(): string + { + return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; + } + + public function getAlternative(): ?string + { + return $this->options['alternative']; + } + + public function getMinimalNumberOfRequiredArguments(): int + { + return ($this->options['needs_charset'] ? 1 : 0) + ($this->options['needs_environment'] ? 1 : 0) + ($this->options['needs_context'] ? 1 : 0) + \count($this->arguments); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Attribute/FirstClassTwigCallableReady.php b/data/web/inc/lib/vendor/twig/twig/src/Attribute/FirstClassTwigCallableReady.php new file mode 100644 index 000000000..ffd8cffc8 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Attribute/FirstClassTwigCallableReady.php @@ -0,0 +1,20 @@ + + */ +final class ChainCache implements CacheInterface +{ + /** + * @param iterable $caches The ordered list of caches used to store and fetch cached items + */ + public function __construct( + private iterable $caches, + ) { + } + + public function generateKey(string $name, string $className): string + { + return $className.'#'.$name; + } + + public function write(string $key, string $content): void + { + $splitKey = $this->splitKey($key); + + foreach ($this->caches as $cache) { + $cache->write($cache->generateKey(...$splitKey), $content); + } + } + + public function load(string $key): void + { + [$name, $className] = $this->splitKey($key); + + foreach ($this->caches as $cache) { + $cache->load($cache->generateKey($name, $className)); + + if (class_exists($className, false)) { + break; + } + } + } + + public function getTimestamp(string $key): int + { + $splitKey = $this->splitKey($key); + + foreach ($this->caches as $cache) { + if (0 < $timestamp = $cache->getTimestamp($cache->generateKey(...$splitKey))) { + return $timestamp; + } + } + + return 0; + } + + /** + * @return string[] + */ + private function splitKey(string $key): array + { + return array_reverse(explode('#', $key, 2)); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Cache/FilesystemCache.php b/data/web/inc/lib/vendor/twig/twig/src/Cache/FilesystemCache.php index e075563ae..2e79fac05 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Cache/FilesystemCache.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Cache/FilesystemCache.php @@ -50,11 +50,11 @@ class FilesystemCache implements CacheInterface if (false === @mkdir($dir, 0777, true)) { clearstatcache(true, $dir); if (!is_dir($dir)) { - throw new \RuntimeException(sprintf('Unable to create the cache directory (%s).', $dir)); + throw new \RuntimeException(\sprintf('Unable to create the cache directory (%s).', $dir)); } } } elseif (!is_writable($dir)) { - throw new \RuntimeException(sprintf('Unable to write in the cache directory (%s).', $dir)); + throw new \RuntimeException(\sprintf('Unable to write in the cache directory (%s).', $dir)); } $tmpFile = tempnam($dir, basename($key)); @@ -63,7 +63,7 @@ class FilesystemCache implements CacheInterface if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) { // Compile cached file into bytecode cache - if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) { + if (\function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) { @opcache_invalidate($key, true); } elseif (\function_exists('apc_compile_file')) { apc_compile_file($key); @@ -73,7 +73,7 @@ class FilesystemCache implements CacheInterface return; } - throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $key)); + throw new \RuntimeException(\sprintf('Failed to write cache file "%s".', $key)); } public function getTimestamp(string $key): int diff --git a/data/web/inc/lib/vendor/twig/twig/src/Cache/ReadOnlyFilesystemCache.php b/data/web/inc/lib/vendor/twig/twig/src/Cache/ReadOnlyFilesystemCache.php new file mode 100644 index 000000000..3ba6514c9 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Cache/ReadOnlyFilesystemCache.php @@ -0,0 +1,25 @@ + + */ +class ReadOnlyFilesystemCache extends FilesystemCache +{ + public function write(string $key, string $content): void + { + // Do nothing with the content, it's a read-only filesystem. + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Compiler.php b/data/web/inc/lib/vendor/twig/twig/src/Compiler.php index 95e1f183b..1a43aa7f6 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Compiler.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Compiler.php @@ -22,15 +22,16 @@ class Compiler private $lastLine; private $source; private $indentation; - private $env; private $debugInfo = []; private $sourceOffset; private $sourceLine; private $varNameSalt = 0; + private $didUseEcho = false; + private $didUseEchoStack = []; - public function __construct(Environment $env) - { - $this->env = $env; + public function __construct( + private Environment $env, + ) { } public function getEnvironment(): Environment @@ -46,7 +47,7 @@ class Compiler /** * @return $this */ - public function compile(Node $node, int $indentation = 0) + public function reset(int $indentation = 0) { $this->lastLine = null; $this->source = ''; @@ -57,23 +58,54 @@ class Compiler $this->indentation = $indentation; $this->varNameSalt = 0; - $node->compile($this); - return $this; } + /** + * @return $this + */ + public function compile(Node $node, int $indentation = 0) + { + $this->reset($indentation); + $this->didUseEchoStack[] = $this->didUseEcho; + + try { + $this->didUseEcho = false; + $node->compile($this); + + if ($this->didUseEcho) { + trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[YieldReady].', $this->didUseEcho, \get_class($node)); + } + + return $this; + } finally { + $this->didUseEcho = array_pop($this->didUseEchoStack); + } + } + /** * @return $this */ public function subcompile(Node $node, bool $raw = true) { - if (false === $raw) { + if (!$raw) { $this->source .= str_repeat(' ', $this->indentation * 4); } - $node->compile($this); + $this->didUseEchoStack[] = $this->didUseEcho; - return $this; + try { + $this->didUseEcho = false; + $node->compile($this); + + if ($this->didUseEcho) { + trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[YieldReady].', $this->didUseEcho, \get_class($node)); + } + + return $this; + } finally { + $this->didUseEcho = array_pop($this->didUseEchoStack); + } } /** @@ -83,6 +115,7 @@ class Compiler */ public function raw(string $string) { + $this->checkForEcho($string); $this->source .= $string; return $this; @@ -96,6 +129,7 @@ class Compiler public function write(...$strings) { foreach ($strings as $string) { + $this->checkForEcho($string); $this->source .= str_repeat(' ', $this->indentation * 4).$string; } @@ -109,7 +143,7 @@ class Compiler */ public function string(string $value) { - $this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\")); + $this->source .= \sprintf('"%s"', addcslashes($value, "\0\t\"\$\\")); return $this; } @@ -161,7 +195,7 @@ class Compiler public function addDebugInfo(Node $node) { if ($node->getTemplateLine() != $this->lastLine) { - $this->write(sprintf("// line %d\n", $node->getTemplateLine())); + $this->write(\sprintf("// line %d\n", $node->getTemplateLine())); $this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset); $this->sourceOffset = \strlen($this->source); @@ -209,6 +243,15 @@ class Compiler public function getVarName(): string { - return sprintf('__internal_compile_%d', $this->varNameSalt++); + return \sprintf('__internal_compile_%d', $this->varNameSalt++); + } + + private function checkForEcho(string $string): void + { + if ($this->didUseEcho) { + return; + } + + $this->didUseEcho = preg_match('/^\s*+(echo|print)\b/', $string, $m) ? $m[1] : false; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Environment.php b/data/web/inc/lib/vendor/twig/twig/src/Environment.php index 85aaab916..24e55e979 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Environment.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Environment.php @@ -22,12 +22,17 @@ use Twig\Extension\CoreExtension; use Twig\Extension\EscaperExtension; use Twig\Extension\ExtensionInterface; use Twig\Extension\OptimizerExtension; +use Twig\Extension\YieldNotReadyExtension; use Twig\Loader\ArrayLoader; use Twig\Loader\ChainLoader; use Twig\Loader\LoaderInterface; +use Twig\Node\Expression\Binary\AbstractBinary; +use Twig\Node\Expression\Unary\AbstractUnary; use Twig\Node\ModuleNode; use Twig\Node\Node; use Twig\NodeVisitor\NodeVisitorInterface; +use Twig\Runtime\EscaperRuntime; +use Twig\RuntimeLoader\FactoryRuntimeLoader; use Twig\RuntimeLoader\RuntimeLoaderInterface; use Twig\TokenParser\TokenParserInterface; @@ -38,11 +43,11 @@ use Twig\TokenParser\TokenParserInterface; */ class Environment { - public const VERSION = '3.4.3'; - public const VERSION_ID = 30403; + public const VERSION = '3.14.0'; + public const VERSION_ID = 31400; public const MAJOR_VERSION = 3; - public const MINOR_VERSION = 4; - public const RELEASE_VERSION = 3; + public const MINOR_VERSION = 14; + public const RELEASE_VERSION = 0; public const EXTRA_VERSION = ''; private $charset; @@ -53,16 +58,19 @@ class Environment private $lexer; private $parser; private $compiler; + /** @var array */ private $globals = []; private $resolvedGlobals; private $loadedTemplates; private $strictVariables; - private $templateClassPrefix = '__TwigTemplate_'; private $originalCache; private $extensionSet; private $runtimeLoaders = []; private $runtimes = []; private $optionsHash; + /** @var bool */ + private $useYield; + private $defaultRuntimeLoader; /** * Constructor. @@ -94,8 +102,12 @@ class Environment * * optimizations: A flag that indicates which optimizations to apply * (default to -1 which means that all optimizations are enabled; * set it to 0 to disable). + * + * * use_yield: true: forces templates to exclusively use "yield" instead of "echo" (all extensions must be yield ready) + * false (default): allows templates to use a mix of "yield" and "echo" calls to allow for a progressive migration + * Switch to "true" when possible as this will be the only supported mode in Twig 4.0 */ - public function __construct(LoaderInterface $loader, $options = []) + public function __construct(LoaderInterface $loader, array $options = []) { $this->setLoader($loader); @@ -107,20 +119,38 @@ class Environment 'cache' => false, 'auto_reload' => null, 'optimizations' => -1, + 'use_yield' => false, ], $options); + $this->useYield = (bool) $options['use_yield']; $this->debug = (bool) $options['debug']; $this->setCharset($options['charset'] ?? 'UTF-8'); $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; $this->strictVariables = (bool) $options['strict_variables']; $this->setCache($options['cache']); $this->extensionSet = new ExtensionSet(); + $this->defaultRuntimeLoader = new FactoryRuntimeLoader([ + EscaperRuntime::class => function () { return new EscaperRuntime($this->charset); }, + ]); $this->addExtension(new CoreExtension()); - $this->addExtension(new EscaperExtension($options['autoescape'])); + $escaperExt = new EscaperExtension($options['autoescape']); + $escaperExt->setEnvironment($this, false); + $this->addExtension($escaperExt); + if (\PHP_VERSION_ID >= 80000) { + $this->addExtension(new YieldNotReadyExtension($this->useYield)); + } $this->addExtension(new OptimizerExtension($options['optimizations'])); } + /** + * @internal + */ + public function useYield(): bool + { + return $this->useYield; + } + /** * Enables debugging mode. */ @@ -246,7 +276,6 @@ class Environment * * * The cache key for the given template; * * The currently enabled extensions; - * * Whether the Twig C extension is available or not; * * PHP version; * * Twig version; * * Options with what environment was created. @@ -256,11 +285,11 @@ class Environment * * @internal */ - public function getTemplateClass(string $name, int $index = null): string + public function getTemplateClass(string $name, ?int $index = null): string { $key = $this->getLoader()->getCacheKey($name).$this->optionsHash; - return $this->templateClassPrefix.hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $key).(null === $index ? '' : '___'.$index); + return '__TwigTemplate_'.hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $key).(null === $index ? '' : '___'.$index); } /** @@ -305,6 +334,11 @@ class Environment if ($name instanceof TemplateWrapper) { return $name; } + if ($name instanceof Template) { + trigger_deprecation('twig/twig', '3.9', 'Passing a "%s" instance to "%s" is deprecated.', self::class, __METHOD__); + + return $name; + } return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name)); } @@ -315,8 +349,8 @@ class Environment * This method is for internal use only and should never be called * directly. * - * @param string $name The template name - * @param int $index The index if it is an embedded template + * @param string $name The template name + * @param int|null $index The index if it is an embedded template * * @throws LoaderError When the template cannot be found * @throws RuntimeError When a previously generated cache is corrupted @@ -324,7 +358,7 @@ class Environment * * @internal */ - public function loadTemplate(string $cls, string $name, int $index = null): Template + public function loadTemplate(string $cls, string $name, ?int $index = null): Template { $mainCls = $cls; if (null !== $index) { @@ -342,7 +376,6 @@ class Environment $this->cache->load($key); } - $source = null; if (!class_exists($cls, false)) { $source = $this->getLoader()->getSourceContext($name); $content = $this->compileSource($source); @@ -359,7 +392,7 @@ class Environment } if (!class_exists($cls, false)) { - throw new RuntimeError(sprintf('Failed to load Twig template "%s", index "%s": cache might be corrupted.', $name, $index), -1, $source); + throw new RuntimeError(\sprintf('Failed to load Twig template "%s", index "%s": cache might be corrupted.', $name, $index), -1, $source); } } } @@ -374,19 +407,19 @@ class Environment * * This method should not be used as a generic way to load templates. * - * @param string $template The template source - * @param string $name An optional name of the template to be used in error messages + * @param string $template The template source + * @param string|null $name An optional name of the template to be used in error messages * * @throws LoaderError When the template cannot be found * @throws SyntaxError When an error occurred during compilation */ - public function createTemplate(string $template, string $name = null): TemplateWrapper + public function createTemplate(string $template, ?string $name = null): TemplateWrapper { $hash = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $template, false); if (null !== $name) { - $name = sprintf('%s (string template %s)', $name, $hash); + $name = \sprintf('%s (string template %s)', $name, $hash); } else { - $name = sprintf('__string_template__%s', $hash); + $name = \sprintf('__string_template__%s', $hash); } $loader = new ChainLoader([ @@ -419,10 +452,10 @@ class Environment /** * Tries to load a template consecutively from an array. * - * Similar to load() but it also accepts instances of \Twig\Template and - * \Twig\TemplateWrapper, and an array of templates where each is tried to be loaded. + * Similar to load() but it also accepts instances of \Twig\TemplateWrapper + * and an array of templates where each is tried to be loaded. * - * @param string|TemplateWrapper|array $names A template or an array of templates to try consecutively + * @param string|TemplateWrapper|array $names A template or an array of templates to try consecutively * * @throws LoaderError When none of the templates can be found * @throws SyntaxError When an error occurred during compilation @@ -436,7 +469,9 @@ class Environment $count = \count($names); foreach ($names as $name) { if ($name instanceof Template) { - return $name; + trigger_deprecation('twig/twig', '3.9', 'Passing a "%s" instance to "%s" is deprecated.', Template::class, __METHOD__); + + return new TemplateWrapper($this, $name); } if ($name instanceof TemplateWrapper) { return $name; @@ -449,7 +484,7 @@ class Environment return $this->load($name); } - throw new LoaderError(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); + throw new LoaderError(\sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); } public function setLexer(Lexer $lexer) @@ -518,7 +553,7 @@ class Environment $e->setSourceContext($source); throw $e; } catch (\Exception $e) { - throw new SyntaxError(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e); + throw new SyntaxError(\sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e); } } @@ -534,7 +569,7 @@ class Environment public function setCharset(string $charset) { - if ('UTF8' === $charset = null === $charset ? null : strtoupper($charset)) { + if ('UTF8' === $charset = strtoupper($charset ?: '')) { // iconv on Windows requires "UTF-8" instead of "UTF8" $charset = 'UTF-8'; } @@ -592,7 +627,11 @@ class Environment } } - throw new RuntimeError(sprintf('Unable to load the "%s" runtime.', $class)); + if (null !== $runtime = $this->defaultRuntimeLoader->load($class)) { + return $this->runtimes[$class] = $runtime; + } + + throw new RuntimeError(\sprintf('Unable to load the "%s" runtime.', $class)); } public function addExtension(ExtensionInterface $extension) @@ -763,7 +802,7 @@ class Environment public function addGlobal(string $name, $value) { if ($this->extensionSet->isInitialized() && !\array_key_exists($name, $this->getGlobals())) { - throw new \LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); + throw new \LogicException(\sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); } if (null !== $this->resolvedGlobals) { @@ -775,6 +814,8 @@ class Environment /** * @internal + * + * @return array */ public function getGlobals(): array { @@ -789,21 +830,26 @@ class Environment return array_merge($this->extensionSet->getGlobals(), $this->globals); } + public function resetGlobals(): void + { + $this->resolvedGlobals = null; + $this->extensionSet->resetGlobals(); + } + + /** + * @deprecated since Twig 3.14 + */ public function mergeGlobals(array $context): array { - // we don't use array_merge as the context being generally - // bigger than globals, this code is faster. - foreach ($this->getGlobals() as $key => $value) { - if (!\array_key_exists($key, $context)) { - $context[$key] = $value; - } - } + trigger_deprecation('twig/twig', '3.14', 'The "%s" method is deprecated.', __METHOD__); - return $context; + return $context + $this->getGlobals(); } /** * @internal + * + * @return array}> */ public function getUnaryOperators(): array { @@ -812,6 +858,8 @@ class Environment /** * @internal + * + * @return array, associativity: ExpressionParser::OPERATOR_*}> */ public function getBinaryOperators(): array { @@ -827,6 +875,7 @@ class Environment self::VERSION, (int) $this->debug, (int) $this->strictVariables, + $this->useYield ? '1' : '0', ]); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Error/Error.php b/data/web/inc/lib/vendor/twig/twig/src/Error/Error.php index a68be65f2..61c309fa1 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Error/Error.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Error/Error.php @@ -53,7 +53,7 @@ class Error extends \Exception * @param int $lineno The template line where the error occurred * @param Source|null $source The source context where the error occurred */ - public function __construct(string $message, int $lineno = -1, Source $source = null, \Exception $previous = null) + public function __construct(string $message, int $lineno = -1, ?Source $source = null, ?\Throwable $previous = null) { parent::__construct('', 0, $previous); @@ -93,7 +93,7 @@ class Error extends \Exception return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null; } - public function setSourceContext(Source $source = null): void + public function setSourceContext(?Source $source = null): void { if (null === $source) { $this->sourceCode = $this->name = $this->sourcePath = null; @@ -130,28 +130,28 @@ class Error extends \Exception } $dot = false; - if ('.' === substr($this->message, -1)) { + if (str_ends_with($this->message, '.')) { $this->message = substr($this->message, 0, -1); $dot = true; } $questionMark = false; - if ('?' === substr($this->message, -1)) { + if (str_ends_with($this->message, '?')) { $this->message = substr($this->message, 0, -1); $questionMark = true; } if ($this->name) { - if (\is_string($this->name) || (\is_object($this->name) && method_exists($this->name, '__toString'))) { - $name = sprintf('"%s"', $this->name); + if (\is_string($this->name) || $this->name instanceof \Stringable) { + $name = \sprintf('"%s"', $this->name); } else { $name = json_encode($this->name); } - $this->message .= sprintf(' in %s', $name); + $this->message .= \sprintf(' in %s', $name); } if ($this->lineno && $this->lineno >= 0) { - $this->message .= sprintf(' at line %d', $this->lineno); + $this->message .= \sprintf(' at line %d', $this->lineno); } if ($dot) { @@ -172,7 +172,7 @@ class Error extends \Exception foreach ($backtrace as $trace) { if (isset($trace['object']) && $trace['object'] instanceof Template) { $currentClass = \get_class($trace['object']); - $isEmbedContainer = null === $templateClass ? false : 0 === strpos($templateClass, $currentClass); + $isEmbedContainer = null === $templateClass ? false : str_starts_with($templateClass, $currentClass); if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) { $template = $trace['object']; $templateClass = \get_class($trace['object']); diff --git a/data/web/inc/lib/vendor/twig/twig/src/Error/SyntaxError.php b/data/web/inc/lib/vendor/twig/twig/src/Error/SyntaxError.php index 726b3309e..841b653f5 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Error/SyntaxError.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Error/SyntaxError.php @@ -30,7 +30,7 @@ class SyntaxError extends Error $alternatives = []; foreach ($items as $item) { $lev = levenshtein($name, $item); - if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) { + if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) { $alternatives[$item] = $lev; } } @@ -41,6 +41,6 @@ class SyntaxError extends Error asort($alternatives); - $this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', array_keys($alternatives)))); + $this->appendMessage(\sprintf(' Did you mean "%s"?', implode('", "', array_keys($alternatives)))); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser.php index 70b6eb05c..dc4a6015d 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser.php @@ -12,20 +12,21 @@ namespace Twig; +use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Error\SyntaxError; use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ArrowFunctionExpression; use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Expression\Binary\AbstractBinary; use Twig\Node\Expression\Binary\ConcatBinary; -use Twig\Node\Expression\BlockReferenceExpression; use Twig\Node\Expression\ConditionalExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\GetAttrExpression; use Twig\Node\Expression\MethodCallExpression; use Twig\Node\Expression\NameExpression; -use Twig\Node\Expression\ParentExpression; use Twig\Node\Expression\TestExpression; +use Twig\Node\Expression\Unary\AbstractUnary; use Twig\Node\Expression\Unary\NegUnary; use Twig\Node\Expression\Unary\NotUnary; use Twig\Node\Expression\Unary\PosUnary; @@ -40,23 +41,22 @@ use Twig\Node\Node; * @see https://en.wikipedia.org/wiki/Operator-precedence_parser * * @author Fabien Potencier - * - * @internal */ class ExpressionParser { public const OPERATOR_LEFT = 1; public const OPERATOR_RIGHT = 2; - private $parser; - private $env; + /** @var array}> */ private $unaryOperators; + /** @var array, associativity: self::OPERATOR_*}> */ private $binaryOperators; + private $readyNodes = []; - public function __construct(Parser $parser, Environment $env) - { - $this->parser = $parser; - $this->env = $env; + public function __construct( + private Parser $parser, + private Environment $env, + ) { $this->unaryOperators = $env->getUnaryOperators(); $this->binaryOperators = $env->getBinaryOperators(); } @@ -80,7 +80,7 @@ class ExpressionParser } elseif (isset($op['callable'])) { $expr = $op['callable']($this->parser, $expr); } else { - $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); + $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence'], true); $class = $op['class']; $expr = new $class($expr, $expr1, $token->getLine()); } @@ -103,52 +103,52 @@ class ExpressionParser $stream = $this->parser->getStream(); // short array syntax (one argument, no parentheses)? - if ($stream->look(1)->test(/* Token::ARROW_TYPE */ 12)) { + if ($stream->look(1)->test(Token::ARROW_TYPE)) { $line = $stream->getCurrent()->getLine(); - $token = $stream->expect(/* Token::NAME_TYPE */ 5); + $token = $stream->expect(Token::NAME_TYPE); $names = [new AssignNameExpression($token->getValue(), $token->getLine())]; - $stream->expect(/* Token::ARROW_TYPE */ 12); + $stream->expect(Token::ARROW_TYPE); return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line); } // first, determine if we are parsing an arrow function by finding => (long form) $i = 0; - if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, '(')) { return null; } ++$i; while (true) { // variable name ++$i; - if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ',')) { break; } ++$i; } - if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) { + if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ')')) { return null; } ++$i; - if (!$stream->look($i)->test(/* Token::ARROW_TYPE */ 12)) { + if (!$stream->look($i)->test(Token::ARROW_TYPE)) { return null; } // yes, let's parse it properly - $token = $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '('); + $token = $stream->expect(Token::PUNCTUATION_TYPE, '('); $line = $token->getLine(); $names = []; while (true) { - $token = $stream->expect(/* Token::NAME_TYPE */ 5); + $token = $stream->expect(Token::NAME_TYPE); $names[] = new AssignNameExpression($token->getValue(), $token->getLine()); - if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { break; } } - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')'); - $stream->expect(/* Token::ARROW_TYPE */ 12); + $stream->expect(Token::PUNCTUATION_TYPE, ')'); + $stream->expect(Token::ARROW_TYPE); return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line); } @@ -164,10 +164,10 @@ class ExpressionParser $class = $operator['class']; return $this->parsePostfixExpression(new $class($expr, $token->getLine())); - } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + } elseif ($token->test(Token::PUNCTUATION_TYPE, '(')) { $this->parser->getStream()->next(); $expr = $this->parseExpression(); - $this->parser->getStream()->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'An opened parenthesis is not properly closed'); + $this->parser->getStream()->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); return $this->parsePostfixExpression($expr); } @@ -177,15 +177,18 @@ class ExpressionParser private function parseConditionalExpression($expr): AbstractExpression { - while ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, '?')) { - if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) { + while ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, '?')) { + if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) { $expr2 = $this->parseExpression(); - if ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) { + if ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) { + // Ternary operator (expr ? expr2 : expr3) $expr3 = $this->parseExpression(); } else { + // Ternary without else (expr ? expr2) $expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine()); } } else { + // Ternary without then (expr ?: expr3) $expr2 = $expr; $expr3 = $this->parseExpression(); } @@ -198,19 +201,19 @@ class ExpressionParser private function isUnary(Token $token): bool { - return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->unaryOperators[$token->getValue()]); + return $token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]); } private function isBinary(Token $token): bool { - return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->binaryOperators[$token->getValue()]); + return $token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]); } public function parsePrimaryExpression() { $token = $this->parser->getCurrentToken(); switch ($token->getType()) { - case /* Token::NAME_TYPE */ 5: + case Token::NAME_TYPE: $this->parser->getStream()->next(); switch ($token->getValue()) { case 'true': @@ -239,17 +242,17 @@ class ExpressionParser } break; - case /* Token::NUMBER_TYPE */ 6: + case Token::NUMBER_TYPE: $this->parser->getStream()->next(); $node = new ConstantExpression($token->getValue(), $token->getLine()); break; - case /* Token::STRING_TYPE */ 7: - case /* Token::INTERPOLATION_START_TYPE */ 10: + case Token::STRING_TYPE: + case Token::INTERPOLATION_START_TYPE: $node = $this->parseStringExpression(); break; - case /* Token::OPERATOR_TYPE */ 8: + case Token::OPERATOR_TYPE: if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) { // in this context, string operators are variable names $this->parser->getStream()->next(); @@ -260,7 +263,7 @@ class ExpressionParser if (isset($this->unaryOperators[$token->getValue()])) { $class = $this->unaryOperators[$token->getValue()]['class']; if (!\in_array($class, [NegUnary::class, PosUnary::class])) { - throw new SyntaxError(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + throw new SyntaxError(\sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); } $this->parser->getStream()->next(); @@ -272,14 +275,14 @@ class ExpressionParser // no break default: - if ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '[')) { - $node = $this->parseArrayExpression(); - } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '{')) { - $node = $this->parseHashExpression(); - } elseif ($token->test(/* Token::OPERATOR_TYPE */ 8, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) { - throw new SyntaxError(sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + if ($token->test(Token::PUNCTUATION_TYPE, '[')) { + $node = $this->parseSequenceExpression(); + } elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) { + $node = $this->parseMappingExpression(); + } elseif ($token->test(Token::OPERATOR_TYPE, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) { + throw new SyntaxError(\sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); } else { - throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + throw new SyntaxError(\sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); } } @@ -294,12 +297,12 @@ class ExpressionParser // a string cannot be followed by another string in a single expression $nextCanBeString = true; while (true) { - if ($nextCanBeString && $token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) { + if ($nextCanBeString && $token = $stream->nextIf(Token::STRING_TYPE)) { $nodes[] = new ConstantExpression($token->getValue(), $token->getLine()); $nextCanBeString = false; - } elseif ($stream->nextIf(/* Token::INTERPOLATION_START_TYPE */ 10)) { + } elseif ($stream->nextIf(Token::INTERPOLATION_START_TYPE)) { $nodes[] = $this->parseExpression(); - $stream->expect(/* Token::INTERPOLATION_END_TYPE */ 11); + $stream->expect(Token::INTERPOLATION_END_TYPE); $nextCanBeString = true; } else { break; @@ -314,56 +317,91 @@ class ExpressionParser return $expr; } + /** + * @deprecated since 3.11, use parseSequenceExpression() instead + */ public function parseArrayExpression() + { + trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseSequenceExpression()" instead.', __METHOD__); + + return $this->parseSequenceExpression(); + } + + public function parseSequenceExpression() { $stream = $this->parser->getStream(); - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '[', 'An array element was expected'); + $stream->expect(Token::PUNCTUATION_TYPE, '[', 'A sequence element was expected'); $node = new ArrayExpression([], $stream->getCurrent()->getLine()); $first = true; - while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { + while (!$stream->test(Token::PUNCTUATION_TYPE, ']')) { if (!$first) { - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'An array element must be followed by a comma'); + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A sequence element must be followed by a comma'); // trailing ,? - if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { + if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { break; } } $first = false; - $node->addElement($this->parseExpression()); + if ($stream->test(Token::SPREAD_TYPE)) { + $stream->next(); + $expr = $this->parseExpression(); + $expr->setAttribute('spread', true); + $node->addElement($expr); + } else { + $node->addElement($this->parseExpression()); + } } - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened array is not properly closed'); + $stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened sequence is not properly closed'); return $node; } + /** + * @deprecated since 3.11, use parseMappingExpression() instead + */ public function parseHashExpression() + { + trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseMappingExpression()" instead.', __METHOD__); + + return $this->parseMappingExpression(); + } + + public function parseMappingExpression() { $stream = $this->parser->getStream(); - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '{', 'A hash element was expected'); + $stream->expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected'); $node = new ArrayExpression([], $stream->getCurrent()->getLine()); $first = true; - while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) { + while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) { if (!$first) { - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A hash value must be followed by a comma'); + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A mapping value must be followed by a comma'); // trailing ,? - if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) { + if ($stream->test(Token::PUNCTUATION_TYPE, '}')) { break; } } $first = false; - // a hash key can be: + if ($stream->test(Token::SPREAD_TYPE)) { + $stream->next(); + $value = $this->parseExpression(); + $value->setAttribute('spread', true); + $node->addElement($value); + continue; + } + + // a mapping key can be: // // * a number -- 12 // * a string -- 'a' // * a name, which is equivalent to a string -- a // * an expression, which must be enclosed in parentheses -- (1 + 2) - if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) { + if ($token = $stream->nextIf(Token::NAME_TYPE)) { $key = new ConstantExpression($token->getValue(), $token->getLine()); // {a} is a shortcut for {a:a} @@ -372,22 +410,22 @@ class ExpressionParser $node->addElement($value, $key); continue; } - } elseif (($token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) || $token = $stream->nextIf(/* Token::NUMBER_TYPE */ 6)) { + } elseif (($token = $stream->nextIf(Token::STRING_TYPE)) || $token = $stream->nextIf(Token::NUMBER_TYPE)) { $key = new ConstantExpression($token->getValue(), $token->getLine()); - } elseif ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + } elseif ($stream->test(Token::PUNCTUATION_TYPE, '(')) { $key = $this->parseExpression(); } else { $current = $stream->getCurrent(); - throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext()); + throw new SyntaxError(\sprintf('A mapping key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext()); } - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A hash key must be followed by a colon (:)'); + $stream->expect(Token::PUNCTUATION_TYPE, ':', 'A mapping key must be followed by a colon (:)'); $value = $this->parseExpression(); $node->addElement($value, $key); } - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened hash is not properly closed'); + $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed'); return $node; } @@ -396,7 +434,7 @@ class ExpressionParser { while (true) { $token = $this->parser->getCurrentToken(); - if (/* Token::PUNCTUATION_TYPE */ 9 == $token->getType()) { + if (Token::PUNCTUATION_TYPE == $token->getType()) { if ('.' == $token->getValue() || '[' == $token->getValue()) { $node = $this->parseSubscriptExpression($node); } elseif ('|' == $token->getValue()) { @@ -414,50 +452,37 @@ class ExpressionParser public function getFunctionNode($name, $line) { - switch ($name) { - case 'parent': - $this->parseArguments(); - if (!\count($this->parser->getBlockStack())) { - throw new SyntaxError('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext()); - } + if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) { + $arguments = new ArrayExpression([], $line); + foreach ($this->parseArguments() as $n) { + $arguments->addElement($n); + } - if (!$this->parser->getParent() && !$this->parser->hasTraits()) { - throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext()); - } + $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line); + $node->setAttribute('safe', true); - return new ParentExpression($this->parser->peekBlockStack(), $line); - case 'block': - $args = $this->parseArguments(); - if (\count($args) < 1) { - throw new SyntaxError('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext()); - } - - return new BlockReferenceExpression($args->getNode(0), \count($args) > 1 ? $args->getNode(1) : null, $line); - case 'attribute': - $args = $this->parseArguments(); - if (\count($args) < 2) { - throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext()); - } - - return new GetAttrExpression($args->getNode(0), $args->getNode(1), \count($args) > 2 ? $args->getNode(2) : null, Template::ANY_CALL, $line); - default: - if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) { - $arguments = new ArrayExpression([], $line); - foreach ($this->parseArguments() as $n) { - $arguments->addElement($n); - } - - $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line); - $node->setAttribute('safe', true); - - return $node; - } - - $args = $this->parseArguments(true); - $class = $this->getFunctionNodeClass($name, $line); - - return new $class($name, $args, $line); + return $node; } + + $args = $this->parseArguments(true); + $function = $this->getFunction($name, $line); + + if ($function->getParserCallable()) { + $fakeNode = new Node(lineno: $line); + $fakeNode->setSourceContext($this->parser->getStream()->getSourceContext()); + + return ($function->getParserCallable())($this->parser, $fakeNode, $args, $line); + } + + if (!isset($this->readyNodes[$class = $function->getNodeClass()])) { + $this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class); + } + + if (!$ready = $this->readyNodes[$class]) { + trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigFunction" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class); + } + + return new $class($ready ? $function : $function->getName(), $args, $line); } public function parseSubscriptExpression($node) @@ -470,29 +495,25 @@ class ExpressionParser if ('.' == $token->getValue()) { $token = $stream->next(); if ( - /* Token::NAME_TYPE */ 5 == $token->getType() + Token::NAME_TYPE == $token->getType() || - /* Token::NUMBER_TYPE */ 6 == $token->getType() + Token::NUMBER_TYPE == $token->getType() || - (/* Token::OPERATOR_TYPE */ 8 == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue())) + (Token::OPERATOR_TYPE == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue())) ) { $arg = new ConstantExpression($token->getValue(), $lineno); - if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { $type = Template::METHOD_CALL; foreach ($this->parseArguments() as $n) { $arguments->addElement($n); } } } else { - throw new SyntaxError(sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext()); + throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext()); } if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { - if (!$arg instanceof ConstantExpression) { - throw new SyntaxError(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext()); - } - $name = $arg->getAttribute('value'); $node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno); @@ -505,34 +526,34 @@ class ExpressionParser // slice? $slice = false; - if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ':')) { + if ($stream->test(Token::PUNCTUATION_TYPE, ':')) { $slice = true; $arg = new ConstantExpression(0, $token->getLine()); } else { $arg = $this->parseExpression(); } - if ($stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) { + if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) { $slice = true; } if ($slice) { - if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { + if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { $length = new ConstantExpression(null, $token->getLine()); } else { $length = $this->parseExpression(); } - $class = $this->getFilterNodeClass('slice', $token->getLine()); + $filter = $this->getFilter('slice', $token->getLine()); $arguments = new Node([$arg, $length]); - $filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine()); + $filter = new ($filter->getNodeClass())($node, $filter, $arguments, $token->getLine()); - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']'); + $stream->expect(Token::PUNCTUATION_TYPE, ']'); return $filter; } - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']'); + $stream->expect(Token::PUNCTUATION_TYPE, ']'); } return new GetAttrExpression($node, $arg, $arguments, $type, $lineno); @@ -545,23 +566,35 @@ class ExpressionParser return $this->parseFilterExpressionRaw($node); } - public function parseFilterExpressionRaw($node, $tag = null) + public function parseFilterExpressionRaw($node) { - while (true) { - $token = $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5); + if (\func_num_args() > 1) { + trigger_deprecation('twig/twig', '3.12', 'Passing a second argument to "%s()" is deprecated.', __METHOD__); + } - $name = new ConstantExpression($token->getValue(), $token->getLine()); - if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + while (true) { + $token = $this->parser->getStream()->expect(Token::NAME_TYPE); + + if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '(')) { $arguments = new Node(); } else { $arguments = $this->parseArguments(true, false, true); } - $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine()); + $filter = $this->getFilter($token->getValue(), $token->getLine()); - $node = new $class($node, $name, $arguments, $token->getLine(), $tag); + $ready = true; + if (!isset($this->readyNodes[$class = $filter->getNodeClass()])) { + $this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class); + } - if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '|')) { + if (!$ready = $this->readyNodes[$class]) { + trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigFilter" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class); + } + + $node = new $class($node, $ready ? $filter : new ConstantExpression($filter->getName(), $token->getLine()), $arguments, $token->getLine()); + + if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '|')) { break; } @@ -575,7 +608,7 @@ class ExpressionParser * Parses arguments. * * @param bool $namedArguments Whether to allow named arguments or not - * @param bool $definition Whether we are parsing arguments for a function definition + * @param bool $definition Whether we are parsing arguments for a function (or macro) definition * * @return Node * @@ -586,28 +619,28 @@ class ExpressionParser $args = []; $stream = $this->parser->getStream(); - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(', 'A list of arguments must begin with an opening parenthesis'); - while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) { + $stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); + while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) { if (!empty($args)) { - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'Arguments must be separated by a comma'); + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); // if the comma above was a trailing comma, early exit the argument parse loop - if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) { + if ($stream->test(Token::PUNCTUATION_TYPE, ')')) { break; } } if ($definition) { - $token = $stream->expect(/* Token::NAME_TYPE */ 5, null, 'An argument must be a name'); + $token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name'); $value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine()); } else { $value = $this->parseExpression(0, $allowArrow); } $name = null; - if ($namedArguments && $token = $stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) { + if ($namedArguments && (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || ($token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':')))) { if (!$value instanceof NameExpression) { - throw new SyntaxError(sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext()); + throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext()); } $name = $value->getAttribute('name'); @@ -615,7 +648,7 @@ class ExpressionParser $value = $this->parsePrimaryExpression(); if (!$this->checkConstantExpression($value)) { - throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, or an array).', $token->getLine(), $stream->getSourceContext()); + throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).', $token->getLine(), $stream->getSourceContext()); } } else { $value = $this->parseExpression(0, $allowArrow); @@ -626,6 +659,7 @@ class ExpressionParser if (null === $name) { $name = $value->getAttribute('name'); $value = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine()); + $value->setAttribute('is_implicit', true); } $args[$name] = $value; } else { @@ -636,7 +670,7 @@ class ExpressionParser } } } - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'A list of arguments must be closed by a parenthesis'); + $stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); return new Node($args); } @@ -647,19 +681,19 @@ class ExpressionParser $targets = []; while (true) { $token = $this->parser->getCurrentToken(); - if ($stream->test(/* Token::OPERATOR_TYPE */ 8) && preg_match(Lexer::REGEX_NAME, $token->getValue())) { + if ($stream->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue())) { // in this context, string operators are variable names $this->parser->getStream()->next(); } else { - $stream->expect(/* Token::NAME_TYPE */ 5, null, 'Only variables can be assigned to'); + $stream->expect(Token::NAME_TYPE, null, 'Only variables can be assigned to'); } $value = $token->getValue(); if (\in_array(strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), ['true', 'false', 'none', 'null'])) { - throw new SyntaxError(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()); + throw new SyntaxError(\sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()); } $targets[] = new AssignNameExpression($value, $token->getLine()); - if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { break; } } @@ -672,7 +706,7 @@ class ExpressionParser $targets = []; while (true) { $targets[] = $this->parseExpression(); - if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) { break; } } @@ -688,121 +722,115 @@ class ExpressionParser private function parseTestExpression(Node $node): TestExpression { $stream = $this->parser->getStream(); - list($name, $test) = $this->getTest($node->getTemplateLine()); + $test = $this->getTest($node->getTemplateLine()); - $class = $this->getTestNodeClass($test); $arguments = null; - if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { $arguments = $this->parseArguments(true); } elseif ($test->hasOneMandatoryArgument()) { $arguments = new Node([0 => $this->parsePrimaryExpression()]); } - if ('defined' === $name && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) { + if ('defined' === $test->getName() && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) { $node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine()); $node->setAttribute('safe', true); } - return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine()); - } - - private function getTest(int $line): array - { - $stream = $this->parser->getStream(); - $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); - - if ($test = $this->env->getTest($name)) { - return [$name, $test]; + $ready = $test instanceof TwigTest; + if (!isset($this->readyNodes[$class = $test->getNodeClass()])) { + $this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class); } - if ($stream->test(/* Token::NAME_TYPE */ 5)) { + if (!$ready = $this->readyNodes[$class]) { + trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigTest" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class); + } + + return new $class($node, $ready ? $test : $test->getName(), $arguments, $this->parser->getCurrentToken()->getLine()); + } + + private function getTest(int $line): TwigTest + { + $stream = $this->parser->getStream(); + $name = $stream->expect(Token::NAME_TYPE)->getValue(); + + if ($stream->test(Token::NAME_TYPE)) { // try 2-words tests $name = $name.' '.$this->parser->getCurrentToken()->getValue(); if ($test = $this->env->getTest($name)) { $stream->next(); - - return [$name, $test]; } + } else { + $test = $this->env->getTest($name); } - $e = new SyntaxError(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()); - $e->addSuggestions($name, array_keys($this->env->getTests())); + if (!$test) { + $e = new SyntaxError(\sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getTests())); - throw $e; - } + throw $e; + } - private function getTestNodeClass(TwigTest $test): string - { if ($test->isDeprecated()) { $stream = $this->parser->getStream(); - $message = sprintf('Twig Test "%s" is deprecated', $test->getName()); + $message = \sprintf('Twig Test "%s" is deprecated', $test->getName()); - if ($test->getDeprecatedVersion()) { - $message .= sprintf(' since version %s', $test->getDeprecatedVersion()); - } if ($test->getAlternative()) { - $message .= sprintf('. Use "%s" instead', $test->getAlternative()); + $message .= \sprintf('. Use "%s" instead', $test->getAlternative()); } $src = $stream->getSourceContext(); - $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine()); + $message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine()); - @trigger_error($message, \E_USER_DEPRECATED); + trigger_deprecation($test->getDeprecatingPackage(), $test->getDeprecatedVersion(), $message); } - return $test->getNodeClass(); + return $test; } - private function getFunctionNodeClass(string $name, int $line): string + private function getFunction(string $name, int $line): TwigFunction { if (!$function = $this->env->getFunction($name)) { - $e = new SyntaxError(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()); + $e = new SyntaxError(\sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()); $e->addSuggestions($name, array_keys($this->env->getFunctions())); throw $e; } if ($function->isDeprecated()) { - $message = sprintf('Twig Function "%s" is deprecated', $function->getName()); - if ($function->getDeprecatedVersion()) { - $message .= sprintf(' since version %s', $function->getDeprecatedVersion()); - } + $message = \sprintf('Twig Function "%s" is deprecated', $function->getName()); if ($function->getAlternative()) { - $message .= sprintf('. Use "%s" instead', $function->getAlternative()); + $message .= \sprintf('. Use "%s" instead', $function->getAlternative()); } $src = $this->parser->getStream()->getSourceContext(); - $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); + $message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); - @trigger_error($message, \E_USER_DEPRECATED); + trigger_deprecation($function->getDeprecatingPackage(), $function->getDeprecatedVersion(), $message); } - return $function->getNodeClass(); + return $function; } - private function getFilterNodeClass(string $name, int $line): string + private function getFilter(string $name, int $line): TwigFilter { if (!$filter = $this->env->getFilter($name)) { - $e = new SyntaxError(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()); + $e = new SyntaxError(\sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()); $e->addSuggestions($name, array_keys($this->env->getFilters())); throw $e; } if ($filter->isDeprecated()) { - $message = sprintf('Twig Filter "%s" is deprecated', $filter->getName()); - if ($filter->getDeprecatedVersion()) { - $message .= sprintf(' since version %s', $filter->getDeprecatedVersion()); - } + $message = \sprintf('Twig Filter "%s" is deprecated', $filter->getName()); if ($filter->getAlternative()) { - $message .= sprintf('. Use "%s" instead', $filter->getAlternative()); + $message .= \sprintf('. Use "%s" instead', $filter->getAlternative()); } $src = $this->parser->getStream()->getSourceContext(); - $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); + $message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); - @trigger_error($message, \E_USER_DEPRECATED); + trigger_deprecation($filter->getDeprecatingPackage(), $filter->getDeprecatedVersion(), $message); } - return $filter->getNodeClass(); + return $filter; } // checks that the node only contains "constant" elements diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/AbstractExtension.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/AbstractExtension.php index 422925f31..a1b083b68 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/AbstractExtension.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/AbstractExtension.php @@ -40,6 +40,6 @@ abstract class AbstractExtension implements ExtensionInterface public function getOperators() { - return []; + return [[], []]; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/CoreExtension.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/CoreExtension.php index b77985859..3ed27a35c 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/CoreExtension.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/CoreExtension.php @@ -9,8 +9,15 @@ * file that was distributed with this source code. */ -namespace Twig\Extension { +namespace Twig\Extension; + +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Error\RuntimeError; +use Twig\Error\SyntaxError; use Twig\ExpressionParser; +use Twig\Markup; +use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\Binary\AddBinary; use Twig\Node\Expression\Binary\AndBinary; use Twig\Node\Expression\Binary\BitwiseAndBinary; @@ -23,6 +30,8 @@ use Twig\Node\Expression\Binary\EqualBinary; use Twig\Node\Expression\Binary\FloorDivBinary; use Twig\Node\Expression\Binary\GreaterBinary; use Twig\Node\Expression\Binary\GreaterEqualBinary; +use Twig\Node\Expression\Binary\HasEveryBinary; +use Twig\Node\Expression\Binary\HasSomeBinary; use Twig\Node\Expression\Binary\InBinary; use Twig\Node\Expression\Binary\LessBinary; use Twig\Node\Expression\Binary\LessEqualBinary; @@ -37,8 +46,12 @@ use Twig\Node\Expression\Binary\RangeBinary; use Twig\Node\Expression\Binary\SpaceshipBinary; use Twig\Node\Expression\Binary\StartsWithBinary; use Twig\Node\Expression\Binary\SubBinary; +use Twig\Node\Expression\BlockReferenceExpression; use Twig\Node\Expression\Filter\DefaultFilter; +use Twig\Node\Expression\FunctionNode\EnumCasesFunction; +use Twig\Node\Expression\GetAttrExpression; use Twig\Node\Expression\NullCoalesceExpression; +use Twig\Node\Expression\ParentExpression; use Twig\Node\Expression\Test\ConstantTest; use Twig\Node\Expression\Test\DefinedTest; use Twig\Node\Expression\Test\DivisiblebyTest; @@ -49,7 +62,12 @@ use Twig\Node\Expression\Test\SameasTest; use Twig\Node\Expression\Unary\NegUnary; use Twig\Node\Expression\Unary\NotUnary; use Twig\Node\Expression\Unary\PosUnary; +use Twig\Node\Node; use Twig\NodeVisitor\MacroAutoImportNodeVisitor; +use Twig\Parser; +use Twig\Source; +use Twig\Template; +use Twig\TemplateWrapper; use Twig\TokenParser\ApplyTokenParser; use Twig\TokenParser\BlockTokenParser; use Twig\TokenParser\DeprecatedTokenParser; @@ -64,11 +82,13 @@ use Twig\TokenParser\ImportTokenParser; use Twig\TokenParser\IncludeTokenParser; use Twig\TokenParser\MacroTokenParser; use Twig\TokenParser\SetTokenParser; +use Twig\TokenParser\TypesTokenParser; use Twig\TokenParser\UseTokenParser; use Twig\TokenParser\WithTokenParser; use Twig\TwigFilter; use Twig\TwigFunction; use Twig\TwigTest; +use Twig\Util\CallableArgumentsExtractor; final class CoreExtension extends AbstractExtension { @@ -79,8 +99,8 @@ final class CoreExtension extends AbstractExtension /** * Sets the default format to be used by the date filter. * - * @param string $format The default date format string - * @param string $dateIntervalFormat The default date interval format string + * @param string|null $format The default date format string + * @param string|null $dateIntervalFormat The default date interval format string */ public function setDateFormat($format = null, $dateIntervalFormat = null) { @@ -163,6 +183,7 @@ final class CoreExtension extends AbstractExtension new ImportTokenParser(), new FromTokenParser(), new SetTokenParser(), + new TypesTokenParser(), new FlushTokenParser(), new DoTokenParser(), new EmbedTokenParser(), @@ -175,65 +196,71 @@ final class CoreExtension extends AbstractExtension { return [ // formatting filters - new TwigFilter('date', 'twig_date_format_filter', ['needs_environment' => true]), - new TwigFilter('date_modify', 'twig_date_modify_filter', ['needs_environment' => true]), - new TwigFilter('format', 'twig_sprintf'), - new TwigFilter('replace', 'twig_replace_filter'), - new TwigFilter('number_format', 'twig_number_format_filter', ['needs_environment' => true]), + new TwigFilter('date', [$this, 'formatDate']), + new TwigFilter('date_modify', [$this, 'modifyDate']), + new TwigFilter('format', [self::class, 'sprintf']), + new TwigFilter('replace', [self::class, 'replace']), + new TwigFilter('number_format', [$this, 'formatNumber']), new TwigFilter('abs', 'abs'), - new TwigFilter('round', 'twig_round'), + new TwigFilter('round', [self::class, 'round']), // encoding - new TwigFilter('url_encode', 'twig_urlencode_filter'), + new TwigFilter('url_encode', [self::class, 'urlencode']), new TwigFilter('json_encode', 'json_encode'), - new TwigFilter('convert_encoding', 'twig_convert_encoding'), + new TwigFilter('convert_encoding', [self::class, 'convertEncoding']), // string filters - new TwigFilter('title', 'twig_title_string_filter', ['needs_environment' => true]), - new TwigFilter('capitalize', 'twig_capitalize_string_filter', ['needs_environment' => true]), - new TwigFilter('upper', 'twig_upper_filter', ['needs_environment' => true]), - new TwigFilter('lower', 'twig_lower_filter', ['needs_environment' => true]), - new TwigFilter('striptags', 'twig_striptags'), - new TwigFilter('trim', 'twig_trim_filter'), - new TwigFilter('nl2br', 'twig_nl2br', ['pre_escape' => 'html', 'is_safe' => ['html']]), - new TwigFilter('spaceless', 'twig_spaceless', ['is_safe' => ['html']]), + new TwigFilter('title', [self::class, 'titleCase'], ['needs_charset' => true]), + new TwigFilter('capitalize', [self::class, 'capitalize'], ['needs_charset' => true]), + new TwigFilter('upper', [self::class, 'upper'], ['needs_charset' => true]), + new TwigFilter('lower', [self::class, 'lower'], ['needs_charset' => true]), + new TwigFilter('striptags', [self::class, 'striptags']), + new TwigFilter('trim', [self::class, 'trim']), + new TwigFilter('nl2br', [self::class, 'nl2br'], ['pre_escape' => 'html', 'is_safe' => ['html']]), + new TwigFilter('spaceless', [self::class, 'spaceless'], ['is_safe' => ['html'], 'deprecated' => '3.12', 'deprecating_package' => 'twig/twig']), // array helpers - new TwigFilter('join', 'twig_join_filter'), - new TwigFilter('split', 'twig_split_filter', ['needs_environment' => true]), - new TwigFilter('sort', 'twig_sort_filter', ['needs_environment' => true]), - new TwigFilter('merge', 'twig_array_merge'), - new TwigFilter('batch', 'twig_array_batch'), - new TwigFilter('column', 'twig_array_column'), - new TwigFilter('filter', 'twig_array_filter', ['needs_environment' => true]), - new TwigFilter('map', 'twig_array_map', ['needs_environment' => true]), - new TwigFilter('reduce', 'twig_array_reduce', ['needs_environment' => true]), + new TwigFilter('join', [self::class, 'join']), + new TwigFilter('split', [self::class, 'split'], ['needs_charset' => true]), + new TwigFilter('sort', [self::class, 'sort'], ['needs_environment' => true]), + new TwigFilter('merge', [self::class, 'merge']), + new TwigFilter('batch', [self::class, 'batch']), + new TwigFilter('column', [self::class, 'column']), + new TwigFilter('filter', [self::class, 'filter'], ['needs_environment' => true]), + new TwigFilter('map', [self::class, 'map'], ['needs_environment' => true]), + new TwigFilter('reduce', [self::class, 'reduce'], ['needs_environment' => true]), + new TwigFilter('find', [self::class, 'find'], ['needs_environment' => true]), // string/array filters - new TwigFilter('reverse', 'twig_reverse_filter', ['needs_environment' => true]), - new TwigFilter('length', 'twig_length_filter', ['needs_environment' => true]), - new TwigFilter('slice', 'twig_slice', ['needs_environment' => true]), - new TwigFilter('first', 'twig_first', ['needs_environment' => true]), - new TwigFilter('last', 'twig_last', ['needs_environment' => true]), + new TwigFilter('reverse', [self::class, 'reverse'], ['needs_charset' => true]), + new TwigFilter('shuffle', [self::class, 'shuffle'], ['needs_charset' => true]), + new TwigFilter('length', [self::class, 'length'], ['needs_charset' => true]), + new TwigFilter('slice', [self::class, 'slice'], ['needs_charset' => true]), + new TwigFilter('first', [self::class, 'first'], ['needs_charset' => true]), + new TwigFilter('last', [self::class, 'last'], ['needs_charset' => true]), // iteration and runtime - new TwigFilter('default', '_twig_default_filter', ['node_class' => DefaultFilter::class]), - new TwigFilter('keys', 'twig_get_array_keys_filter'), + new TwigFilter('default', [self::class, 'default'], ['node_class' => DefaultFilter::class]), + new TwigFilter('keys', [self::class, 'keys']), ]; } public function getFunctions(): array { return [ + new TwigFunction('parent', null, ['parser_callable' => [self::class, 'parseParentFunction']]), + new TwigFunction('block', null, ['parser_callable' => [self::class, 'parseBlockFunction']]), + new TwigFunction('attribute', null, ['parser_callable' => [self::class, 'parseAttributeFunction']]), new TwigFunction('max', 'max'), new TwigFunction('min', 'min'), new TwigFunction('range', 'range'), - new TwigFunction('constant', 'twig_constant'), - new TwigFunction('cycle', 'twig_cycle'), - new TwigFunction('random', 'twig_random', ['needs_environment' => true]), - new TwigFunction('date', 'twig_date_converter', ['needs_environment' => true]), - new TwigFunction('include', 'twig_include', ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all']]), - new TwigFunction('source', 'twig_source', ['needs_environment' => true, 'is_safe' => ['all']]), + new TwigFunction('constant', [self::class, 'constant']), + new TwigFunction('cycle', [self::class, 'cycle']), + new TwigFunction('random', [self::class, 'random'], ['needs_charset' => true]), + new TwigFunction('date', [$this, 'convertDate']), + new TwigFunction('include', [self::class, 'include'], ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all']]), + new TwigFunction('source', [self::class, 'source'], ['needs_environment' => true, 'is_safe' => ['all']]), + new TwigFunction('enum_cases', [self::class, 'enumCases'], ['node_class' => EnumCasesFunction::class]), ]; } @@ -248,8 +275,10 @@ final class CoreExtension extends AbstractExtension new TwigTest('null', null, ['node_class' => NullTest::class]), new TwigTest('divisible by', null, ['node_class' => DivisiblebyTest::class, 'one_mandatory_argument' => true]), new TwigTest('constant', null, ['node_class' => ConstantTest::class]), - new TwigTest('empty', 'twig_test_empty'), - new TwigTest('iterable', 'twig_test_iterable'), + new TwigTest('empty', [self::class, 'testEmpty']), + new TwigTest('iterable', 'is_iterable'), + new TwigTest('sequence', [self::class, 'testSequence']), + new TwigTest('mapping', [self::class, 'testMapping']), ]; } @@ -284,6 +313,8 @@ final class CoreExtension extends AbstractExtension 'matches' => ['precedence' => 20, 'class' => MatchesBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], 'starts with' => ['precedence' => 20, 'class' => StartsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], 'ends with' => ['precedence' => 20, 'class' => EndsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'has some' => ['precedence' => 20, 'class' => HasSomeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'has every' => ['precedence' => 20, 'class' => HasEveryBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], '..' => ['precedence' => 25, 'class' => RangeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], '+' => ['precedence' => 30, 'class' => AddBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], '-' => ['precedence' => 30, 'class' => SubBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], @@ -299,193 +330,226 @@ final class CoreExtension extends AbstractExtension ], ]; } -} -} -namespace { - use Twig\Environment; - use Twig\Error\LoaderError; - use Twig\Error\RuntimeError; - use Twig\Extension\CoreExtension; - use Twig\Extension\SandboxExtension; - use Twig\Markup; - use Twig\Source; - use Twig\Template; - use Twig\TemplateWrapper; + /** + * Cycles over a sequence. + * + * @param array|\ArrayAccess $values A non-empty sequence of values + * @param positive-int $position The position of the value to return in the cycle + * + * @return mixed The value at the given position in the sequence, wrapping around as needed + * + * @internal + */ + public static function cycle($values, $position): mixed + { + if (!\is_array($values)) { + if (!$values instanceof \ArrayAccess) { + throw new RuntimeError('The "cycle" function expects an array or "ArrayAccess" as first argument.'); + } -/** - * Cycles over a value. - * - * @param \ArrayAccess|array $values - * @param int $position The cycle position - * - * @return string The next value in the cycle - */ -function twig_cycle($values, $position) -{ - if (!\is_array($values) && !$values instanceof \ArrayAccess) { - return $values; + if (!is_countable($values)) { + // To be uncommented in 4.0 + // throw new RuntimeError('The "cycle" function expects a countable sequence as first argument.'); + + trigger_deprecation('twig/twig', '3.12', 'Passing a non-countable sequence of values to "%s()" is deprecated.', __METHOD__); + + return $values; + } + + $values = self::toArray($values, false); + } + + if (!$count = \count($values)) { + throw new RuntimeError('The "cycle" function does not work on empty sequences.'); + } + + return $values[$position % $count]; } - return $values[$position % \count($values)]; -} + /** + * Returns a random value depending on the supplied parameter type: + * - a random item from a \Traversable or array + * - a random character from a string + * - a random integer between 0 and the integer parameter. + * + * @param \Traversable|array|int|float|string $values The values to pick a random item from + * @param int|null $max Maximum value used when $values is an int + * + * @return mixed A random value from the given sequence + * + * @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is) + * + * @internal + */ + public static function random(string $charset, $values = null, $max = null) + { + if (null === $values) { + return null === $max ? mt_rand() : mt_rand(0, (int) $max); + } -/** - * Returns a random value depending on the supplied parameter type: - * - a random item from a \Traversable or array - * - a random character from a string - * - a random integer between 0 and the integer parameter. - * - * @param \Traversable|array|int|float|string $values The values to pick a random item from - * @param int|null $max Maximum value used when $values is an int - * - * @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is) - * - * @return mixed A random value from the given sequence - */ -function twig_random(Environment $env, $values = null, $max = null) -{ - if (null === $values) { - return null === $max ? mt_rand() : mt_rand(0, (int) $max); - } - - if (\is_int($values) || \is_float($values)) { - if (null === $max) { - if ($values < 0) { - $max = 0; - $min = $values; + if (\is_int($values) || \is_float($values)) { + if (null === $max) { + if ($values < 0) { + $max = 0; + $min = $values; + } else { + $max = $values; + $min = 0; + } } else { - $max = $values; - $min = 0; + $min = $values; } + + return mt_rand((int) $min, (int) $max); + } + + if (\is_string($values)) { + if ('' === $values) { + return ''; + } + + if ('UTF-8' !== $charset) { + $values = self::convertEncoding($values, 'UTF-8', $charset); + } + + // unicode version of str_split() + // split at all positions, but not after the start and not before the end + $values = preg_split('/(? $value) { + $values[$i] = self::convertEncoding($value, $charset, 'UTF-8'); + } + } + } + + if (!is_iterable($values)) { + return $values; + } + + $values = self::toArray($values); + + if (0 === \count($values)) { + throw new RuntimeError('The random function cannot pick from an empty sequence/mapping.'); + } + + return $values[array_rand($values, 1)]; + } + + /** + * Formats a date. + * + * {{ post.published_at|date("m/d/Y") }} + * + * @param \DateTimeInterface|\DateInterval|string $date A date + * @param string|null $format The target format, null to use the default + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + */ + public function formatDate($date, $format = null, $timezone = null): string + { + if (null === $format) { + $formats = $this->getDateFormat(); + $format = $date instanceof \DateInterval ? $formats[1] : $formats[0]; + } + + if ($date instanceof \DateInterval) { + return $date->format($format); + } + + return $this->convertDate($date, $timezone)->format($format); + } + + /** + * Returns a new date object modified. + * + * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }} + * + * @param \DateTimeInterface|string $date A date + * @param string $modifier A modifier string + * + * @return \DateTime|\DateTimeImmutable + * + * @internal + */ + public function modifyDate($date, $modifier) + { + return $this->convertDate($date, false)->modify($modifier); + } + + /** + * Returns a formatted string. + * + * @param string|null $format + * @param ...$values + * + * @internal + */ + public static function sprintf($format, ...$values): string + { + return \sprintf($format ?? '', ...$values); + } + + /** + * @internal + */ + public static function dateConverter(Environment $env, $date, $format = null, $timezone = null): string + { + return $env->getExtension(self::class)->formatDate($date, $format, $timezone); + } + + /** + * Converts an input to a \DateTime instance. + * + * {% if date(user.created_at) < date('+2days') %} + * {# do something #} + * {% endif %} + * + * @param \DateTimeInterface|string|null $date A date or null to use the current time + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + * + * @return \DateTime|\DateTimeImmutable + */ + public function convertDate($date = null, $timezone = null) + { + // determine the timezone + if (false !== $timezone) { + if (null === $timezone) { + $timezone = $this->getTimezone(); + } elseif (!$timezone instanceof \DateTimeZone) { + $timezone = new \DateTimeZone($timezone); + } + } + + // immutable dates + if ($date instanceof \DateTimeImmutable) { + return false !== $timezone ? $date->setTimezone($timezone) : $date; + } + + if ($date instanceof \DateTime) { + $date = clone $date; + if (false !== $timezone) { + $date->setTimezone($timezone); + } + + return $date; + } + + if (null === $date || 'now' === $date) { + if (null === $date) { + $date = 'now'; + } + + return new \DateTime($date, false !== $timezone ? $timezone : $this->getTimezone()); + } + + $asString = (string) $date; + if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) { + $date = new \DateTime('@'.$date); } else { - $min = $values; - $max = $max; + $date = new \DateTime($date, $this->getTimezone()); } - return mt_rand((int) $min, (int) $max); - } - - if (\is_string($values)) { - if ('' === $values) { - return ''; - } - - $charset = $env->getCharset(); - - if ('UTF-8' !== $charset) { - $values = twig_convert_encoding($values, 'UTF-8', $charset); - } - - // unicode version of str_split() - // split at all positions, but not after the start and not before the end - $values = preg_split('/(? $value) { - $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8'); - } - } - } - - if (!twig_test_iterable($values)) { - return $values; - } - - $values = twig_to_array($values); - - if (0 === \count($values)) { - throw new RuntimeError('The random function cannot pick from an empty array.'); - } - - return $values[array_rand($values, 1)]; -} - -/** - * Converts a date to the given format. - * - * {{ post.published_at|date("m/d/Y") }} - * - * @param \DateTimeInterface|\DateInterval|string $date A date - * @param string|null $format The target format, null to use the default - * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged - * - * @return string The formatted date - */ -function twig_date_format_filter(Environment $env, $date, $format = null, $timezone = null) -{ - if (null === $format) { - $formats = $env->getExtension(CoreExtension::class)->getDateFormat(); - $format = $date instanceof \DateInterval ? $formats[1] : $formats[0]; - } - - if ($date instanceof \DateInterval) { - return $date->format($format); - } - - return twig_date_converter($env, $date, $timezone)->format($format); -} - -/** - * Returns a new date object modified. - * - * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }} - * - * @param \DateTimeInterface|string $date A date - * @param string $modifier A modifier string - * - * @return \DateTimeInterface - */ -function twig_date_modify_filter(Environment $env, $date, $modifier) -{ - $date = twig_date_converter($env, $date, false); - - return $date->modify($modifier); -} - -/** - * Returns a formatted string. - * - * @param string|null $format - * @param ...$values - * - * @return string - */ -function twig_sprintf($format, ...$values) -{ - return sprintf($format ?? '', ...$values); -} - -/** - * Converts an input to a \DateTime instance. - * - * {% if date(user.created_at) < date('+2days') %} - * {# do something #} - * {% endif %} - * - * @param \DateTimeInterface|string|null $date A date or null to use the current time - * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged - * - * @return \DateTimeInterface - */ -function twig_date_converter(Environment $env, $date = null, $timezone = null) -{ - // determine the timezone - if (false !== $timezone) { - if (null === $timezone) { - $timezone = $env->getExtension(CoreExtension::class)->getTimezone(); - } elseif (!$timezone instanceof \DateTimeZone) { - $timezone = new \DateTimeZone($timezone); - } - } - - // immutable dates - if ($date instanceof \DateTimeImmutable) { - return false !== $timezone ? $date->setTimezone($timezone) : $date; - } - - if ($date instanceof \DateTimeInterface) { - $date = clone $date; if (false !== $timezone) { $date->setTimezone($timezone); } @@ -493,968 +557,1086 @@ function twig_date_converter(Environment $env, $date = null, $timezone = null) return $date; } - if (null === $date || 'now' === $date) { - if (null === $date) { - $date = 'now'; + /** + * Replaces strings within a string. + * + * @param string|null $str String to replace in + * @param array|\Traversable $from Replace values + * + * @internal + */ + public static function replace($str, $from): string + { + if (!is_iterable($from)) { + throw new RuntimeError(\sprintf('The "replace" filter expects a sequence/mapping or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from))); } - return new \DateTime($date, false !== $timezone ? $timezone : $env->getExtension(CoreExtension::class)->getTimezone()); + return strtr($str ?? '', self::toArray($from)); } - $asString = (string) $date; - if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) { - $date = new \DateTime('@'.$date); - } else { - $date = new \DateTime($date, $env->getExtension(CoreExtension::class)->getTimezone()); - } + /** + * Rounds a number. + * + * @param int|float|string|null $value The value to round + * @param int|float $precision The rounding precision + * @param string $method The method to use for rounding + * + * @return int|float The rounded number + * + * @internal + */ + public static function round($value, $precision = 0, $method = 'common') + { + $value = (float) $value; - if (false !== $timezone) { - $date->setTimezone($timezone); - } - - return $date; -} - -/** - * Replaces strings within a string. - * - * @param string|null $str String to replace in - * @param array|\Traversable $from Replace values - * - * @return string - */ -function twig_replace_filter($str, $from) -{ - if (!twig_test_iterable($from)) { - throw new RuntimeError(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from))); - } - - return strtr($str ?? '', twig_to_array($from)); -} - -/** - * Rounds a number. - * - * @param int|float|string|null $value The value to round - * @param int|float $precision The rounding precision - * @param string $method The method to use for rounding - * - * @return int|float The rounded number - */ -function twig_round($value, $precision = 0, $method = 'common') -{ - $value = (float) $value; - - if ('common' === $method) { - return round($value, $precision); - } - - if ('ceil' !== $method && 'floor' !== $method) { - throw new RuntimeError('The round filter only supports the "common", "ceil", and "floor" methods.'); - } - - return $method($value * 10 ** $precision) / 10 ** $precision; -} - -/** - * Number format filter. - * - * All of the formatting options can be left null, in that case the defaults will - * be used. Supplying any of the parameters will override the defaults set in the - * environment object. - * - * @param mixed $number A float/int/string of the number to format - * @param int $decimal the number of decimal points to display - * @param string $decimalPoint the character(s) to use for the decimal point - * @param string $thousandSep the character(s) to use for the thousands separator - * - * @return string The formatted number - */ -function twig_number_format_filter(Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null) -{ - $defaults = $env->getExtension(CoreExtension::class)->getNumberFormat(); - if (null === $decimal) { - $decimal = $defaults[0]; - } - - if (null === $decimalPoint) { - $decimalPoint = $defaults[1]; - } - - if (null === $thousandSep) { - $thousandSep = $defaults[2]; - } - - return number_format((float) $number, $decimal, $decimalPoint, $thousandSep); -} - -/** - * URL encodes (RFC 3986) a string as a path segment or an array as a query string. - * - * @param string|array|null $url A URL or an array of query parameters - * - * @return string The URL encoded value - */ -function twig_urlencode_filter($url) -{ - if (\is_array($url)) { - return http_build_query($url, '', '&', \PHP_QUERY_RFC3986); - } - - return rawurlencode($url ?? ''); -} - -/** - * Merges an array with another one. - * - * {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %} - * - * {% set items = items|merge({ 'peugeot': 'car' }) %} - * - * {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #} - * - * @param array|\Traversable $arr1 An array - * @param array|\Traversable $arr2 An array - * - * @return array The merged array - */ -function twig_array_merge($arr1, $arr2) -{ - if (!twig_test_iterable($arr1)) { - throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($arr1))); - } - - if (!twig_test_iterable($arr2)) { - throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.', \gettype($arr2))); - } - - return array_merge(twig_to_array($arr1), twig_to_array($arr2)); -} - -/** - * Slices a variable. - * - * @param mixed $item A variable - * @param int $start Start of the slice - * @param int $length Size of the slice - * @param bool $preserveKeys Whether to preserve key or not (when the input is an array) - * - * @return mixed The sliced variable - */ -function twig_slice(Environment $env, $item, $start, $length = null, $preserveKeys = false) -{ - if ($item instanceof \Traversable) { - while ($item instanceof \IteratorAggregate) { - $item = $item->getIterator(); + if ('common' === $method) { + return round($value, $precision); } - if ($start >= 0 && $length >= 0 && $item instanceof \Iterator) { - try { - return iterator_to_array(new \LimitIterator($item, $start, null === $length ? -1 : $length), $preserveKeys); - } catch (\OutOfBoundsException $e) { - return []; + if ('ceil' !== $method && 'floor' !== $method) { + throw new RuntimeError('The round filter only supports the "common", "ceil", and "floor" methods.'); + } + + return $method($value * 10 ** $precision) / 10 ** $precision; + } + + /** + * Formats a number. + * + * All of the formatting options can be left null, in that case the defaults will + * be used. Supplying any of the parameters will override the defaults set in the + * environment object. + * + * @param mixed $number A float/int/string of the number to format + * @param int|null $decimal the number of decimal points to display + * @param string|null $decimalPoint the character(s) to use for the decimal point + * @param string|null $thousandSep the character(s) to use for the thousands separator + */ + public function formatNumber($number, $decimal = null, $decimalPoint = null, $thousandSep = null): string + { + $defaults = $this->getNumberFormat(); + if (null === $decimal) { + $decimal = $defaults[0]; + } + + if (null === $decimalPoint) { + $decimalPoint = $defaults[1]; + } + + if (null === $thousandSep) { + $thousandSep = $defaults[2]; + } + + return number_format((float) $number, $decimal, $decimalPoint, $thousandSep); + } + + /** + * URL encodes (RFC 3986) a string as a path segment or an array as a query string. + * + * @param string|array|null $url A URL or an array of query parameters + * + * @internal + */ + public static function urlencode($url): string + { + if (\is_array($url)) { + return http_build_query($url, '', '&', \PHP_QUERY_RFC3986); + } + + return rawurlencode($url ?? ''); + } + + /** + * Merges any number of arrays or Traversable objects. + * + * {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %} + * + * {% set items = items|merge({ 'peugeot': 'car' }, { 'banana': 'fruit' }) %} + * + * {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'banana': 'fruit' } #} + * + * @param array|\Traversable ...$arrays Any number of arrays or Traversable objects to merge + * + * @internal + */ + public static function merge(...$arrays): array + { + $result = []; + + foreach ($arrays as $argNumber => $array) { + if (!is_iterable($array)) { + throw new RuntimeError(\sprintf('The merge filter only works with sequences/mappings or "Traversable", got "%s" for argument %d.', \gettype($array), $argNumber + 1)); } + + $result = array_merge($result, self::toArray($array)); } - $item = iterator_to_array($item, $preserveKeys); + return $result; } - if (\is_array($item)) { - return \array_slice($item, $start, $length, $preserveKeys); - } + /** + * Slices a variable. + * + * @param mixed $item A variable + * @param int $start Start of the slice + * @param int $length Size of the slice + * @param bool $preserveKeys Whether to preserve key or not (when the input is an array) + * + * @return mixed The sliced variable + * + * @internal + */ + public static function slice(string $charset, $item, $start, $length = null, $preserveKeys = false) + { + if ($item instanceof \Traversable) { + while ($item instanceof \IteratorAggregate) { + $item = $item->getIterator(); + } - return (string) mb_substr((string) $item, $start, $length, $env->getCharset()); -} + if ($start >= 0 && $length >= 0 && $item instanceof \Iterator) { + try { + return iterator_to_array(new \LimitIterator($item, $start, $length ?? -1), $preserveKeys); + } catch (\OutOfBoundsException $e) { + return []; + } + } -/** - * Returns the first element of the item. - * - * @param mixed $item A variable - * - * @return mixed The first element of the item - */ -function twig_first(Environment $env, $item) -{ - $elements = twig_slice($env, $item, 0, 1, false); - - return \is_string($elements) ? $elements : current($elements); -} - -/** - * Returns the last element of the item. - * - * @param mixed $item A variable - * - * @return mixed The last element of the item - */ -function twig_last(Environment $env, $item) -{ - $elements = twig_slice($env, $item, -1, 1, false); - - return \is_string($elements) ? $elements : current($elements); -} - -/** - * Joins the values to a string. - * - * The separators between elements are empty strings per default, you can define them with the optional parameters. - * - * {{ [1, 2, 3]|join(', ', ' and ') }} - * {# returns 1, 2 and 3 #} - * - * {{ [1, 2, 3]|join('|') }} - * {# returns 1|2|3 #} - * - * {{ [1, 2, 3]|join }} - * {# returns 123 #} - * - * @param array $value An array - * @param string $glue The separator - * @param string|null $and The separator for the last pair - * - * @return string The concatenated string - */ -function twig_join_filter($value, $glue = '', $and = null) -{ - if (!twig_test_iterable($value)) { - $value = (array) $value; - } - - $value = twig_to_array($value, false); - - if (0 === \count($value)) { - return ''; - } - - if (null === $and || $and === $glue) { - return implode($glue, $value); - } - - if (1 === \count($value)) { - return $value[0]; - } - - return implode($glue, \array_slice($value, 0, -1)).$and.$value[\count($value) - 1]; -} - -/** - * Splits the string into an array. - * - * {{ "one,two,three"|split(',') }} - * {# returns [one, two, three] #} - * - * {{ "one,two,three,four,five"|split(',', 3) }} - * {# returns [one, two, "three,four,five"] #} - * - * {{ "123"|split('') }} - * {# returns [1, 2, 3] #} - * - * {{ "aabbcc"|split('', 2) }} - * {# returns [aa, bb, cc] #} - * - * @param string|null $value A string - * @param string $delimiter The delimiter - * @param int $limit The limit - * - * @return array The split string as an array - */ -function twig_split_filter(Environment $env, $value, $delimiter, $limit = null) -{ - $value = $value ?? ''; - - if (\strlen($delimiter) > 0) { - return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); - } - - if ($limit <= 1) { - return preg_split('/(?getCharset()); - if ($length < $limit) { - return [$value]; - } - - $r = []; - for ($i = 0; $i < $length; $i += $limit) { - $r[] = mb_substr($value, $i, $limit, $env->getCharset()); - } - - return $r; -} - -// The '_default' filter is used internally to avoid using the ternary operator -// which costs a lot for big contexts (before PHP 5.4). So, on average, -// a function call is cheaper. -/** - * @internal - */ -function _twig_default_filter($value, $default = '') -{ - if (twig_test_empty($value)) { - return $default; - } - - return $value; -} - -/** - * Returns the keys for the given array. - * - * It is useful when you want to iterate over the keys of an array: - * - * {% for key in array|keys %} - * {# ... #} - * {% endfor %} - * - * @param array $array An array - * - * @return array The keys - */ -function twig_get_array_keys_filter($array) -{ - if ($array instanceof \Traversable) { - while ($array instanceof \IteratorAggregate) { - $array = $array->getIterator(); + $item = iterator_to_array($item, $preserveKeys); } - $keys = []; - if ($array instanceof \Iterator) { - $array->rewind(); - while ($array->valid()) { - $keys[] = $array->key(); - $array->next(); + if (\is_array($item)) { + return \array_slice($item, $start, $length, $preserveKeys); + } + + return mb_substr((string) $item, $start, $length, $charset); + } + + /** + * Returns the first element of the item. + * + * @param mixed $item A variable + * + * @return mixed The first element of the item + * + * @internal + */ + public static function first(string $charset, $item) + { + $elements = self::slice($charset, $item, 0, 1, false); + + return \is_string($elements) ? $elements : current($elements); + } + + /** + * Returns the last element of the item. + * + * @param mixed $item A variable + * + * @return mixed The last element of the item + * + * @internal + */ + public static function last(string $charset, $item) + { + $elements = self::slice($charset, $item, -1, 1, false); + + return \is_string($elements) ? $elements : current($elements); + } + + /** + * Joins the values to a string. + * + * The separators between elements are empty strings per default, you can define them with the optional parameters. + * + * {{ [1, 2, 3]|join(', ', ' and ') }} + * {# returns 1, 2 and 3 #} + * + * {{ [1, 2, 3]|join('|') }} + * {# returns 1|2|3 #} + * + * {{ [1, 2, 3]|join }} + * {# returns 123 #} + * + * @param array $value An array + * @param string $glue The separator + * @param string|null $and The separator for the last pair + * + * @internal + */ + public static function join($value, $glue = '', $and = null): string + { + if (!is_iterable($value)) { + $value = (array) $value; + } + + $value = self::toArray($value, false); + + if (0 === \count($value)) { + return ''; + } + + if (null === $and || $and === $glue) { + return implode($glue, $value); + } + + if (1 === \count($value)) { + return $value[0]; + } + + return implode($glue, \array_slice($value, 0, -1)).$and.$value[\count($value) - 1]; + } + + /** + * Splits the string into an array. + * + * {{ "one,two,three"|split(',') }} + * {# returns [one, two, three] #} + * + * {{ "one,two,three,four,five"|split(',', 3) }} + * {# returns [one, two, "three,four,five"] #} + * + * {{ "123"|split('') }} + * {# returns [1, 2, 3] #} + * + * {{ "aabbcc"|split('', 2) }} + * {# returns [aa, bb, cc] #} + * + * @param string|null $value A string + * @param string $delimiter The delimiter + * @param int|null $limit The limit + * + * @internal + */ + public static function split(string $charset, $value, $delimiter, $limit = null): array + { + $value = $value ?? ''; + + if ('' !== $delimiter) { + return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); + } + + if ($limit <= 1) { + return preg_split('/(?getIterator(); + } + + $keys = []; + if ($array instanceof \Iterator) { + $array->rewind(); + while ($array->valid()) { + $keys[] = $array->key(); + $array->next(); + } + + return $keys; + } + + foreach ($array as $key => $item) { + $keys[] = $key; } return $keys; } - foreach ($array as $key => $item) { - $keys[] = $key; + if (!\is_array($array)) { + return []; } - return $keys; + return array_keys($array); } - if (!\is_array($array)) { - return []; - } - - return array_keys($array); -} - -/** - * Reverses a variable. - * - * @param array|\Traversable|string|null $item An array, a \Traversable instance, or a string - * @param bool $preserveKeys Whether to preserve key or not - * - * @return mixed The reversed input - */ -function twig_reverse_filter(Environment $env, $item, $preserveKeys = false) -{ - if ($item instanceof \Traversable) { - return array_reverse(iterator_to_array($item), $preserveKeys); - } - - if (\is_array($item)) { - return array_reverse($item, $preserveKeys); - } - - $string = (string) $item; - - $charset = $env->getCharset(); - - if ('UTF-8' !== $charset) { - $string = twig_convert_encoding($string, 'UTF-8', $charset); - } - - preg_match_all('/./us', $string, $matches); - - $string = implode('', array_reverse($matches[0])); - - if ('UTF-8' !== $charset) { - $string = twig_convert_encoding($string, $charset, 'UTF-8'); - } - - return $string; -} - -/** - * Sorts an array. - * - * @param array|\Traversable $array - * - * @return array - */ -function twig_sort_filter(Environment $env, $array, $arrow = null) -{ - if ($array instanceof \Traversable) { - $array = iterator_to_array($array); - } elseif (!\is_array($array)) { - throw new RuntimeError(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', \gettype($array))); - } - - if (null !== $arrow) { - twig_check_arrow_in_sandbox($env, $arrow, 'sort', 'filter'); - - uasort($array, $arrow); - } else { - asort($array); - } - - return $array; -} - -/** - * @internal - */ -function twig_in_filter($value, $compare) -{ - if ($value instanceof Markup) { - $value = (string) $value; - } - if ($compare instanceof Markup) { - $compare = (string) $compare; - } - - if (\is_string($compare)) { - if (\is_string($value) || \is_int($value) || \is_float($value)) { - return '' === $value || false !== strpos($compare, (string) $value); + /** + * Reverses a variable. + * + * @param array|\Traversable|string|null $item An array, a \Traversable instance, or a string + * @param bool $preserveKeys Whether to preserve key or not + * + * @return mixed The reversed input + * + * @internal + */ + public static function reverse(string $charset, $item, $preserveKeys = false) + { + if ($item instanceof \Traversable) { + return array_reverse(iterator_to_array($item), $preserveKeys); } - return false; + if (\is_array($item)) { + return array_reverse($item, $preserveKeys); + } + + $string = (string) $item; + + if ('UTF-8' !== $charset) { + $string = self::convertEncoding($string, 'UTF-8', $charset); + } + + preg_match_all('/./us', $string, $matches); + + $string = implode('', array_reverse($matches[0])); + + if ('UTF-8' !== $charset) { + $string = self::convertEncoding($string, $charset, 'UTF-8'); + } + + return $string; } - if (!is_iterable($compare)) { - return false; + /** + * Shuffles an array, a \Traversable instance, or a string. + * The function does not preserve keys. + * + * @param array|\Traversable|string|null $item + * + * @return mixed + * + * @internal + */ + public static function shuffle(string $charset, $item) + { + if (\is_string($item)) { + if ('UTF-8' !== $charset) { + $item = self::convertEncoding($item, 'UTF-8', $charset); + } + + $item = preg_split('/(? string + if (\is_int($a) && \is_string($b)) { + $bTrim = trim($b, " \t\n\r\v\f"); + if (!is_numeric($bTrim)) { + return (string) $a <=> $b; + } + if ((int) $bTrim == $bTrim) { + return $a <=> (int) $bTrim; + } else { + return (float) $a <=> (float) $bTrim; + } + } + if (\is_string($a) && \is_int($b)) { + $aTrim = trim($a, " \t\n\r\v\f"); + if (!is_numeric($aTrim)) { + return $a <=> (string) $b; + } + if ((int) $aTrim == $aTrim) { + return (int) $aTrim <=> $b; + } else { + return (float) $aTrim <=> (float) $b; + } + } + + // float <=> string + if (\is_float($a) && \is_string($b)) { + if (is_nan($a)) { + return 1; + } + $bTrim = trim($b, " \t\n\r\v\f"); + if (!is_numeric($bTrim)) { + return (string) $a <=> $b; + } + + return $a <=> (float) $bTrim; + } + if (\is_string($a) && \is_float($b)) { + if (is_nan($b)) { + return 1; + } + $aTrim = trim($a, " \t\n\r\v\f"); + if (!is_numeric($aTrim)) { + return $a <=> (string) $b; + } + + return (float) $aTrim <=> $b; + } + + // fallback to <=> + return $a <=> $b; + } + + /** + * @throws RuntimeError When an invalid pattern is used + * + * @internal + */ + public static function matches(string $regexp, ?string $str): int + { + set_error_handler(function ($t, $m) use ($regexp) { + throw new RuntimeError(\sprintf('Regexp "%s" passed to "matches" is not valid', $regexp).substr($m, 12)); + }); + try { + return preg_match($regexp, $str ?? ''); + } finally { + restore_error_handler(); } } - return false; -} + /** + * Returns a trimmed string. + * + * @param string|null $string + * @param string|null $characterMask + * @param string $side + * + * @throws RuntimeError When an invalid trimming side is used (not a string or not 'left', 'right', or 'both') + * + * @internal + */ + public static function trim($string, $characterMask = null, $side = 'both'): string + { + if (null === $characterMask) { + $characterMask = " \t\n\r\0\x0B"; + } -/** - * Compares two values using a more strict version of the PHP non-strict comparison operator. - * - * @see https://wiki.php.net/rfc/string_to_number_comparison - * @see https://wiki.php.net/rfc/trailing_whitespace_numerics - * - * @internal - */ -function twig_compare($a, $b) -{ - // int <=> string - if (\is_int($a) && \is_string($b)) { - $bTrim = trim($b, " \t\n\r\v\f"); - if (!is_numeric($bTrim)) { - return (string) $a <=> $b; - } - if ((int) $bTrim == $bTrim) { - return $a <=> (int) $bTrim; - } else { - return (float) $a <=> (float) $bTrim; - } - } - if (\is_string($a) && \is_int($b)) { - $aTrim = trim($a, " \t\n\r\v\f"); - if (!is_numeric($aTrim)) { - return $a <=> (string) $b; - } - if ((int) $aTrim == $aTrim) { - return (int) $aTrim <=> $b; - } else { - return (float) $aTrim <=> (float) $b; + switch ($side) { + case 'both': + return trim($string ?? '', $characterMask); + case 'left': + return ltrim($string ?? '', $characterMask); + case 'right': + return rtrim($string ?? '', $characterMask); + default: + throw new RuntimeError('Trimming side must be "left", "right" or "both".'); } } - // float <=> string - if (\is_float($a) && \is_string($b)) { - if (is_nan($a)) { - return 1; - } - $bTrim = trim($b, " \t\n\r\v\f"); - if (!is_numeric($bTrim)) { - return (string) $a <=> $b; + /** + * Inserts HTML line breaks before all newlines in a string. + * + * @param string|null $string + * + * @internal + */ + public static function nl2br($string): string + { + return nl2br($string ?? ''); + } + + /** + * Removes whitespaces between HTML tags. + * + * @param string|null $content + * + * @internal + */ + public static function spaceless($content): string + { + return trim(preg_replace('/>\s+<', $content ?? '')); + } + + /** + * @param string|null $string + * @param string $to + * @param string $from + * + * @internal + */ + public static function convertEncoding($string, $to, $from): string + { + if (!\function_exists('iconv')) { + throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); } - return $a <=> (float) $bTrim; + return iconv($from, $to, $string ?? ''); } - if (\is_string($a) && \is_float($b)) { - if (is_nan($b)) { - return 1; - } - $aTrim = trim($a, " \t\n\r\v\f"); - if (!is_numeric($aTrim)) { - return $a <=> (string) $b; + + /** + * Returns the length of a variable. + * + * @param mixed $thing A variable + * + * @internal + */ + public static function length(string $charset, $thing): int + { + if (null === $thing) { + return 0; } - return (float) $aTrim <=> $b; + if (\is_scalar($thing)) { + return mb_strlen($thing, $charset); + } + + if ($thing instanceof \Countable || \is_array($thing) || $thing instanceof \SimpleXMLElement) { + return \count($thing); + } + + if ($thing instanceof \Traversable) { + return iterator_count($thing); + } + + if ($thing instanceof \Stringable) { + return mb_strlen((string) $thing, $charset); + } + + return 1; } - // fallback to <=> - return $a <=> $b; -} - -/** - * Returns a trimmed string. - * - * @param string|null $string - * @param string|null $characterMask - * @param string $side - * - * @return string - * - * @throws RuntimeError When an invalid trimming side is used (not a string or not 'left', 'right', or 'both') - */ -function twig_trim_filter($string, $characterMask = null, $side = 'both') -{ - if (null === $characterMask) { - $characterMask = " \t\n\r\0\x0B"; + /** + * Converts a string to uppercase. + * + * @param string|null $string A string + * + * @internal + */ + public static function upper(string $charset, $string): string + { + return mb_strtoupper($string ?? '', $charset); } - switch ($side) { - case 'both': - return trim($string ?? '', $characterMask); - case 'left': - return ltrim($string ?? '', $characterMask); - case 'right': - return rtrim($string ?? '', $characterMask); - default: - throw new RuntimeError('Trimming side must be "left", "right" or "both".'); - } -} - -/** - * Inserts HTML line breaks before all newlines in a string. - * - * @param string|null $string - * - * @return string - */ -function twig_nl2br($string) -{ - return nl2br($string ?? ''); -} - -/** - * Removes whitespaces between HTML tags. - * - * @param string|null $string - * - * @return string - */ -function twig_spaceless($content) -{ - return trim(preg_replace('/>\s+<', $content ?? '')); -} - -/** - * @param string|null $string - * @param string $to - * @param string $from - * - * @return string - */ -function twig_convert_encoding($string, $to, $from) -{ - if (!\function_exists('iconv')) { - throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); + /** + * Converts a string to lowercase. + * + * @param string|null $string A string + * + * @internal + */ + public static function lower(string $charset, $string): string + { + return mb_strtolower($string ?? '', $charset); } - return iconv($from, $to, $string ?? ''); -} - -/** - * Returns the length of a variable. - * - * @param mixed $thing A variable - * - * @return int The length of the value - */ -function twig_length_filter(Environment $env, $thing) -{ - if (null === $thing) { - return 0; + /** + * Strips HTML and PHP tags from a string. + * + * @param string|null $string + * @param string[]|string|null $allowable_tags + * + * @internal + */ + public static function striptags($string, $allowable_tags = null): string + { + return strip_tags($string ?? '', $allowable_tags); } - if (is_scalar($thing)) { - return mb_strlen($thing, $env->getCharset()); - } - - if ($thing instanceof \Countable || \is_array($thing) || $thing instanceof \SimpleXMLElement) { - return \count($thing); - } - - if ($thing instanceof \Traversable) { - return iterator_count($thing); - } - - if (method_exists($thing, '__toString') && !$thing instanceof \Countable) { - return mb_strlen((string) $thing, $env->getCharset()); - } - - return 1; -} - -/** - * Converts a string to uppercase. - * - * @param string|null $string A string - * - * @return string The uppercased string - */ -function twig_upper_filter(Environment $env, $string) -{ - return mb_strtoupper($string ?? '', $env->getCharset()); -} - -/** - * Converts a string to lowercase. - * - * @param string|null $string A string - * - * @return string The lowercased string - */ -function twig_lower_filter(Environment $env, $string) -{ - return mb_strtolower($string ?? '', $env->getCharset()); -} - -/** - * Strips HTML and PHP tags from a string. - * - * @param string|null $string - * @param string[]|string|null $string - * - * @return string - */ -function twig_striptags($string, $allowable_tags = null) -{ - return strip_tags($string ?? '', $allowable_tags); -} - -/** - * Returns a titlecased string. - * - * @param string|null $string A string - * - * @return string The titlecased string - */ -function twig_title_string_filter(Environment $env, $string) -{ - if (null !== $charset = $env->getCharset()) { + /** + * Returns a titlecased string. + * + * @param string|null $string A string + * + * @internal + */ + public static function titleCase(string $charset, $string): string + { return mb_convert_case($string ?? '', \MB_CASE_TITLE, $charset); } - return ucwords(strtolower($string ?? '')); -} + /** + * Returns a capitalized string. + * + * @param string|null $string A string + * + * @internal + */ + public static function capitalize(string $charset, $string): string + { + return mb_strtoupper(mb_substr($string ?? '', 0, 1, $charset), $charset).mb_strtolower(mb_substr($string ?? '', 1, null, $charset), $charset); + } -/** - * Returns a capitalized string. - * - * @param string|null $string A string - * - * @return string The capitalized string - */ -function twig_capitalize_string_filter(Environment $env, $string) -{ - $charset = $env->getCharset(); + /** + * @internal + */ + public static function callMacro(Template $template, string $method, array $args, int $lineno, array $context, Source $source) + { + if (!method_exists($template, $method)) { + $parent = $template; + while ($parent = $parent->getParent($context)) { + if (method_exists($parent, $method)) { + return $parent->$method(...$args); + } + } - return mb_strtoupper(mb_substr($string ?? '', 0, 1, $charset), $charset).mb_strtolower(mb_substr($string ?? '', 1, null, $charset), $charset); -} + throw new RuntimeError(\sprintf('Macro "%s" is not defined in template "%s".', substr($method, \strlen('macro_')), $template->getTemplateName()), $lineno, $source); + } -/** - * @internal - */ -function twig_call_macro(Template $template, string $method, array $args, int $lineno, array $context, Source $source) -{ - if (!method_exists($template, $method)) { - $parent = $template; - while ($parent = $parent->getParent($context)) { - if (method_exists($parent, $method)) { - return $parent->$method(...$args); + return $template->$method(...$args); + } + + /** + * @template TSequence + * + * @param TSequence $seq + * + * @return ($seq is iterable ? TSequence : array{}) + * + * @internal + */ + public static function ensureTraversable($seq) + { + if (is_iterable($seq)) { + return $seq; + } + + return []; + } + + /** + * @internal + */ + public static function toArray($seq, $preserveKeys = true) + { + if ($seq instanceof \Traversable) { + return iterator_to_array($seq, $preserveKeys); + } + + if (!\is_array($seq)) { + return $seq; + } + + return $preserveKeys ? $seq : array_values($seq); + } + + /** + * Checks if a variable is empty. + * + * {# evaluates to true if the foo variable is null, false, or the empty string #} + * {% if foo is empty %} + * {# ... #} + * {% endif %} + * + * @param mixed $value A variable + * + * @internal + */ + public static function testEmpty($value): bool + { + if ($value instanceof \Countable) { + return 0 === \count($value); + } + + if ($value instanceof \Traversable) { + return !iterator_count($value); + } + + if ($value instanceof \Stringable) { + return '' === (string) $value; + } + + return '' === $value || false === $value || null === $value || [] === $value; + } + + /** + * Checks if a variable is a sequence. + * + * {# evaluates to true if the foo variable is a sequence #} + * {% if foo is sequence %} + * {# ... #} + * {% endif %} + * + * @param mixed $value + * + * @internal + */ + public static function testSequence($value): bool + { + if ($value instanceof \ArrayObject) { + $value = $value->getArrayCopy(); + } + + if ($value instanceof \Traversable) { + $value = iterator_to_array($value); + } + + return \is_array($value) && array_is_list($value); + } + + /** + * Checks if a variable is a mapping. + * + * {# evaluates to true if the foo variable is a mapping #} + * {% if foo is mapping %} + * {# ... #} + * {% endif %} + * + * @param mixed $value + * + * @internal + */ + public static function testMapping($value): bool + { + if ($value instanceof \ArrayObject) { + $value = $value->getArrayCopy(); + } + + if ($value instanceof \Traversable) { + $value = iterator_to_array($value); + } + + return (\is_array($value) && !array_is_list($value)) || \is_object($value); + } + + /** + * Renders a template. + * + * @param array $context + * @param string|array|TemplateWrapper $template The template to render or an array of templates to try consecutively + * @param array $variables The variables to pass to the template + * @param bool $withContext + * @param bool $ignoreMissing Whether to ignore missing templates or not + * @param bool $sandboxed Whether to sandbox the template or not + * + * @internal + */ + public static function include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false): string + { + $alreadySandboxed = false; + $sandbox = null; + if ($withContext) { + $variables = array_merge($context, $variables); + } + + if ($isSandboxed = $sandboxed && $env->hasExtension(SandboxExtension::class)) { + $sandbox = $env->getExtension(SandboxExtension::class); + if (!$alreadySandboxed = $sandbox->isSandboxed()) { + $sandbox->enableSandbox(); } } - throw new RuntimeError(sprintf('Macro "%s" is not defined in template "%s".', substr($method, \strlen('macro_')), $template->getTemplateName()), $lineno, $source); - } - - return $template->$method(...$args); -} - -/** - * @internal - */ -function twig_ensure_traversable($seq) -{ - if ($seq instanceof \Traversable || \is_array($seq)) { - return $seq; - } - - return []; -} - -/** - * @internal - */ -function twig_to_array($seq, $preserveKeys = true) -{ - if ($seq instanceof \Traversable) { - return iterator_to_array($seq, $preserveKeys); - } - - if (!\is_array($seq)) { - return $seq; - } - - return $preserveKeys ? $seq : array_values($seq); -} - -/** - * Checks if a variable is empty. - * - * {# evaluates to true if the foo variable is null, false, or the empty string #} - * {% if foo is empty %} - * {# ... #} - * {% endif %} - * - * @param mixed $value A variable - * - * @return bool true if the value is empty, false otherwise - */ -function twig_test_empty($value) -{ - if ($value instanceof \Countable) { - return 0 === \count($value); - } - - if ($value instanceof \Traversable) { - return !iterator_count($value); - } - - if (\is_object($value) && method_exists($value, '__toString')) { - return '' === (string) $value; - } - - return '' === $value || false === $value || null === $value || [] === $value; -} - -/** - * Checks if a variable is traversable. - * - * {# evaluates to true if the foo variable is an array or a traversable object #} - * {% if foo is iterable %} - * {# ... #} - * {% endif %} - * - * @param mixed $value A variable - * - * @return bool true if the value is traversable - */ -function twig_test_iterable($value) -{ - return $value instanceof \Traversable || \is_array($value); -} - -/** - * Renders a template. - * - * @param array $context - * @param string|array $template The template to render or an array of templates to try consecutively - * @param array $variables The variables to pass to the template - * @param bool $withContext - * @param bool $ignoreMissing Whether to ignore missing templates or not - * @param bool $sandboxed Whether to sandbox the template or not - * - * @return string The rendered template - */ -function twig_include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false) -{ - $alreadySandboxed = false; - $sandbox = null; - if ($withContext) { - $variables = array_merge($context, $variables); - } - - if ($isSandboxed = $sandboxed && $env->hasExtension(SandboxExtension::class)) { - $sandbox = $env->getExtension(SandboxExtension::class); - if (!$alreadySandboxed = $sandbox->isSandboxed()) { - $sandbox->enableSandbox(); - } - - foreach ((\is_array($template) ? $template : [$template]) as $name) { - // if a Template instance is passed, it might have been instantiated outside of a sandbox, check security - if ($name instanceof TemplateWrapper || $name instanceof Template) { - $name->unwrap()->checkSecurity(); - } - } - } - - try { - $loaded = null; try { - $loaded = $env->resolveTemplate($template); + $loaded = null; + try { + $loaded = $env->resolveTemplate($template); + } catch (LoaderError $e) { + if (!$ignoreMissing) { + throw $e; + } + + return ''; + } + + if ($isSandboxed) { + $loaded->unwrap()->checkSecurity(); + } + + return $loaded->render($variables); + } finally { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + } + } + + /** + * Returns a template content without rendering it. + * + * @param string $name The template name + * @param bool $ignoreMissing Whether to ignore missing templates or not + * + * @internal + */ + public static function source(Environment $env, $name, $ignoreMissing = false): string + { + $loader = $env->getLoader(); + try { + return $loader->getSourceContext($name)->getCode(); } catch (LoaderError $e) { if (!$ignoreMissing) { throw $e; } - } - return $loaded ? $loaded->render($variables) : ''; - } finally { - if ($isSandboxed && !$alreadySandboxed) { - $sandbox->disableSandbox(); - } - } -} - -/** - * Returns a template content without rendering it. - * - * @param string $name The template name - * @param bool $ignoreMissing Whether to ignore missing templates or not - * - * @return string The template source - */ -function twig_source(Environment $env, $name, $ignoreMissing = false) -{ - $loader = $env->getLoader(); - try { - return $loader->getSourceContext($name)->getCode(); - } catch (LoaderError $e) { - if (!$ignoreMissing) { - throw $e; - } - } -} - -/** - * Provides the ability to get constants from instances as well as class/global constants. - * - * @param string $constant The name of the constant - * @param object|null $object The object to get the constant from - * - * @return string - */ -function twig_constant($constant, $object = null) -{ - if (null !== $object) { - if ('class' === $constant) { - return \get_class($object); - } - - $constant = \get_class($object).'::'.$constant; - } - - return \constant($constant); -} - -/** - * Checks if a constant exists. - * - * @param string $constant The name of the constant - * @param object|null $object The object to get the constant from - * - * @return bool - */ -function twig_constant_is_defined($constant, $object = null) -{ - if (null !== $object) { - if ('class' === $constant) { - return true; - } - - $constant = \get_class($object).'::'.$constant; - } - - return \defined($constant); -} - -/** - * Batches item. - * - * @param array $items An array of items - * @param int $size The size of the batch - * @param mixed $fill A value used to fill missing items - * - * @return array - */ -function twig_array_batch($items, $size, $fill = null, $preserveKeys = true) -{ - if (!twig_test_iterable($items)) { - throw new RuntimeError(sprintf('The "batch" filter expects an array or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items))); - } - - $size = ceil($size); - - $result = array_chunk(twig_to_array($items, $preserveKeys), $size, $preserveKeys); - - if (null !== $fill && $result) { - $last = \count($result) - 1; - if ($fillCount = $size - \count($result[$last])) { - for ($i = 0; $i < $fillCount; ++$i) { - $result[$last][] = $fill; - } + return ''; } } - return $result; -} + /** + * Returns the list of cases of the enum. + * + * @template T of \UnitEnum + * + * @param class-string $enum + * + * @return list + * + * @internal + */ + public static function enumCases(string $enum): array + { + if (!enum_exists($enum)) { + throw new RuntimeError(\sprintf('Enum "%s" does not exist.', $enum)); + } -/** - * Returns the attribute value for a given array/object. - * - * @param mixed $object The object or array from where to get the item - * @param mixed $item The item to get from the array or object - * @param array $arguments An array of arguments to pass if the item is an object method - * @param string $type The type of attribute (@see \Twig\Template constants) - * @param bool $isDefinedTest Whether this is only a defined check - * @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not - * @param int $lineno The template line where the attribute was called - * - * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true - * - * @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false - * - * @internal - */ -function twig_get_attribute(Environment $env, Source $source, $object, $item, array $arguments = [], $type = /* Template::ANY_CALL */ 'any', $isDefinedTest = false, $ignoreStrictCheck = false, $sandboxed = false, int $lineno = -1) -{ - // array - if (/* Template::METHOD_CALL */ 'method' !== $type) { - $arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item; + return $enum::cases(); + } - if (((\is_array($object) || $object instanceof \ArrayObject) && (isset($object[$arrayItem]) || \array_key_exists($arrayItem, (array) $object))) - || ($object instanceof ArrayAccess && isset($object[$arrayItem])) - ) { - if ($isDefinedTest) { - return true; + /** + * Provides the ability to get constants from instances as well as class/global constants. + * + * @param string $constant The name of the constant + * @param object|null $object The object to get the constant from + * @param bool $checkDefined Whether to check if the constant is defined or not + * + * @return mixed Class constants can return many types like scalars, arrays, and + * objects depending on the PHP version (\BackedEnum, \UnitEnum, etc.) + * When $checkDefined is true, returns true when the constant is defined, false otherwise + * + * @internal + */ + public static function constant($constant, $object = null, bool $checkDefined = false) + { + if (null !== $object) { + if ('class' === $constant) { + return $checkDefined ? true : \get_class($object); } - return $object[$arrayItem]; + $constant = \get_class($object).'::'.$constant; } - if (/* Template::ARRAY_CALL */ 'array' === $type || !\is_object($object)) { + if (!\defined($constant)) { + if ($checkDefined) { + return false; + } + + if ('::class' === strtolower(substr($constant, -7))) { + throw new RuntimeError(\sprintf('You cannot use the Twig function "constant()" to access "%s". You could provide an object and call constant("class", $object) or use the class name directly as a string.', $constant)); + } + + throw new RuntimeError(\sprintf('Constant "%s" is undefined.', $constant)); + } + + return $checkDefined ? true : \constant($constant); + } + + /** + * Batches item. + * + * @param array $items An array of items + * @param int $size The size of the batch + * @param mixed $fill A value used to fill missing items + * + * @internal + */ + public static function batch($items, $size, $fill = null, $preserveKeys = true): array + { + if (!is_iterable($items)) { + throw new RuntimeError(\sprintf('The "batch" filter expects a sequence/mapping or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items))); + } + + $size = (int) ceil($size); + + $result = array_chunk(self::toArray($items, $preserveKeys), $size, $preserveKeys); + + if (null !== $fill && $result) { + $last = \count($result) - 1; + if ($fillCount = $size - \count($result[$last])) { + for ($i = 0; $i < $fillCount; ++$i) { + $result[$last][] = $fill; + } + } + } + + return $result; + } + + /** + * Returns the attribute value for a given array/object. + * + * @param mixed $object The object or array from where to get the item + * @param mixed $item The item to get from the array or object + * @param array $arguments An array of arguments to pass if the item is an object method + * @param string $type The type of attribute (@see \Twig\Template constants) + * @param bool $isDefinedTest Whether this is only a defined check + * @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not + * @param int $lineno The template line where the attribute was called + * + * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true + * + * @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false + * + * @internal + */ + public static function getAttribute(Environment $env, Source $source, $object, $item, array $arguments = [], $type = Template::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false, $sandboxed = false, int $lineno = -1) + { + // array + if (Template::METHOD_CALL !== $type) { + $arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item; + + if (((\is_array($object) || $object instanceof \ArrayObject) && (isset($object[$arrayItem]) || \array_key_exists($arrayItem, (array) $object))) + || ($object instanceof \ArrayAccess && isset($object[$arrayItem])) + ) { + if ($isDefinedTest) { + return true; + } + + return $object[$arrayItem]; + } + + if (Template::ARRAY_CALL === $type || !\is_object($object)) { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$env->isStrictVariables()) { + return; + } + + if ($object instanceof \ArrayAccess) { + $message = \sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, \get_class($object)); + } elseif (\is_object($object)) { + $message = \sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, \get_class($object)); + } elseif (\is_array($object)) { + if (empty($object)) { + $message = \sprintf('Key "%s" does not exist as the sequence/mapping is empty.', $arrayItem); + } else { + $message = \sprintf('Key "%s" for sequence/mapping with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); + } + } elseif (Template::ARRAY_CALL === $type) { + if (null === $object) { + $message = \sprintf('Impossible to access a key ("%s") on a null variable.', $item); + } else { + $message = \sprintf('Impossible to access a key ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + } elseif (null === $object) { + $message = \sprintf('Impossible to access an attribute ("%s") on a null variable.', $item); + } else { + $message = \sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + + throw new RuntimeError($message, $lineno, $source); + } + } + + if (!\is_object($object)) { if ($isDefinedTest) { return false; } @@ -1463,233 +1645,326 @@ function twig_get_attribute(Environment $env, Source $source, $object, $item, ar return; } - if ($object instanceof ArrayAccess) { - $message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, \get_class($object)); - } elseif (\is_object($object)) { - $message = sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, \get_class($object)); + if (null === $object) { + $message = \sprintf('Impossible to invoke a method ("%s") on a null variable.', $item); } elseif (\is_array($object)) { - if (empty($object)) { - $message = sprintf('Key "%s" does not exist as the array is empty.', $arrayItem); - } else { - $message = sprintf('Key "%s" for array with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); - } - } elseif (/* Template::ARRAY_CALL */ 'array' === $type) { - if (null === $object) { - $message = sprintf('Impossible to access a key ("%s") on a null variable.', $item); - } else { - $message = sprintf('Impossible to access a key ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); - } - } elseif (null === $object) { - $message = sprintf('Impossible to access an attribute ("%s") on a null variable.', $item); + $message = \sprintf('Impossible to invoke a method ("%s") on a sequence/mapping.', $item); } else { - $message = sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + $message = \sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); } throw new RuntimeError($message, $lineno, $source); } - } - if (!\is_object($object)) { - if ($isDefinedTest) { - return false; + if ($object instanceof Template) { + throw new RuntimeError('Accessing \Twig\Template attributes is forbidden.', $lineno, $source); } - if ($ignoreStrictCheck || !$env->isStrictVariables()) { - return; - } + // object property + if (Template::METHOD_CALL !== $type) { + if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) { + if ($isDefinedTest) { + return true; + } - if (null === $object) { - $message = sprintf('Impossible to invoke a method ("%s") on a null variable.', $item); - } elseif (\is_array($object)) { - $message = sprintf('Impossible to invoke a method ("%s") on an array.', $item); - } else { - $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); - } + if ($sandboxed) { + $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $item, $lineno, $source); + } - throw new RuntimeError($message, $lineno, $source); - } - - if ($object instanceof Template) { - throw new RuntimeError('Accessing \Twig\Template attributes is forbidden.', $lineno, $source); - } - - // object property - if (/* Template::METHOD_CALL */ 'method' !== $type) { - if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) { - if ($isDefinedTest) { - return true; + return $object->$item; } - - if ($sandboxed) { - $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $item, $lineno, $source); - } - - return $object->$item; } - } - static $cache = []; + static $cache = []; - $class = \get_class($object); + $class = \get_class($object); - // object method - // precedence: getXxx() > isXxx() > hasXxx() - if (!isset($cache[$class])) { - $methods = get_class_methods($object); - sort($methods); - $lcMethods = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, $methods); - $classCache = []; - foreach ($methods as $i => $method) { - $classCache[$method] = $method; - $classCache[$lcName = $lcMethods[$i]] = $method; + // object method + // precedence: getXxx() > isXxx() > hasXxx() + if (!isset($cache[$class])) { + $methods = get_class_methods($object); + sort($methods); + $lcMethods = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, $methods); + $classCache = []; + foreach ($methods as $i => $method) { + $classCache[$method] = $method; + $classCache[$lcName = $lcMethods[$i]] = $method; - if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) { - $name = substr($method, 3); - $lcName = substr($lcName, 3); - } elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) { - $name = substr($method, 2); - $lcName = substr($lcName, 2); - } elseif ('h' === $lcName[0] && 0 === strpos($lcName, 'has')) { - $name = substr($method, 3); - $lcName = substr($lcName, 3); - if (\in_array('is'.$lcName, $lcMethods)) { + if ('g' === $lcName[0] && str_starts_with($lcName, 'get')) { + $name = substr($method, 3); + $lcName = substr($lcName, 3); + } elseif ('i' === $lcName[0] && str_starts_with($lcName, 'is')) { + $name = substr($method, 2); + $lcName = substr($lcName, 2); + } elseif ('h' === $lcName[0] && str_starts_with($lcName, 'has')) { + $name = substr($method, 3); + $lcName = substr($lcName, 3); + if (\in_array('is'.$lcName, $lcMethods)) { + continue; + } + } else { continue; } - } else { - continue; - } - // skip get() and is() methods (in which case, $name is empty) - if ($name) { - if (!isset($classCache[$name])) { - $classCache[$name] = $method; - } + // skip get() and is() methods (in which case, $name is empty) + if ($name) { + if (!isset($classCache[$name])) { + $classCache[$name] = $method; + } - if (!isset($classCache[$lcName])) { - $classCache[$lcName] = $method; + if (!isset($classCache[$lcName])) { + $classCache[$lcName] = $method; + } } } + $cache[$class] = $classCache; + } + + $call = false; + if (isset($cache[$class][$item])) { + $method = $cache[$class][$item]; + } elseif (isset($cache[$class][$lcItem = strtr($item, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')])) { + $method = $cache[$class][$lcItem]; + } elseif (isset($cache[$class]['__call'])) { + $method = $item; + $call = true; + } else { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$env->isStrictVariables()) { + return; + } + + throw new RuntimeError(\sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()"/"has%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), $lineno, $source); } - $cache[$class] = $classCache; - } - $call = false; - if (isset($cache[$class][$item])) { - $method = $cache[$class][$item]; - } elseif (isset($cache[$class][$lcItem = strtr($item, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')])) { - $method = $cache[$class][$lcItem]; - } elseif (isset($cache[$class]['__call'])) { - $method = $item; - $call = true; - } else { if ($isDefinedTest) { - return false; + return true; } - if ($ignoreStrictCheck || !$env->isStrictVariables()) { - return; + if ($sandboxed) { + $env->getExtension(SandboxExtension::class)->checkMethodAllowed($object, $method, $lineno, $source); } - throw new RuntimeError(sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()"/"has%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), $lineno, $source); + // Some objects throw exceptions when they have __call, and the method we try + // to call is not supported. If ignoreStrictCheck is true, we should return null. + try { + $ret = $object->$method(...$arguments); + } catch (\BadMethodCallException $e) { + if ($call && ($ignoreStrictCheck || !$env->isStrictVariables())) { + return; + } + throw $e; + } + + return $ret; } - if ($isDefinedTest) { + /** + * Returns the values from a single column in the input array. + * + *
+     *  {% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %}
+     *
+     *  {% set fruits = items|column('fruit') %}
+     *
+     *  {# fruits now contains ['apple', 'orange'] #}
+     * 
+ * + * @param array|\Traversable $array An array + * @param int|string $name The column name + * @param int|string|null $index The column to use as the index/keys for the returned array + * + * @return array The array of values + * + * @internal + */ + public static function column($array, $name, $index = null): array + { + if ($array instanceof \Traversable) { + $array = iterator_to_array($array); + } elseif (!\is_array($array)) { + throw new RuntimeError(\sprintf('The column filter only works with sequences/mappings or "Traversable", got "%s" as first argument.', \gettype($array))); + } + + return array_column($array, $name, $index); + } + + /** + * @internal + */ + public static function filter(Environment $env, $array, $arrow) + { + if (!is_iterable($array)) { + throw new RuntimeError(\sprintf('The "filter" filter expects a sequence/mapping or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array))); + } + + self::checkArrowInSandbox($env, $arrow, 'filter', 'filter'); + + if (\is_array($array)) { + return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); + } + + // the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator + return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow); + } + + /** + * @internal + */ + public static function find(Environment $env, $array, $arrow) + { + self::checkArrowInSandbox($env, $arrow, 'find', 'filter'); + + foreach ($array as $k => $v) { + if ($arrow($v, $k)) { + return $v; + } + } + + return null; + } + + /** + * @internal + */ + public static function map(Environment $env, $array, $arrow) + { + if (!is_iterable($array)) { + throw new RuntimeError(\sprintf('The "map" filter expects a sequence/mapping or "Traversable", got "%s".', get_debug_type($array))); + } + + self::checkArrowInSandbox($env, $arrow, 'map', 'filter'); + + $r = []; + foreach ($array as $k => $v) { + $r[$k] = $arrow($v, $k); + } + + return $r; + } + + /** + * @internal + */ + public static function reduce(Environment $env, $array, $arrow, $initial = null) + { + self::checkArrowInSandbox($env, $arrow, 'reduce', 'filter'); + + if (!\is_array($array) && !$array instanceof \Traversable) { + throw new RuntimeError(\sprintf('The "reduce" filter only works with sequences/mappings or "Traversable", got "%s" as first argument.', \gettype($array))); + } + + $accumulator = $initial; + foreach ($array as $key => $value) { + $accumulator = $arrow($accumulator, $value, $key); + } + + return $accumulator; + } + + /** + * @internal + */ + public static function arraySome(Environment $env, $array, $arrow) + { + self::checkArrowInSandbox($env, $arrow, 'has some', 'operator'); + + foreach ($array as $k => $v) { + if ($arrow($v, $k)) { + return true; + } + } + + return false; + } + + /** + * @internal + */ + public static function arrayEvery(Environment $env, $array, $arrow) + { + self::checkArrowInSandbox($env, $arrow, 'has every', 'operator'); + + foreach ($array as $k => $v) { + if (!$arrow($v, $k)) { + return false; + } + } + return true; } - if ($sandboxed) { - $env->getExtension(SandboxExtension::class)->checkMethodAllowed($object, $method, $lineno, $source); - } - - // Some objects throw exceptions when they have __call, and the method we try - // to call is not supported. If ignoreStrictCheck is true, we should return null. - try { - $ret = $object->$method(...$arguments); - } catch (\BadMethodCallException $e) { - if ($call && ($ignoreStrictCheck || !$env->isStrictVariables())) { - return; + /** + * @internal + */ + public static function checkArrowInSandbox(Environment $env, $arrow, $thing, $type) + { + if (!$arrow instanceof \Closure && $env->hasExtension(SandboxExtension::class) && $env->getExtension(SandboxExtension::class)->isSandboxed()) { + throw new RuntimeError(\sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.', $thing, $type)); } - throw $e; } - return $ret; -} + /** + * @internal to be removed in Twig 4 + */ + public static function captureOutput(iterable $body): string + { + $level = ob_get_level(); + ob_start(); -/** - * Returns the values from a single column in the input array. - * - *
- *  {% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %}
- *
- *  {% set fruits = items|column('fruit') %}
- *
- *  {# fruits now contains ['apple', 'orange'] #}
- * 
- * - * @param array|Traversable $array An array - * @param mixed $name The column name - * @param mixed $index The column to use as the index/keys for the returned array - * - * @return array The array of values - */ -function twig_array_column($array, $name, $index = null): array -{ - if ($array instanceof Traversable) { - $array = iterator_to_array($array); - } elseif (!\is_array($array)) { - throw new RuntimeError(sprintf('The column filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); - } + try { + foreach ($body as $data) { + echo $data; + } + } catch (\Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } - return array_column($array, $name, $index); -} - -function twig_array_filter(Environment $env, $array, $arrow) -{ - if (!twig_test_iterable($array)) { - throw new RuntimeError(sprintf('The "filter" filter expects an array or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array))); - } - - twig_check_arrow_in_sandbox($env, $arrow, 'filter', 'filter'); - - if (\is_array($array)) { - return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); - } - - // the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator - return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow); -} - -function twig_array_map(Environment $env, $array, $arrow) -{ - twig_check_arrow_in_sandbox($env, $arrow, 'map', 'filter'); - - $r = []; - foreach ($array as $k => $v) { - $r[$k] = $arrow($v, $k); - } - - return $r; -} - -function twig_array_reduce(Environment $env, $array, $arrow, $initial = null) -{ - twig_check_arrow_in_sandbox($env, $arrow, 'reduce', 'filter'); - - if (!\is_array($array)) { - if (!$array instanceof \Traversable) { - throw new RuntimeError(sprintf('The "reduce" filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); + throw $e; } - $array = iterator_to_array($array); + return ob_get_clean(); } - return array_reduce($array, $arrow, $initial); -} + /** + * @internal + */ + public static function parseParentFunction(Parser $parser, Node $fakeNode, $args, int $line): AbstractExpression + { + if (!$blockName = $parser->peekBlockStack()) { + throw new SyntaxError('Calling the "parent" function outside of a block is forbidden.', $line, $parser->getStream()->getSourceContext()); + } -function twig_check_arrow_in_sandbox(Environment $env, $arrow, $thing, $type) -{ - if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { - throw new RuntimeError(sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.', $thing, $type)); + if (!$parser->hasInheritance()) { + throw new SyntaxError('Calling the "parent" function on a template that does not call "extends" or "use" is forbidden.', $line, $parser->getStream()->getSourceContext()); + } + + return new ParentExpression($blockName, $line); + } + + /** + * @internal + */ + public static function parseBlockFunction(Parser $parser, Node $fakeNode, $args, int $line): AbstractExpression + { + $fakeFunction = new TwigFunction('block', fn ($name, $template = null) => null); + $args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($args); + + return new BlockReferenceExpression($args[0], $args[1] ?? null, $line); + } + + /** + * @internal + */ + public static function parseAttributeFunction(Parser $parser, Node $fakeNode, $args, int $line): AbstractExpression + { + $fakeFunction = new TwigFunction('attribute', fn ($variable, $attribute, $arguments = null) => null); + $args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($args); + + return new GetAttrExpression($args[0], $args[1], $args[2] ?? null, Template::ANY_CALL, $line); } } -} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/DebugExtension.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/DebugExtension.php index bfb23d7bd..dac21c317 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/DebugExtension.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/DebugExtension.php @@ -9,7 +9,11 @@ * file that was distributed with this source code. */ -namespace Twig\Extension { +namespace Twig\Extension; + +use Twig\Environment; +use Twig\Template; +use Twig\TemplateWrapper; use Twig\TwigFunction; final class DebugExtension extends AbstractExtension @@ -18,47 +22,41 @@ final class DebugExtension extends AbstractExtension { // dump is safe if var_dump is overridden by xdebug $isDumpOutputHtmlSafe = \extension_loaded('xdebug') - // false means that it was not set (and the default is on) or it explicitly enabled - && (false === ini_get('xdebug.overload_var_dump') || ini_get('xdebug.overload_var_dump')) - // false means that it was not set (and the default is on) or it explicitly enabled - // xdebug.overload_var_dump produces HTML only when html_errors is also enabled - && (false === ini_get('html_errors') || ini_get('html_errors')) + // Xdebug overloads var_dump in develop mode when html_errors is enabled + && str_contains(\ini_get('xdebug.mode'), 'develop') + && (false === \ini_get('html_errors') || \ini_get('html_errors')) || 'cli' === \PHP_SAPI ; return [ - new TwigFunction('dump', 'twig_var_dump', ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]), + new TwigFunction('dump', [self::class, 'dump'], ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]), ]; } -} -} -namespace { -use Twig\Environment; -use Twig\Template; -use Twig\TemplateWrapper; - -function twig_var_dump(Environment $env, $context, ...$vars) -{ - if (!$env->isDebug()) { - return; - } - - ob_start(); - - if (!$vars) { - $vars = []; - foreach ($context as $key => $value) { - if (!$value instanceof Template && !$value instanceof TemplateWrapper) { - $vars[$key] = $value; - } + /** + * @internal + */ + public static function dump(Environment $env, $context, ...$vars) + { + if (!$env->isDebug()) { + return; } - var_dump($vars); - } else { - var_dump(...$vars); - } + ob_start(); - return ob_get_clean(); -} + if (!$vars) { + $vars = []; + foreach ($context as $key => $value) { + if (!$value instanceof Template && !$value instanceof TemplateWrapper) { + $vars[$key] = $value; + } + } + + var_dump($vars); + } else { + var_dump(...$vars); + } + + return ob_get_clean(); + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/EscaperExtension.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/EscaperExtension.php index 9d2251dc6..52531c436 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/EscaperExtension.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/EscaperExtension.php @@ -9,22 +9,24 @@ * file that was distributed with this source code. */ -namespace Twig\Extension { +namespace Twig\Extension; + +use Twig\Environment; use Twig\FileExtensionEscapingStrategy; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\Filter\RawFilter; +use Twig\Node\Node; use Twig\NodeVisitor\EscaperNodeVisitor; +use Twig\Runtime\EscaperRuntime; use Twig\TokenParser\AutoEscapeTokenParser; use Twig\TwigFilter; final class EscaperExtension extends AbstractExtension { - private $defaultStrategy; + private $environment; private $escapers = []; - - /** @internal */ - public $safeClasses = []; - - /** @internal */ - public $safeLookup = []; + private $escaper; + private $defaultStrategy; /** * @param string|false|callable $defaultStrategy An escaping strategy @@ -49,19 +51,43 @@ final class EscaperExtension extends AbstractExtension public function getFilters(): array { return [ - new TwigFilter('escape', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), - new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), - new TwigFilter('raw', 'twig_raw_filter', ['is_safe' => ['all']]), + new TwigFilter('escape', [EscaperRuntime::class, 'escape'], ['is_safe_callback' => [self::class, 'escapeFilterIsSafe']]), + new TwigFilter('e', [EscaperRuntime::class, 'escape'], ['is_safe_callback' => [self::class, 'escapeFilterIsSafe']]), + new TwigFilter('raw', null, ['is_safe' => ['all'], 'node_class' => RawFilter::class]), ]; } + /** + * @deprecated since Twig 3.10 + */ + public function setEnvironment(Environment $environment): void + { + $triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true; + if ($triggerDeprecation) { + trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated and not needed if you are using methods from "Twig\Runtime\EscaperRuntime".', __METHOD__); + } + + $this->environment = $environment; + $this->escaper = $environment->getRuntime(EscaperRuntime::class); + } + + /** + * @deprecated since Twig 3.10 + */ + public function setEscaperRuntime(EscaperRuntime $escaper) + { + trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated and not needed if you are using methods from "Twig\Runtime\EscaperRuntime".', __METHOD__); + + $this->escaper = $escaper; + } + /** * Sets the default strategy to use when not defined by the user. * * The strategy can be a valid PHP callback that takes the template * name as an argument and returns the strategy to use. * - * @param string|false|callable $defaultStrategy An escaping strategy + * @param string|false|callable(string $templateName): string $defaultStrategy An escaping strategy */ public function setDefaultStrategy($defaultStrategy): void { @@ -93,324 +119,82 @@ final class EscaperExtension extends AbstractExtension /** * Defines a new escaper to be used via the escape filter. * - * @param string $strategy The strategy name that should be used as a strategy in the escape call - * @param callable $callable A valid PHP callable + * @param string $strategy The strategy name that should be used as a strategy in the escape call + * @param callable(Environment, string, string): string $callable A valid PHP callable + * + * @deprecated since Twig 3.10 */ public function setEscaper($strategy, callable $callable) { + trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::setEscaper()" method instead (be warned that Environment is not passed anymore to the callable).', __METHOD__); + + if (!isset($this->environment)) { + throw new \LogicException(\sprintf('You must call "setEnvironment()" before calling "%s()".', __METHOD__)); + } + $this->escapers[$strategy] = $callable; + $callable = function ($string, $charset) use ($callable) { + return $callable($this->environment, $string, $charset); + }; + + $this->escaper->setEscaper($strategy, $callable); } /** * Gets all defined escapers. * - * @return callable[] An array of escapers + * @return array An array of escapers + * + * @deprecated since Twig 3.10 */ public function getEscapers() { + trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::getEscaper()" method instead.', __METHOD__); + return $this->escapers; } + /** + * @deprecated since Twig 3.10 + */ public function setSafeClasses(array $safeClasses = []) { - $this->safeClasses = []; - $this->safeLookup = []; - foreach ($safeClasses as $class => $strategies) { - $this->addSafeClass($class, $strategies); + trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::setSafeClasses()" method instead.', __METHOD__); + + if (!isset($this->escaper)) { + throw new \LogicException(\sprintf('You must call "setEnvironment()" before calling "%s()".', __METHOD__)); } + + $this->escaper->setSafeClasses($safeClasses); } + /** + * @deprecated since Twig 3.10 + */ public function addSafeClass(string $class, array $strategies) { - $class = ltrim($class, '\\'); - if (!isset($this->safeClasses[$class])) { - $this->safeClasses[$class] = []; - } - $this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies); + trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::addSafeClass()" method instead.', __METHOD__); - foreach ($strategies as $strategy) { - $this->safeLookup[$strategy][$class] = true; - } - } -} -} - -namespace { -use Twig\Environment; -use Twig\Error\RuntimeError; -use Twig\Extension\EscaperExtension; -use Twig\Markup; -use Twig\Node\Expression\ConstantExpression; -use Twig\Node\Node; - -/** - * Marks a variable as being safe. - * - * @param string $string A PHP variable - */ -function twig_raw_filter($string) -{ - return $string; -} - -/** - * Escapes a string. - * - * @param mixed $string The value to be escaped - * @param string $strategy The escaping strategy - * @param string $charset The charset - * @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) - * - * @return string - */ -function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false) -{ - if ($autoescape && $string instanceof Markup) { - return $string; - } - - if (!\is_string($string)) { - if (\is_object($string) && method_exists($string, '__toString')) { - if ($autoescape) { - $c = \get_class($string); - $ext = $env->getExtension(EscaperExtension::class); - if (!isset($ext->safeClasses[$c])) { - $ext->safeClasses[$c] = []; - foreach (class_parents($string) + class_implements($string) as $class) { - if (isset($ext->safeClasses[$class])) { - $ext->safeClasses[$c] = array_unique(array_merge($ext->safeClasses[$c], $ext->safeClasses[$class])); - foreach ($ext->safeClasses[$class] as $s) { - $ext->safeLookup[$s][$c] = true; - } - } - } - } - if (isset($ext->safeLookup[$strategy][$c]) || isset($ext->safeLookup['all'][$c])) { - return (string) $string; - } - } - - $string = (string) $string; - } elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) { - return $string; - } - } - - if ('' === $string) { - return ''; - } - - if (null === $charset) { - $charset = $env->getCharset(); - } - - switch ($strategy) { - case 'html': - // see https://www.php.net/htmlspecialchars - - // Using a static variable to avoid initializing the array - // each time the function is called. Moving the declaration on the - // top of the function slow downs other escaping strategies. - static $htmlspecialcharsCharsets = [ - 'ISO-8859-1' => true, 'ISO8859-1' => true, - 'ISO-8859-15' => true, 'ISO8859-15' => true, - 'utf-8' => true, 'UTF-8' => true, - 'CP866' => true, 'IBM866' => true, '866' => true, - 'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true, - '1251' => true, - 'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true, - 'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true, - 'BIG5' => true, '950' => true, - 'GB2312' => true, '936' => true, - 'BIG5-HKSCS' => true, - 'SHIFT_JIS' => true, 'SJIS' => true, '932' => true, - 'EUC-JP' => true, 'EUCJP' => true, - 'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true, - ]; - - if (isset($htmlspecialcharsCharsets[$charset])) { - return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset); - } - - if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) { - // cache the lowercase variant for future iterations - $htmlspecialcharsCharsets[$charset] = true; - - return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset); - } - - $string = twig_convert_encoding($string, 'UTF-8', $charset); - $string = htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'); - - return iconv('UTF-8', $charset, $string); - - case 'js': - // escape all non-alphanumeric characters - // into their \x or \uHHHH representations - if ('UTF-8' !== $charset) { - $string = twig_convert_encoding($string, 'UTF-8', $charset); - } - - if (!preg_match('//u', $string)) { - throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); - } - - $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) { - $char = $matches[0]; - - /* - * A few characters have short escape sequences in JSON and JavaScript. - * Escape sequences supported only by JavaScript, not JSON, are omitted. - * \" is also supported but omitted, because the resulting string is not HTML safe. - */ - static $shortMap = [ - '\\' => '\\\\', - '/' => '\\/', - "\x08" => '\b', - "\x0C" => '\f', - "\x0A" => '\n', - "\x0D" => '\r', - "\x09" => '\t', - ]; - - if (isset($shortMap[$char])) { - return $shortMap[$char]; - } - - $codepoint = mb_ord($char, 'UTF-8'); - if (0x10000 > $codepoint) { - return sprintf('\u%04X', $codepoint); - } - - // Split characters outside the BMP into surrogate pairs - // https://tools.ietf.org/html/rfc2781.html#section-2.1 - $u = $codepoint - 0x10000; - $high = 0xD800 | ($u >> 10); - $low = 0xDC00 | ($u & 0x3FF); - - return sprintf('\u%04X\u%04X', $high, $low); - }, $string); - - if ('UTF-8' !== $charset) { - $string = iconv('UTF-8', $charset, $string); - } - - return $string; - - case 'css': - if ('UTF-8' !== $charset) { - $string = twig_convert_encoding($string, 'UTF-8', $charset); - } - - if (!preg_match('//u', $string)) { - throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); - } - - $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) { - $char = $matches[0]; - - return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8')); - }, $string); - - if ('UTF-8' !== $charset) { - $string = iconv('UTF-8', $charset, $string); - } - - return $string; - - case 'html_attr': - if ('UTF-8' !== $charset) { - $string = twig_convert_encoding($string, 'UTF-8', $charset); - } - - if (!preg_match('//u', $string)) { - throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); - } - - $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) { - /** - * This function is adapted from code coming from Zend Framework. - * - * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com) - * @license https://framework.zend.com/license/new-bsd New BSD License - */ - $chr = $matches[0]; - $ord = \ord($chr); - - /* - * The following replaces characters undefined in HTML with the - * hex entity for the Unicode replacement character. - */ - if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) { - return '�'; - } - - /* - * Check if the current character to escape has a name entity we should - * replace it with while grabbing the hex value of the character. - */ - if (1 === \strlen($chr)) { - /* - * While HTML supports far more named entities, the lowest common denominator - * has become HTML5's XML Serialisation which is restricted to the those named - * entities that XML supports. Using HTML entities would result in this error: - * XML Parsing Error: undefined entity - */ - static $entityMap = [ - 34 => '"', /* quotation mark */ - 38 => '&', /* ampersand */ - 60 => '<', /* less-than sign */ - 62 => '>', /* greater-than sign */ - ]; - - if (isset($entityMap[$ord])) { - return $entityMap[$ord]; - } - - return sprintf('&#x%02X;', $ord); - } - - /* - * Per OWASP recommendations, we'll use hex entities for any other - * characters where a named entity does not exist. - */ - return sprintf('&#x%04X;', mb_ord($chr, 'UTF-8')); - }, $string); - - if ('UTF-8' !== $charset) { - $string = iconv('UTF-8', $charset, $string); - } - - return $string; - - case 'url': - return rawurlencode($string); - - default: - $escapers = $env->getExtension(EscaperExtension::class)->getEscapers(); - if (array_key_exists($strategy, $escapers)) { - return $escapers[$strategy]($env, $string, $charset); - } - - $validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers))); - - throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies)); - } -} - -/** - * @internal - */ -function twig_escape_filter_is_safe(Node $filterArgs) -{ - foreach ($filterArgs as $arg) { - if ($arg instanceof ConstantExpression) { - return [$arg->getAttribute('value')]; + if (!isset($this->escaper)) { + throw new \LogicException(\sprintf('You must call "setEnvironment()" before calling "%s()".', __METHOD__)); } - return []; + $this->escaper->addSafeClass($class, $strategies); } - return ['html']; -} + /** + * @internal + */ + public static function escapeFilterIsSafe(Node $filterArgs) + { + foreach ($filterArgs as $arg) { + if ($arg instanceof ConstantExpression) { + return [$arg->getAttribute('value')]; + } + + return []; + } + + return ['html']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/ExtensionInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/ExtensionInterface.php index 75fa237e1..10a42b6b1 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/ExtensionInterface.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/ExtensionInterface.php @@ -11,6 +11,8 @@ namespace Twig\Extension; +use Twig\ExpressionParser; +use Twig\Node\Expression\AbstractExpression; use Twig\NodeVisitor\NodeVisitorInterface; use Twig\TokenParser\TokenParserInterface; use Twig\TwigFilter; @@ -63,6 +65,11 @@ interface ExtensionInterface * Returns a list of operators to add to the existing list. * * @return array First array of unary operators, second array of binary operators + * + * @psalm-return array{ + * array}>, + * array, associativity: ExpressionParser::OPERATOR_*}> + * } */ public function getOperators(); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/GlobalsInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/GlobalsInterface.php index ec0c68292..d52cd107e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/GlobalsInterface.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/GlobalsInterface.php @@ -12,14 +12,14 @@ namespace Twig\Extension; /** - * Enables usage of the deprecated Twig\Extension\AbstractExtension::getGlobals() method. - * - * Explicitly implement this interface if you really need to implement the - * deprecated getGlobals() method in your extensions. + * Allows Twig extensions to add globals to the context. * * @author Fabien Potencier */ interface GlobalsInterface { + /** + * @return array + */ public function getGlobals(): array; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/OptimizerExtension.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/OptimizerExtension.php index 965bfdb04..d3fe46a67 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/OptimizerExtension.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/OptimizerExtension.php @@ -15,11 +15,9 @@ use Twig\NodeVisitor\OptimizerNodeVisitor; final class OptimizerExtension extends AbstractExtension { - private $optimizers; - - public function __construct(int $optimizers = -1) - { - $this->optimizers = $optimizers; + public function __construct( + private int $optimizers = -1, + ) { } public function getNodeVisitors(): array diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/SandboxExtension.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/SandboxExtension.php index c861159b6..4e96760f7 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/SandboxExtension.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/SandboxExtension.php @@ -15,6 +15,7 @@ use Twig\NodeVisitor\SandboxNodeVisitor; use Twig\Sandbox\SecurityNotAllowedMethodError; use Twig\Sandbox\SecurityNotAllowedPropertyError; use Twig\Sandbox\SecurityPolicyInterface; +use Twig\Sandbox\SourcePolicyInterface; use Twig\Source; use Twig\TokenParser\SandboxTokenParser; @@ -23,11 +24,13 @@ final class SandboxExtension extends AbstractExtension private $sandboxedGlobally; private $sandboxed; private $policy; + private $sourcePolicy; - public function __construct(SecurityPolicyInterface $policy, $sandboxed = false) + public function __construct(SecurityPolicyInterface $policy, $sandboxed = false, ?SourcePolicyInterface $sourcePolicy = null) { $this->policy = $policy; $this->sandboxedGlobally = $sandboxed; + $this->sourcePolicy = $sourcePolicy; } public function getTokenParsers(): array @@ -50,9 +53,9 @@ final class SandboxExtension extends AbstractExtension $this->sandboxed = false; } - public function isSandboxed(): bool + public function isSandboxed(?Source $source = null): bool { - return $this->sandboxedGlobally || $this->sandboxed; + return $this->sandboxedGlobally || $this->sandboxed || $this->isSourceSandboxed($source); } public function isSandboxedGlobally(): bool @@ -60,6 +63,15 @@ final class SandboxExtension extends AbstractExtension return $this->sandboxedGlobally; } + private function isSourceSandboxed(?Source $source): bool + { + if (null === $source || null === $this->sourcePolicy) { + return false; + } + + return $this->sourcePolicy->enableSandbox($source); + } + public function setSecurityPolicy(SecurityPolicyInterface $policy) { $this->policy = $policy; @@ -70,16 +82,16 @@ final class SandboxExtension extends AbstractExtension return $this->policy; } - public function checkSecurity($tags, $filters, $functions): void + public function checkSecurity($tags, $filters, $functions, ?Source $source = null): void { - if ($this->isSandboxed()) { + if ($this->isSandboxed($source)) { $this->policy->checkSecurity($tags, $filters, $functions); } } - public function checkMethodAllowed($obj, $method, int $lineno = -1, Source $source = null): void + public function checkMethodAllowed($obj, $method, int $lineno = -1, ?Source $source = null): void { - if ($this->isSandboxed()) { + if ($this->isSandboxed($source)) { try { $this->policy->checkMethodAllowed($obj, $method); } catch (SecurityNotAllowedMethodError $e) { @@ -91,9 +103,9 @@ final class SandboxExtension extends AbstractExtension } } - public function checkPropertyAllowed($obj, $property, int $lineno = -1, Source $source = null): void + public function checkPropertyAllowed($obj, $property, int $lineno = -1, ?Source $source = null): void { - if ($this->isSandboxed()) { + if ($this->isSandboxed($source)) { try { $this->policy->checkPropertyAllowed($obj, $property); } catch (SecurityNotAllowedPropertyError $e) { @@ -105,9 +117,9 @@ final class SandboxExtension extends AbstractExtension } } - public function ensureToStringAllowed($obj, int $lineno = -1, Source $source = null) + public function ensureToStringAllowed($obj, int $lineno = -1, ?Source $source = null) { - if ($this->isSandboxed() && \is_object($obj) && method_exists($obj, '__toString')) { + if ($this->isSandboxed($source) && $obj instanceof \Stringable) { try { $this->policy->checkMethodAllowed($obj, '__toString'); } catch (SecurityNotAllowedMethodError $e) { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/StagingExtension.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/StagingExtension.php index 0ea47f90c..59db2ca7d 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/StagingExtension.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/StagingExtension.php @@ -35,7 +35,7 @@ final class StagingExtension extends AbstractExtension public function addFunction(TwigFunction $function): void { if (isset($this->functions[$function->getName()])) { - throw new \LogicException(sprintf('Function "%s" is already registered.', $function->getName())); + throw new \LogicException(\sprintf('Function "%s" is already registered.', $function->getName())); } $this->functions[$function->getName()] = $function; @@ -49,7 +49,7 @@ final class StagingExtension extends AbstractExtension public function addFilter(TwigFilter $filter): void { if (isset($this->filters[$filter->getName()])) { - throw new \LogicException(sprintf('Filter "%s" is already registered.', $filter->getName())); + throw new \LogicException(\sprintf('Filter "%s" is already registered.', $filter->getName())); } $this->filters[$filter->getName()] = $filter; @@ -73,7 +73,7 @@ final class StagingExtension extends AbstractExtension public function addTokenParser(TokenParserInterface $parser): void { if (isset($this->tokenParsers[$parser->getTag()])) { - throw new \LogicException(sprintf('Tag "%s" is already registered.', $parser->getTag())); + throw new \LogicException(\sprintf('Tag "%s" is already registered.', $parser->getTag())); } $this->tokenParsers[$parser->getTag()] = $parser; @@ -87,7 +87,7 @@ final class StagingExtension extends AbstractExtension public function addTest(TwigTest $test): void { if (isset($this->tests[$test->getName()])) { - throw new \LogicException(sprintf('Test "%s" is already registered.', $test->getName())); + throw new \LogicException(\sprintf('Test "%s" is already registered.', $test->getName())); } $this->tests[$test->getName()] = $test; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/StringLoaderExtension.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/StringLoaderExtension.php index 7b4514710..698d181f1 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/StringLoaderExtension.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/StringLoaderExtension.php @@ -9,7 +9,10 @@ * file that was distributed with this source code. */ -namespace Twig\Extension { +namespace Twig\Extension; + +use Twig\Environment; +use Twig\TemplateWrapper; use Twig\TwigFunction; final class StringLoaderExtension extends AbstractExtension @@ -17,26 +20,21 @@ final class StringLoaderExtension extends AbstractExtension public function getFunctions(): array { return [ - new TwigFunction('template_from_string', 'twig_template_from_string', ['needs_environment' => true]), + new TwigFunction('template_from_string', [self::class, 'templateFromString'], ['needs_environment' => true]), ]; } -} -} -namespace { -use Twig\Environment; -use Twig\TemplateWrapper; - -/** - * Loads a template from a string. - * - * {{ include(template_from_string("Hello {{ name }}")) }} - * - * @param string $template A template as a string or object implementing __toString() - * @param string $name An optional name of the template to be used in error messages - */ -function twig_template_from_string(Environment $env, $template, string $name = null): TemplateWrapper -{ - return $env->createTemplate((string) $template, $name); -} + /** + * Loads a template from a string. + * + * {{ include(template_from_string("Hello {{ name }}")) }} + * + * @param string|null $name An optional name of the template to be used in error messages + * + * @internal + */ + public static function templateFromString(Environment $env, string|\Stringable $template, ?string $name = null): TemplateWrapper + { + return $env->createTemplate((string) $template, $name); + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/YieldNotReadyExtension.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/YieldNotReadyExtension.php new file mode 100644 index 000000000..49dfb8085 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/YieldNotReadyExtension.php @@ -0,0 +1,30 @@ +useYield)]; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExtensionSet.php b/data/web/inc/lib/vendor/twig/twig/src/ExtensionSet.php index 36e5bbc59..28d57a41c 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/ExtensionSet.php +++ b/data/web/inc/lib/vendor/twig/twig/src/ExtensionSet.php @@ -15,6 +15,9 @@ use Twig\Error\RuntimeError; use Twig\Extension\ExtensionInterface; use Twig\Extension\GlobalsInterface; use Twig\Extension\StagingExtension; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\Binary\AbstractBinary; +use Twig\Node\Expression\Unary\AbstractUnary; use Twig\NodeVisitor\NodeVisitorInterface; use Twig\TokenParser\TokenParserInterface; @@ -31,11 +34,23 @@ final class ExtensionSet private $staging; private $parsers; private $visitors; + /** @var array */ private $filters; + /** @var array */ + private $dynamicFilters; + /** @var array */ private $tests; + /** @var array */ + private $dynamicTests; + /** @var array */ private $functions; + /** @var array */ + private $dynamicFunctions; + /** @var array}> */ private $unaryOperators; + /** @var array, associativity: ExpressionParser::OPERATOR_*}> */ private $binaryOperators; + /** @var array */ private $globals; private $functionCallbacks = []; private $filterCallbacks = []; @@ -62,7 +77,7 @@ final class ExtensionSet $class = ltrim($class, '\\'); if (!isset($this->extensions[$class])) { - throw new RuntimeError(sprintf('The "%s" extension is not enabled.', $class)); + throw new RuntimeError(\sprintf('The "%s" extension is not enabled.', $class)); } return $this->extensions[$class]; @@ -117,11 +132,11 @@ final class ExtensionSet $class = \get_class($extension); if ($this->initialized) { - throw new \LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class)); + throw new \LogicException(\sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class)); } if (isset($this->extensions[$class])) { - throw new \LogicException(sprintf('Unable to register extension "%s" as it is already registered.', $class)); + throw new \LogicException(\sprintf('Unable to register extension "%s" as it is already registered.', $class)); } $this->extensions[$class] = $extension; @@ -130,7 +145,7 @@ final class ExtensionSet public function addFunction(TwigFunction $function): void { if ($this->initialized) { - throw new \LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $function->getName())); + throw new \LogicException(\sprintf('Unable to add function "%s" as extensions have already been initialized.', $function->getName())); } $this->staging->addFunction($function); @@ -158,14 +173,11 @@ final class ExtensionSet return $this->functions[$name]; } - foreach ($this->functions as $pattern => $function) { - $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); - - if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) { + foreach ($this->dynamicFunctions as $pattern => $function) { + if (preg_match($pattern, $name, $matches)) { array_shift($matches); - $function->setArguments($matches); - return $function; + return $function->withDynamicArguments($name, $function->getName(), $matches); } } @@ -186,7 +198,7 @@ final class ExtensionSet public function addFilter(TwigFilter $filter): void { if ($this->initialized) { - throw new \LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $filter->getName())); + throw new \LogicException(\sprintf('Unable to add filter "%s" as extensions have already been initialized.', $filter->getName())); } $this->staging->addFilter($filter); @@ -214,14 +226,11 @@ final class ExtensionSet return $this->filters[$name]; } - foreach ($this->filters as $pattern => $filter) { - $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); - - if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) { + foreach ($this->dynamicFilters as $pattern => $filter) { + if (preg_match($pattern, $name, $matches)) { array_shift($matches); - $filter->setArguments($matches); - return $filter; + return $filter->withDynamicArguments($name, $filter->getName(), $matches); } } @@ -305,6 +314,9 @@ final class ExtensionSet $this->parserCallbacks[] = $callable; } + /** + * @return array + */ public function getGlobals(): array { if (null !== $this->globals) { @@ -317,12 +329,7 @@ final class ExtensionSet continue; } - $extGlobals = $extension->getGlobals(); - if (!\is_array($extGlobals)) { - throw new \UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', \get_class($extension))); - } - - $globals = array_merge($globals, $extGlobals); + $globals = array_merge($globals, $extension->getGlobals()); } if ($this->initialized) { @@ -332,10 +339,15 @@ final class ExtensionSet return $globals; } + public function resetGlobals(): void + { + $this->globals = null; + } + public function addTest(TwigTest $test): void { if ($this->initialized) { - throw new \LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $test->getName())); + throw new \LogicException(\sprintf('Unable to add test "%s" as extensions have already been initialized.', $test->getName())); } $this->staging->addTest($test); @@ -363,22 +375,20 @@ final class ExtensionSet return $this->tests[$name]; } - foreach ($this->tests as $pattern => $test) { - $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + foreach ($this->dynamicTests as $pattern => $test) { + if (preg_match($pattern, $name, $matches)) { + array_shift($matches); - if ($count) { - if (preg_match('#^'.$pattern.'$#', $name, $matches)) { - array_shift($matches); - $test->setArguments($matches); - - return $test; - } + return $test->withDynamicArguments($name, $test->getName(), $matches); } } return null; } + /** + * @return array}> + */ public function getUnaryOperators(): array { if (!$this->initialized) { @@ -388,6 +398,9 @@ final class ExtensionSet return $this->unaryOperators; } + /** + * @return array, associativity: ExpressionParser::OPERATOR_*}> + */ public function getBinaryOperators(): array { if (!$this->initialized) { @@ -403,6 +416,9 @@ final class ExtensionSet $this->filters = []; $this->functions = []; $this->tests = []; + $this->dynamicFilters = []; + $this->dynamicFunctions = []; + $this->dynamicTests = []; $this->visitors = []; $this->unaryOperators = []; $this->binaryOperators = []; @@ -419,17 +435,26 @@ final class ExtensionSet { // filters foreach ($extension->getFilters() as $filter) { - $this->filters[$filter->getName()] = $filter; + $this->filters[$name = $filter->getName()] = $filter; + if (str_contains($name, '*')) { + $this->dynamicFilters['#^'.str_replace('\\*', '(.*?)', preg_quote($name, '#')).'$#'] = $filter; + } } // functions foreach ($extension->getFunctions() as $function) { - $this->functions[$function->getName()] = $function; + $this->functions[$name = $function->getName()] = $function; + if (str_contains($name, '*')) { + $this->dynamicFunctions['#^'.str_replace('\\*', '(.*?)', preg_quote($name, '#')).'$#'] = $function; + } } // tests foreach ($extension->getTests() as $test) { - $this->tests[$test->getName()] = $test; + $this->tests[$name = $test->getName()] = $test; + if (str_contains($name, '*')) { + $this->dynamicTests['#^'.str_replace('\\*', '(.*?)', preg_quote($name, '#')).'$#'] = $test; + } } // token parsers @@ -449,11 +474,11 @@ final class ExtensionSet // operators if ($operators = $extension->getOperators()) { if (!\is_array($operators)) { - throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array with operators, got "%s".', \get_class($extension), \is_object($operators) ? \get_class($operators) : \gettype($operators).(\is_resource($operators) ? '' : '#'.$operators))); + throw new \InvalidArgumentException(\sprintf('"%s::getOperators()" must return an array with operators, got "%s".', \get_class($extension), \is_object($operators) ? \get_class($operators) : \gettype($operators).(\is_resource($operators) ? '' : '#'.$operators))); } if (2 !== \count($operators)) { - throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators))); + throw new \InvalidArgumentException(\sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators))); } $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); diff --git a/data/web/inc/lib/vendor/twig/twig/src/FileExtensionEscapingStrategy.php b/data/web/inc/lib/vendor/twig/twig/src/FileExtensionEscapingStrategy.php index 65198bbb6..812071bf9 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/FileExtensionEscapingStrategy.php +++ b/data/web/inc/lib/vendor/twig/twig/src/FileExtensionEscapingStrategy.php @@ -37,7 +37,7 @@ class FileExtensionEscapingStrategy return 'html'; // return html for directories } - if ('.twig' === substr($name, -5)) { + if (str_ends_with($name, '.twig')) { $name = substr($name, 0, -5); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Lexer.php b/data/web/inc/lib/vendor/twig/twig/src/Lexer.php index 9ff028c87..28feaa2c1 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Lexer.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Lexer.php @@ -19,6 +19,8 @@ use Twig\Error\SyntaxError; */ class Lexer { + private $isInitialized = false; + private $tokens; private $code; private $cursor; @@ -48,6 +50,14 @@ class Lexer public const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; public const PUNCTUATION = '()[]{}?:.,|'; + private const SPECIAL_CHARS = [ + 'f' => "\f", + 'n' => "\n", + 'r' => "\r", + 't' => "\t", + 'v' => "\v", + ]; + public function __construct(Environment $env, array $options = []) { $this->env = $env; @@ -61,6 +71,13 @@ class Lexer 'whitespace_line_chars' => ' \t\0\x0B', 'interpolation' => ['#{', '}'], ], $options); + } + + private function initialize() + { + if ($this->isInitialized) { + return; + } // when PHP 7.3 is the min version, we will be able to remove the '#' part in preg_quote as it's part of the default $this->regexes = [ @@ -149,10 +166,14 @@ class Lexer 'interpolation_start' => '{'.preg_quote($this->options['interpolation'][0], '#').'\s*}A', 'interpolation_end' => '{\s*'.preg_quote($this->options['interpolation'][1], '#').'}A', ]; + + $this->isInitialized = true; } public function tokenize(Source $source): TokenStream { + $this->initialize(); + $this->source = $source; $this->code = str_replace(["\r\n", "\r"], "\n", $source->getCode()); $this->cursor = 0; @@ -194,11 +215,11 @@ class Lexer } } - $this->pushToken(/* Token::EOF_TYPE */ -1); + $this->pushToken(Token::EOF_TYPE); if (!empty($this->brackets)) { - list($expect, $lineno) = array_pop($this->brackets); - throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + [$expect, $lineno] = array_pop($this->brackets); + throw new SyntaxError(\sprintf('Unclosed "%s".', $expect), $lineno, $this->source); } return new TokenStream($this->tokens, $this->source); @@ -208,7 +229,7 @@ class Lexer { // if no matches are left we return the rest of the template as simple text token if ($this->position == \count($this->positions[0]) - 1) { - $this->pushToken(/* Token::TEXT_TYPE */ 0, substr($this->code, $this->cursor)); + $this->pushToken(Token::TEXT_TYPE, substr($this->code, $this->cursor)); $this->cursor = $this->end; return; @@ -237,7 +258,7 @@ class Lexer $text = rtrim($text, " \t\0\x0B"); } } - $this->pushToken(/* Token::TEXT_TYPE */ 0, $text); + $this->pushToken(Token::TEXT_TYPE, $text); $this->moveCursor($textContent.$position[0]); switch ($this->positions[1][$this->position][0]) { @@ -255,14 +276,14 @@ class Lexer $this->moveCursor($match[0]); $this->lineno = (int) $match[1]; } else { - $this->pushToken(/* Token::BLOCK_START_TYPE */ 1); + $this->pushToken(Token::BLOCK_START_TYPE); $this->pushState(self::STATE_BLOCK); $this->currentVarBlockLine = $this->lineno; } break; case $this->options['tag_variable'][0]: - $this->pushToken(/* Token::VAR_START_TYPE */ 2); + $this->pushToken(Token::VAR_START_TYPE); $this->pushState(self::STATE_VAR); $this->currentVarBlockLine = $this->lineno; break; @@ -272,7 +293,7 @@ class Lexer private function lexBlock(): void { if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) { - $this->pushToken(/* Token::BLOCK_END_TYPE */ 3); + $this->pushToken(Token::BLOCK_END_TYPE); $this->moveCursor($match[0]); $this->popState(); } else { @@ -283,7 +304,7 @@ class Lexer private function lexVar(): void { if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) { - $this->pushToken(/* Token::VAR_END_TYPE */ 4); + $this->pushToken(Token::VAR_END_TYPE); $this->moveCursor($match[0]); $this->popState(); } else { @@ -298,23 +319,28 @@ class Lexer $this->moveCursor($match[0]); if ($this->cursor >= $this->end) { - throw new SyntaxError(sprintf('Unclosed "%s".', self::STATE_BLOCK === $this->state ? 'block' : 'variable'), $this->currentVarBlockLine, $this->source); + throw new SyntaxError(\sprintf('Unclosed "%s".', self::STATE_BLOCK === $this->state ? 'block' : 'variable'), $this->currentVarBlockLine, $this->source); } } + // spread operator + if ('.' === $this->code[$this->cursor] && ($this->cursor + 2 < $this->end) && '.' === $this->code[$this->cursor + 1] && '.' === $this->code[$this->cursor + 2]) { + $this->pushToken(Token::SPREAD_TYPE, '...'); + $this->moveCursor('...'); + } // arrow function - if ('=' === $this->code[$this->cursor] && '>' === $this->code[$this->cursor + 1]) { + elseif ('=' === $this->code[$this->cursor] && ($this->cursor + 1 < $this->end) && '>' === $this->code[$this->cursor + 1]) { $this->pushToken(Token::ARROW_TYPE, '=>'); $this->moveCursor('=>'); } // operators elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) { - $this->pushToken(/* Token::OPERATOR_TYPE */ 8, preg_replace('/\s+/', ' ', $match[0])); + $this->pushToken(Token::OPERATOR_TYPE, preg_replace('/\s+/', ' ', $match[0])); $this->moveCursor($match[0]); } // names elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) { - $this->pushToken(/* Token::NAME_TYPE */ 5, $match[0]); + $this->pushToken(Token::NAME_TYPE, $match[0]); $this->moveCursor($match[0]); } // numbers @@ -323,33 +349,33 @@ class Lexer if (ctype_digit($match[0]) && $number <= \PHP_INT_MAX) { $number = (int) $match[0]; // integers lower than the maximum } - $this->pushToken(/* Token::NUMBER_TYPE */ 6, $number); + $this->pushToken(Token::NUMBER_TYPE, $number); $this->moveCursor($match[0]); } // punctuation - elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) { + elseif (str_contains(self::PUNCTUATION, $this->code[$this->cursor])) { // opening bracket - if (false !== strpos('([{', $this->code[$this->cursor])) { + if (str_contains('([{', $this->code[$this->cursor])) { $this->brackets[] = [$this->code[$this->cursor], $this->lineno]; } // closing bracket - elseif (false !== strpos(')]}', $this->code[$this->cursor])) { + elseif (str_contains(')]}', $this->code[$this->cursor])) { if (empty($this->brackets)) { - throw new SyntaxError(sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + throw new SyntaxError(\sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); } - list($expect, $lineno) = array_pop($this->brackets); + [$expect, $lineno] = array_pop($this->brackets); if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) { - throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + throw new SyntaxError(\sprintf('Unclosed "%s".', $expect), $lineno, $this->source); } } - $this->pushToken(/* Token::PUNCTUATION_TYPE */ 9, $this->code[$this->cursor]); + $this->pushToken(Token::PUNCTUATION_TYPE, $this->code[$this->cursor]); ++$this->cursor; } // strings elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) { - $this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes(substr($match[0], 1, -1))); + $this->pushToken(Token::STRING_TYPE, $this->stripcslashes(substr($match[0], 1, -1), substr($match[0], 0, 1))); $this->moveCursor($match[0]); } // opening double quoted string @@ -360,10 +386,67 @@ class Lexer } // unlexable else { - throw new SyntaxError(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + throw new SyntaxError(\sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); } } + private function stripcslashes(string $str, string $quoteType): string + { + $result = ''; + $length = \strlen($str); + + $i = 0; + while ($i < $length) { + if (false === $pos = strpos($str, '\\', $i)) { + $result .= substr($str, $i); + break; + } + + $result .= substr($str, $i, $pos - $i); + $i = $pos + 1; + + if ($i >= $length) { + $result .= '\\'; + break; + } + + $nextChar = $str[$i]; + + if (isset(self::SPECIAL_CHARS[$nextChar])) { + $result .= self::SPECIAL_CHARS[$nextChar]; + } elseif ('\\' === $nextChar) { + $result .= $nextChar; + } elseif ("'" === $nextChar || '"' === $nextChar) { + if ($nextChar !== $quoteType) { + trigger_deprecation('twig/twig', '3.12', 'Character "%s" at position %d should not be escaped; the "\" character is ignored in Twig v3 but will not be in v4. Please remove the extra "\" character.', $nextChar, $i + 1); + } + $result .= $nextChar; + } elseif ('#' === $nextChar && $i + 1 < $length && '{' === $str[$i + 1]) { + $result .= '#{'; + ++$i; + } elseif ('x' === $nextChar && $i + 1 < $length && ctype_xdigit($str[$i + 1])) { + $hex = $str[++$i]; + if ($i + 1 < $length && ctype_xdigit($str[$i + 1])) { + $hex .= $str[++$i]; + } + $result .= \chr(hexdec($hex)); + } elseif (ctype_digit($nextChar) && $nextChar < '8') { + $octal = $nextChar; + while ($i + 1 < $length && ctype_digit($str[$i + 1]) && $str[$i + 1] < '8' && \strlen($octal) < 3) { + $octal .= $str[++$i]; + } + $result .= \chr(octdec($octal)); + } else { + trigger_deprecation('twig/twig', '3.12', 'Character "%s" at position %d should not be escaped; the "\" character is ignored in Twig v3 but will not be in v4. Please remove the extra "\" character.', $nextChar, $i + 1); + $result .= $nextChar; + } + + ++$i; + } + + return $result; + } + private function lexRawData(): void { if (!preg_match($this->regexes['lex_raw_data'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) { @@ -385,7 +468,7 @@ class Lexer } } - $this->pushToken(/* Token::TEXT_TYPE */ 0, $text); + $this->pushToken(Token::TEXT_TYPE, $text); } private function lexComment(): void @@ -401,23 +484,23 @@ class Lexer { if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) { $this->brackets[] = [$this->options['interpolation'][0], $this->lineno]; - $this->pushToken(/* Token::INTERPOLATION_START_TYPE */ 10); + $this->pushToken(Token::INTERPOLATION_START_TYPE); $this->moveCursor($match[0]); $this->pushState(self::STATE_INTERPOLATION); - } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && \strlen($match[0]) > 0) { - $this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes($match[0])); + } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && '' !== $match[0]) { + $this->pushToken(Token::STRING_TYPE, $this->stripcslashes($match[0], '"')); $this->moveCursor($match[0]); } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) { - list($expect, $lineno) = array_pop($this->brackets); + [$expect, $lineno] = array_pop($this->brackets); if ('"' != $this->code[$this->cursor]) { - throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + throw new SyntaxError(\sprintf('Unclosed "%s".', $expect), $lineno, $this->source); } $this->popState(); ++$this->cursor; } else { // unlexable - throw new SyntaxError(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + throw new SyntaxError(\sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); } } @@ -426,7 +509,7 @@ class Lexer $bracket = end($this->brackets); if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, 0, $this->cursor)) { array_pop($this->brackets); - $this->pushToken(/* Token::INTERPOLATION_END_TYPE */ 11); + $this->pushToken(Token::INTERPOLATION_END_TYPE); $this->moveCursor($match[0]); $this->popState(); } else { @@ -437,7 +520,7 @@ class Lexer private function pushToken($type, $value = ''): void { // do not push empty text tokens - if (/* Token::TEXT_TYPE */ 0 === $type && '' === $value) { + if (Token::TEXT_TYPE === $type && '' === $value) { return; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Loader/ArrayLoader.php b/data/web/inc/lib/vendor/twig/twig/src/Loader/ArrayLoader.php index 5d726c35a..2bb54b7a8 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Loader/ArrayLoader.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Loader/ArrayLoader.php @@ -28,14 +28,12 @@ use Twig\Source; */ final class ArrayLoader implements LoaderInterface { - private $templates = []; - /** * @param array $templates An array of templates (keys are the names, and values are the source code) */ - public function __construct(array $templates = []) - { - $this->templates = $templates; + public function __construct( + private array $templates = [], + ) { } public function setTemplate(string $name, string $template): void @@ -46,7 +44,7 @@ final class ArrayLoader implements LoaderInterface public function getSourceContext(string $name): Source { if (!isset($this->templates[$name])) { - throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); + throw new LoaderError(\sprintf('Template "%s" is not defined.', $name)); } return new Source($this->templates[$name], $name); @@ -60,7 +58,7 @@ final class ArrayLoader implements LoaderInterface public function getCacheKey(string $name): string { if (!isset($this->templates[$name])) { - throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); + throw new LoaderError(\sprintf('Template "%s" is not defined.', $name)); } return $name.':'.$this->templates[$name]; @@ -69,7 +67,7 @@ final class ArrayLoader implements LoaderInterface public function isFresh(string $name, int $time): bool { if (!isset($this->templates[$name])) { - throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); + throw new LoaderError(\sprintf('Template "%s" is not defined.', $name)); } return true; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Loader/ChainLoader.php b/data/web/inc/lib/vendor/twig/twig/src/Loader/ChainLoader.php index fbf4f3a06..6e4f9511c 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Loader/ChainLoader.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Loader/ChainLoader.php @@ -21,22 +21,28 @@ use Twig\Source; */ final class ChainLoader implements LoaderInterface { + /** + * @var array + */ private $hasSourceCache = []; - private $loaders = []; /** - * @param LoaderInterface[] $loaders + * @param iterable $loaders */ - public function __construct(array $loaders = []) - { - foreach ($loaders as $loader) { - $this->addLoader($loader); - } + public function __construct( + private iterable $loaders = [], + ) { } public function addLoader(LoaderInterface $loader): void { - $this->loaders[] = $loader; + $current = $this->loaders; + + $this->loaders = (static function () use ($current, $loader): \Generator { + yield from $current; + yield $loader; + })(); + $this->hasSourceCache = []; } @@ -45,13 +51,18 @@ final class ChainLoader implements LoaderInterface */ public function getLoaders(): array { + if (!\is_array($this->loaders)) { + $this->loaders = iterator_to_array($this->loaders, false); + } + return $this->loaders; } public function getSourceContext(string $name): Source { $exceptions = []; - foreach ($this->loaders as $loader) { + + foreach ($this->getLoaders() as $loader) { if (!$loader->exists($name)) { continue; } @@ -63,7 +74,7 @@ final class ChainLoader implements LoaderInterface } } - throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + throw new LoaderError(\sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); } public function exists(string $name): bool @@ -72,7 +83,7 @@ final class ChainLoader implements LoaderInterface return $this->hasSourceCache[$name]; } - foreach ($this->loaders as $loader) { + foreach ($this->getLoaders() as $loader) { if ($loader->exists($name)) { return $this->hasSourceCache[$name] = true; } @@ -84,7 +95,8 @@ final class ChainLoader implements LoaderInterface public function getCacheKey(string $name): string { $exceptions = []; - foreach ($this->loaders as $loader) { + + foreach ($this->getLoaders() as $loader) { if (!$loader->exists($name)) { continue; } @@ -96,13 +108,14 @@ final class ChainLoader implements LoaderInterface } } - throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + throw new LoaderError(\sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); } public function isFresh(string $name, int $time): bool { $exceptions = []; - foreach ($this->loaders as $loader) { + + foreach ($this->getLoaders() as $loader) { if (!$loader->exists($name)) { continue; } @@ -114,6 +127,6 @@ final class ChainLoader implements LoaderInterface } } - throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + throw new LoaderError(\sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Loader/FilesystemLoader.php b/data/web/inc/lib/vendor/twig/twig/src/Loader/FilesystemLoader.php index 62267a11c..c60964f5f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Loader/FilesystemLoader.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Loader/FilesystemLoader.php @@ -34,9 +34,9 @@ class FilesystemLoader implements LoaderInterface * @param string|array $paths A path or an array of paths where to look for templates * @param string|null $rootPath The root path common to all relative paths (null for getcwd()) */ - public function __construct($paths = [], string $rootPath = null) + public function __construct($paths = [], ?string $rootPath = null) { - $this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR; + $this->rootPath = ($rootPath ?? getcwd()).\DIRECTORY_SEPARATOR; if (null !== $rootPath && false !== ($realPath = realpath($rootPath))) { $this->rootPath = $realPath.\DIRECTORY_SEPARATOR; } @@ -89,7 +89,7 @@ class FilesystemLoader implements LoaderInterface $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path; if (!is_dir($checkPath)) { - throw new LoaderError(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); + throw new LoaderError(\sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); } $this->paths[$namespace][] = rtrim($path, '/\\'); @@ -105,7 +105,7 @@ class FilesystemLoader implements LoaderInterface $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path; if (!is_dir($checkPath)) { - throw new LoaderError(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); + throw new LoaderError(\sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); } $path = rtrim($path, '/\\'); @@ -183,7 +183,7 @@ class FilesystemLoader implements LoaderInterface } try { - list($namespace, $shortname) = $this->parseName($name); + [$namespace, $shortname] = $this->parseName($name); $this->validateName($shortname); } catch (LoaderError $e) { @@ -195,7 +195,7 @@ class FilesystemLoader implements LoaderInterface } if (!isset($this->paths[$namespace])) { - $this->errorCache[$name] = sprintf('There are no registered paths for namespace "%s".', $namespace); + $this->errorCache[$name] = \sprintf('There are no registered paths for namespace "%s".', $namespace); if (!$throw) { return null; @@ -218,7 +218,7 @@ class FilesystemLoader implements LoaderInterface } } - $this->errorCache[$name] = sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])); + $this->errorCache[$name] = \sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])); if (!$throw) { return null; @@ -236,7 +236,7 @@ class FilesystemLoader implements LoaderInterface { if (isset($name[0]) && '@' == $name[0]) { if (false === $pos = strpos($name, '/')) { - throw new LoaderError(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); + throw new LoaderError(\sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); } $namespace = substr($name, 1, $pos - 1); @@ -250,7 +250,7 @@ class FilesystemLoader implements LoaderInterface private function validateName(string $name): void { - if (false !== strpos($name, "\0")) { + if (str_contains($name, "\0")) { throw new LoaderError('A template name cannot contain NUL bytes.'); } @@ -265,7 +265,7 @@ class FilesystemLoader implements LoaderInterface } if ($level < 0) { - throw new LoaderError(sprintf('Looks like you try to load a template outside configured directories (%s).', $name)); + throw new LoaderError(\sprintf('Looks like you try to load a template outside configured directories (%s).', $name)); } } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Markup.php b/data/web/inc/lib/vendor/twig/twig/src/Markup.php index 1788acc4f..4fae779ee 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Markup.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Markup.php @@ -16,10 +16,10 @@ namespace Twig; * * @author Fabien Potencier */ -class Markup implements \Countable, \JsonSerializable +class Markup implements \Countable, \JsonSerializable, \Stringable { private $content; - private $charset; + private ?string $charset; public function __construct($content, $charset) { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/AutoEscapeNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/AutoEscapeNode.php index cd970411b..ee806396e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/AutoEscapeNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/AutoEscapeNode.php @@ -11,6 +11,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; /** @@ -24,11 +25,12 @@ use Twig\Compiler; * * @author Fabien Potencier */ +#[YieldReady] class AutoEscapeNode extends Node { - public function __construct($value, Node $body, int $lineno, string $tag = 'autoescape') + public function __construct($value, Node $body, int $lineno) { - parent::__construct(['body' => $body], ['value' => $value], $lineno, $tag); + parent::__construct(['body' => $body], ['value' => $value], $lineno); } public function compile(Compiler $compiler): void diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/BlockNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/BlockNode.php index 0632ba747..b4f939cf6 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/BlockNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/BlockNode.php @@ -12,6 +12,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; /** @@ -19,24 +20,29 @@ use Twig\Compiler; * * @author Fabien Potencier */ +#[YieldReady] class BlockNode extends Node { - public function __construct(string $name, Node $body, int $lineno, string $tag = null) + public function __construct(string $name, Node $body, int $lineno) { - parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag); + parent::__construct(['body' => $body], ['name' => $name], $lineno); } public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) - ->write(sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n") + ->write("/**\n") + ->write(" * @return iterable\n") + ->write(" */\n") + ->write(\sprintf("public function block_%s(array \$context, array \$blocks = []): iterable\n", $this->getAttribute('name')), "{\n") ->indent() ->write("\$macros = \$this->macros;\n") ; $compiler ->subcompile($this->getNode('body')) + ->write("yield from [];\n") ->outdent() ->write("}\n\n") ; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/BlockReferenceNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/BlockReferenceNode.php index cc8af5b52..7c313a04c 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/BlockReferenceNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/BlockReferenceNode.php @@ -12,6 +12,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; /** @@ -19,18 +20,19 @@ use Twig\Compiler; * * @author Fabien Potencier */ +#[YieldReady] class BlockReferenceNode extends Node implements NodeOutputInterface { - public function __construct(string $name, int $lineno, string $tag = null) + public function __construct(string $name, int $lineno) { - parent::__construct([], ['name' => $name], $lineno, $tag); + parent::__construct([], ['name' => $name], $lineno); } public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) - ->write(sprintf("\$this->displayBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name'))) + ->write(\sprintf("yield from \$this->unwrap()->yieldBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name'))) ; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/BodyNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/BodyNode.php index 041cbf685..08115b3bd 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/BodyNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/BodyNode.php @@ -11,11 +11,14 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; + /** * Represents a body node. * * @author Fabien Potencier */ +#[YieldReady] class BodyNode extends Node { } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/CaptureNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/CaptureNode.php new file mode 100644 index 000000000..3b7f0b6d8 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/CaptureNode.php @@ -0,0 +1,57 @@ + + */ +#[YieldReady] +class CaptureNode extends Node +{ + public function __construct(Node $body, int $lineno) + { + parent::__construct(['body' => $body], ['raw' => false], $lineno); + } + + public function compile(Compiler $compiler): void + { + $useYield = $compiler->getEnvironment()->useYield(); + + if (!$this->getAttribute('raw')) { + $compiler->raw("('' === \$tmp = "); + } + $compiler + ->raw($useYield ? "implode('', iterator_to_array(" : '\\Twig\\Extension\\CoreExtension::captureOutput(') + ->raw("(function () use (&\$context, \$macros, \$blocks) {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->write("yield from [];\n") + ->outdent() + ->write('})()') + ; + if ($useYield) { + $compiler->raw(', false))'); + } else { + $compiler->raw(')'); + } + if (!$this->getAttribute('raw')) { + $compiler->raw(") ? '' : new Markup(\$tmp, \$this->env->getCharset());"); + } else { + $compiler->raw(';'); + } + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityCallNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityCallNode.php index a78a38d80..9c162d129 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityCallNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityCallNode.php @@ -11,17 +11,19 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; /** * @author Fabien Potencier */ +#[YieldReady] class CheckSecurityCallNode extends Node { public function compile(Compiler $compiler) { $compiler - ->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n") + ->write("\$this->sandbox = \$this->extensions[SandboxExtension::class];\n") ->write("\$this->checkSecurity();\n") ; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityNode.php index 472732796..6e591aad4 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityNode.php @@ -11,17 +11,24 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; /** * @author Fabien Potencier */ +#[YieldReady] class CheckSecurityNode extends Node { private $usedFilters; private $usedTags; private $usedFunctions; + /** + * @param array $usedFilters + * @param array $usedTags + * @param array $usedFunctions + */ public function __construct(array $usedFilters, array $usedTags, array $usedFunctions) { $this->usedFilters = $usedFilters; @@ -33,32 +40,22 @@ class CheckSecurityNode extends Node public function compile(Compiler $compiler): void { - $tags = $filters = $functions = []; - foreach (['tags', 'filters', 'functions'] as $type) { - foreach ($this->{'used'.ucfirst($type)} as $name => $node) { - if ($node instanceof Node) { - ${$type}[$name] = $node->getTemplateLine(); - } else { - ${$type}[$node] = null; - } - } - } - $compiler ->write("\n") ->write("public function checkSecurity()\n") ->write("{\n") ->indent() - ->write('static $tags = ')->repr(array_filter($tags))->raw(";\n") - ->write('static $filters = ')->repr(array_filter($filters))->raw(";\n") - ->write('static $functions = ')->repr(array_filter($functions))->raw(";\n\n") + ->write('static $tags = ')->repr(array_filter($this->usedTags))->raw(";\n") + ->write('static $filters = ')->repr(array_filter($this->usedFilters))->raw(";\n") + ->write('static $functions = ')->repr(array_filter($this->usedFunctions))->raw(";\n\n") ->write("try {\n") ->indent() ->write("\$this->sandbox->checkSecurity(\n") ->indent() - ->write(!$tags ? "[],\n" : "['".implode("', '", array_keys($tags))."'],\n") - ->write(!$filters ? "[],\n" : "['".implode("', '", array_keys($filters))."'],\n") - ->write(!$functions ? "[]\n" : "['".implode("', '", array_keys($functions))."']\n") + ->write(!$this->usedTags ? "[],\n" : "['".implode("', '", array_keys($this->usedTags))."'],\n") + ->write(!$this->usedFilters ? "[],\n" : "['".implode("', '", array_keys($this->usedFilters))."'],\n") + ->write(!$this->usedFunctions ? "[],\n" : "['".implode("', '", array_keys($this->usedFunctions))."'],\n") + ->write("\$this->source\n") ->outdent() ->write(");\n") ->outdent() diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/CheckToStringNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/CheckToStringNode.php index c7a9d6984..937240c1d 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/CheckToStringNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/CheckToStringNode.php @@ -11,6 +11,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; @@ -24,11 +25,12 @@ use Twig\Node\Expression\AbstractExpression; * * @author Fabien Potencier */ +#[YieldReady] class CheckToStringNode extends AbstractExpression { public function __construct(AbstractExpression $expr) { - parent::__construct(['expr' => $expr], [], $expr->getTemplateLine(), $expr->getNodeTag()); + parent::__construct(['expr' => $expr], [], $expr->getTemplateLine()); } public function compile(Compiler $compiler): void diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/DeprecatedNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/DeprecatedNode.php index 5ff44307f..0772adfc3 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/DeprecatedNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/DeprecatedNode.php @@ -11,6 +11,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\ConstantExpression; @@ -20,11 +21,12 @@ use Twig\Node\Expression\ConstantExpression; * * @author Yonel Ceruto */ +#[YieldReady] class DeprecatedNode extends Node { - public function __construct(AbstractExpression $expr, int $lineno, string $tag = null) + public function __construct(AbstractExpression $expr, int $lineno) { - parent::__construct(['expr' => $expr], [], $lineno, $tag); + parent::__construct(['expr' => $expr], [], $lineno); } public function compile(Compiler $compiler): void @@ -33,21 +35,39 @@ class DeprecatedNode extends Node $expr = $this->getNode('expr'); - if ($expr instanceof ConstantExpression) { - $compiler->write('@trigger_error(') - ->subcompile($expr); - } else { + if (!$expr instanceof ConstantExpression) { $varName = $compiler->getVarName(); - $compiler->write(sprintf('$%s = ', $varName)) + $compiler + ->write(\sprintf('$%s = ', $varName)) ->subcompile($expr) ->raw(";\n") - ->write(sprintf('@trigger_error($%s', $varName)); + ; + } + + $compiler->write('trigger_deprecation('); + if ($this->hasNode('package')) { + $compiler->subcompile($this->getNode('package')); + } else { + $compiler->raw("''"); + } + $compiler->raw(', '); + if ($this->hasNode('version')) { + $compiler->subcompile($this->getNode('version')); + } else { + $compiler->raw("''"); + } + $compiler->raw(', '); + + if ($expr instanceof ConstantExpression) { + $compiler->subcompile($expr); + } else { + $compiler->write(\sprintf('$%s', $varName)); } $compiler ->raw('.') - ->string(sprintf(' ("%s" at line %d).', $this->getTemplateName(), $this->getTemplateLine())) - ->raw(", E_USER_DEPRECATED);\n") + ->string(\sprintf(' in "%s" at line %d.', $this->getTemplateName(), $this->getTemplateLine())) + ->raw(");\n") ; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/DoNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/DoNode.php index f7783d19f..1593fd050 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/DoNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/DoNode.php @@ -11,6 +11,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; @@ -19,11 +20,12 @@ use Twig\Node\Expression\AbstractExpression; * * @author Fabien Potencier */ +#[YieldReady] class DoNode extends Node { - public function __construct(AbstractExpression $expr, int $lineno, string $tag = null) + public function __construct(AbstractExpression $expr, int $lineno) { - parent::__construct(['expr' => $expr], [], $lineno, $tag); + parent::__construct(['expr' => $expr], [], $lineno); } public function compile(Compiler $compiler): void diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/EmbedNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/EmbedNode.php index 903c3f6c7..4cd3b38f2 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/EmbedNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/EmbedNode.php @@ -11,6 +11,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\ConstantExpression; @@ -20,12 +21,13 @@ use Twig\Node\Expression\ConstantExpression; * * @author Fabien Potencier */ +#[YieldReady] class EmbedNode extends IncludeNode { // we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module) - public function __construct(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null) + public function __construct(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno) { - parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag); + parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno); $this->setAttribute('name', $name); $this->setAttribute('index', $index); diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/AbstractExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/AbstractExpression.php index 42da0559d..1692f5671 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/AbstractExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/AbstractExpression.php @@ -21,4 +21,8 @@ use Twig\Node\Node; */ abstract class AbstractExpression extends Node { + public function isGenerator(): bool + { + return $this->hasAttribute('is_generator') && $this->getAttribute('is_generator'); + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrayExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrayExpression.php index 0e25fe46a..5f8b0f63f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrayExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrayExpression.php @@ -55,7 +55,7 @@ class ArrayExpression extends AbstractExpression return false; } - public function addElement(AbstractExpression $value, AbstractExpression $key = null): void + public function addElement(AbstractExpression $value, ?AbstractExpression $key = null): void { if (null === $key) { $key = new ConstantExpression(++$this->index, $value->getTemplateLine()); @@ -66,20 +66,70 @@ class ArrayExpression extends AbstractExpression public function compile(Compiler $compiler): void { + $keyValuePairs = $this->getKeyValuePairs(); + $needsArrayMergeSpread = \PHP_VERSION_ID < 80100 && $this->hasSpreadItem($keyValuePairs); + + if ($needsArrayMergeSpread) { + $compiler->raw('CoreExtension::merge('); + } $compiler->raw('['); $first = true; - foreach ($this->getKeyValuePairs() as $pair) { + $reopenAfterMergeSpread = false; + $nextIndex = 0; + foreach ($keyValuePairs as $pair) { + if ($reopenAfterMergeSpread) { + $compiler->raw(', ['); + $reopenAfterMergeSpread = false; + } + + if ($needsArrayMergeSpread && $pair['value']->hasAttribute('spread')) { + $compiler->raw('], ')->subcompile($pair['value']); + $first = true; + $reopenAfterMergeSpread = true; + continue; + } if (!$first) { $compiler->raw(', '); } $first = false; - $compiler - ->subcompile($pair['key']) - ->raw(' => ') - ->subcompile($pair['value']) - ; + if ($pair['value']->hasAttribute('spread') && !$needsArrayMergeSpread) { + $compiler->raw('...')->subcompile($pair['value']); + ++$nextIndex; + } else { + $key = $pair['key'] instanceof ConstantExpression ? $pair['key']->getAttribute('value') : null; + + if ($nextIndex !== $key) { + if (\is_int($key)) { + $nextIndex = $key + 1; + } + $compiler + ->subcompile($pair['key']) + ->raw(' => ') + ; + } else { + ++$nextIndex; + } + + $compiler->subcompile($pair['value']); + } } - $compiler->raw(']'); + if (!$reopenAfterMergeSpread) { + $compiler->raw(']'); + } + if ($needsArrayMergeSpread) { + $compiler->raw(')'); + } + } + + private function hasSpreadItem(array $pairs): bool + { + foreach ($pairs as $pair) { + if ($pair['value']->hasAttribute('spread')) { + return true; + } + } + + return false; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php index eaad03c9c..2bae4edd7 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php @@ -21,9 +21,9 @@ use Twig\Node\Node; */ class ArrowFunctionExpression extends AbstractExpression { - public function __construct(AbstractExpression $expr, Node $names, $lineno, $tag = null) + public function __construct(AbstractExpression $expr, Node $names, $lineno) { - parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno, $tag); + parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno); } public function compile(Compiler $compiler): void diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php index c3516b853..a73a5608d 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php @@ -20,11 +20,11 @@ class EndsWithBinary extends AbstractBinary $left = $compiler->getVarName(); $right = $compiler->getVarName(); $compiler - ->raw(sprintf('(is_string($%s = ', $left)) + ->raw(\sprintf('(is_string($%s = ', $left)) ->subcompile($this->getNode('left')) - ->raw(sprintf(') && is_string($%s = ', $right)) + ->raw(\sprintf(') && is_string($%s = ', $right)) ->subcompile($this->getNode('right')) - ->raw(sprintf(') && (\'\' === $%2$s || $%2$s === substr($%1$s, -strlen($%2$s))))', $left, $right)) + ->raw(\sprintf(') && str_ends_with($%1$s, $%2$s))', $left, $right)) ; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php index 6b48549ef..5f423196f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php @@ -24,7 +24,7 @@ class EqualBinary extends AbstractBinary } $compiler - ->raw('(0 === twig_compare(') + ->raw('(0 === CoreExtension::compare(') ->subcompile($this->getNode('left')) ->raw(', ') ->subcompile($this->getNode('right')) diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php index e1dd06780..f42de3f86 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php @@ -24,7 +24,7 @@ class GreaterBinary extends AbstractBinary } $compiler - ->raw('(1 === twig_compare(') + ->raw('(1 === CoreExtension::compare(') ->subcompile($this->getNode('left')) ->raw(', ') ->subcompile($this->getNode('right')) diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php index df9bfcfbf..0c4f43fd9 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php @@ -24,7 +24,7 @@ class GreaterEqualBinary extends AbstractBinary } $compiler - ->raw('(0 <= twig_compare(') + ->raw('(0 <= CoreExtension::compare(') ->subcompile($this->getNode('left')) ->raw(', ') ->subcompile($this->getNode('right')) diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/HasEveryBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/HasEveryBinary.php new file mode 100644 index 000000000..c57bb20e9 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/HasEveryBinary.php @@ -0,0 +1,33 @@ +raw('CoreExtension::arrayEvery($this->env, ') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/HasSomeBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/HasSomeBinary.php new file mode 100644 index 000000000..12293f84c --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/HasSomeBinary.php @@ -0,0 +1,33 @@ +raw('CoreExtension::arraySome($this->env, ') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php index 6dbfa97f0..68a98fe15 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php @@ -18,7 +18,7 @@ class InBinary extends AbstractBinary public function compile(Compiler $compiler): void { $compiler - ->raw('twig_in_filter(') + ->raw('CoreExtension::inFilter(') ->subcompile($this->getNode('left')) ->raw(', ') ->subcompile($this->getNode('right')) diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php index 598e62913..fb3264a2d 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php @@ -24,7 +24,7 @@ class LessBinary extends AbstractBinary } $compiler - ->raw('(-1 === twig_compare(') + ->raw('(-1 === CoreExtension::compare(') ->subcompile($this->getNode('left')) ->raw(', ') ->subcompile($this->getNode('right')) diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php index e3c4af58d..8f3653892 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php @@ -24,7 +24,7 @@ class LessEqualBinary extends AbstractBinary } $compiler - ->raw('(0 >= twig_compare(') + ->raw('(0 >= CoreExtension::compare(') ->subcompile($this->getNode('left')) ->raw(', ') ->subcompile($this->getNode('right')) diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php index bc97292cd..4669044e0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php @@ -18,7 +18,7 @@ class MatchesBinary extends AbstractBinary public function compile(Compiler $compiler): void { $compiler - ->raw('preg_match(') + ->raw('CoreExtension::matches(') ->subcompile($this->getNode('right')) ->raw(', ') ->subcompile($this->getNode('left')) diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php index db47a2890..d137ef627 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php @@ -24,7 +24,7 @@ class NotEqualBinary extends AbstractBinary } $compiler - ->raw('(0 !== twig_compare(') + ->raw('(0 !== CoreExtension::compare(') ->subcompile($this->getNode('left')) ->raw(', ') ->subcompile($this->getNode('right')) diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php index fcba6cca1..80c8755d8 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php @@ -18,7 +18,7 @@ class NotInBinary extends AbstractBinary public function compile(Compiler $compiler): void { $compiler - ->raw('!twig_in_filter(') + ->raw('!CoreExtension::inFilter(') ->subcompile($this->getNode('left')) ->raw(', ') ->subcompile($this->getNode('right')) diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php index d0df1c4b6..4519f30d9 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php @@ -20,11 +20,11 @@ class StartsWithBinary extends AbstractBinary $left = $compiler->getVarName(); $right = $compiler->getVarName(); $compiler - ->raw(sprintf('(is_string($%s = ', $left)) + ->raw(\sprintf('(is_string($%s = ', $left)) ->subcompile($this->getNode('left')) - ->raw(sprintf(') && is_string($%s = ', $right)) + ->raw(\sprintf(') && is_string($%s = ', $right)) ->subcompile($this->getNode('right')) - ->raw(sprintf(') && (\'\' === $%2$s || 0 === strpos($%1$s, $%2$s)))', $left, $right)) + ->raw(\sprintf(') && str_starts_with($%1$s, $%2$s))', $left, $right)) ; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php index b1e2a8f7b..acd231e15 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php @@ -22,14 +22,14 @@ use Twig\Node\Node; */ class BlockReferenceExpression extends AbstractExpression { - public function __construct(Node $name, ?Node $template, int $lineno, string $tag = null) + public function __construct(Node $name, ?Node $template, int $lineno) { $nodes = ['name' => $name]; if (null !== $template) { $nodes['template'] = $template; } - parent::__construct($nodes, ['is_defined_test' => false, 'output' => false], $lineno, $tag); + parent::__construct($nodes, ['is_defined_test' => false, 'output' => false], $lineno); } public function compile(Compiler $compiler): void @@ -40,8 +40,9 @@ class BlockReferenceExpression extends AbstractExpression if ($this->getAttribute('output')) { $compiler->addDebugInfo($this); + $compiler->write('yield from '); $this - ->compileTemplateCall($compiler, 'displayBlock') + ->compileTemplateCall($compiler, 'yieldBlock') ->raw(";\n"); } else { $this->compileTemplateCall($compiler, 'renderBlock'); @@ -65,7 +66,7 @@ class BlockReferenceExpression extends AbstractExpression ; } - $compiler->raw(sprintf('->%s', $method)); + $compiler->raw(\sprintf('->unwrap()->%s', $method)); return $this->compileBlockArguments($compiler); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/CallExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/CallExpression.php index 28881066b..6fc6f66e0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/CallExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/CallExpression.php @@ -15,40 +15,49 @@ use Twig\Compiler; use Twig\Error\SyntaxError; use Twig\Extension\ExtensionInterface; use Twig\Node\Node; +use Twig\TwigCallableInterface; +use Twig\TwigFilter; +use Twig\TwigFunction; +use Twig\TwigTest; +use Twig\Util\CallableArgumentsExtractor; +use Twig\Util\ReflectionCallable; abstract class CallExpression extends AbstractExpression { - private $reflector; + private $reflector = null; protected function compileCallable(Compiler $compiler) { - $callable = $this->getAttribute('callable'); + $twigCallable = $this->getTwigCallable(); + $callable = $twigCallable->getCallable(); - if (\is_string($callable) && false === strpos($callable, '::')) { + if (\is_string($callable) && !str_contains($callable, '::')) { $compiler->raw($callable); } else { - [$r, $callable] = $this->reflectCallable($callable); + $rc = $this->reflectCallable($twigCallable); + $r = $rc->getReflector(); + $callable = $rc->getCallable(); if (\is_string($callable)) { $compiler->raw($callable); } elseif (\is_array($callable) && \is_string($callable[0])) { if (!$r instanceof \ReflectionMethod || $r->isStatic()) { - $compiler->raw(sprintf('%s::%s', $callable[0], $callable[1])); + $compiler->raw(\sprintf('%s::%s', $callable[0], $callable[1])); } else { - $compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1])); + $compiler->raw(\sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1])); } } elseif (\is_array($callable) && $callable[0] instanceof ExtensionInterface) { $class = \get_class($callable[0]); if (!$compiler->getEnvironment()->hasExtension($class)) { // Compile a non-optimized call to trigger a \Twig\Error\RuntimeError, which cannot be a compile-time error - $compiler->raw(sprintf('$this->env->getExtension(\'%s\')', $class)); + $compiler->raw(\sprintf('$this->env->getExtension(\'%s\')', $class)); } else { - $compiler->raw(sprintf('$this->extensions[\'%s\']', ltrim($class, '\\'))); + $compiler->raw(\sprintf('$this->extensions[\'%s\']', ltrim($class, '\\'))); } - $compiler->raw(sprintf('->%s', $callable[1])); + $compiler->raw(\sprintf('->%s', $callable[1])); } else { - $compiler->raw(sprintf('$this->env->get%s(\'%s\')->getCallable()', ucfirst($this->getAttribute('type')), $this->getAttribute('name'))); + $compiler->raw(\sprintf('$this->env->get%s(\'%s\')->getCallable()', ucfirst($this->getAttribute('type')), $twigCallable->getDynamicName())); } } @@ -57,16 +66,30 @@ abstract class CallExpression extends AbstractExpression protected function compileArguments(Compiler $compiler, $isArray = false): void { + if (\func_num_args() >= 2) { + trigger_deprecation('twig/twig', '3.11', 'Passing a second argument to "%s()" is deprecated.', __METHOD__); + } + $compiler->raw($isArray ? '[' : '('); $first = true; - if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { + $twigCallable = $this->getAttribute('twig_callable'); + + if ($twigCallable->needsCharset()) { + $compiler->raw('$this->env->getCharset()'); + $first = false; + } + + if ($twigCallable->needsEnvironment()) { + if (!$first) { + $compiler->raw(', '); + } $compiler->raw('$this->env'); $first = false; } - if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { + if ($twigCallable->needsContext()) { if (!$first) { $compiler->raw(', '); } @@ -74,14 +97,12 @@ abstract class CallExpression extends AbstractExpression $first = false; } - if ($this->hasAttribute('arguments')) { - foreach ($this->getAttribute('arguments') as $argument) { - if (!$first) { - $compiler->raw(', '); - } - $compiler->string($argument); - $first = false; + foreach ($twigCallable->getArguments() as $argument) { + if (!$first) { + $compiler->raw(', '); } + $compiler->string($argument); + $first = false; } if ($this->hasNode('node')) { @@ -93,8 +114,7 @@ abstract class CallExpression extends AbstractExpression } if ($this->hasNode('arguments')) { - $callable = $this->getAttribute('callable'); - $arguments = $this->getArguments($callable, $this->getNode('arguments')); + $arguments = (new CallableArgumentsExtractor($this, $this->getTwigCallable()))->extractArguments($this->getNode('arguments')); foreach ($arguments as $node) { if (!$first) { $compiler->raw(', '); @@ -107,8 +127,13 @@ abstract class CallExpression extends AbstractExpression $compiler->raw($isArray ? ']' : ')'); } + /** + * @deprecated since 3.12, use Twig\Util\CallableArgumentsExtractor::getArguments() instead + */ protected function getArguments($callable, $arguments) { + trigger_deprecation('twig/twig', '3.12', 'The "%s()" method is deprecated, use Twig\Util\CallableArgumentsExtractor::getArguments() instead.', __METHOD__); + $callType = $this->getAttribute('type'); $callName = $this->getAttribute('name'); @@ -119,28 +144,28 @@ abstract class CallExpression extends AbstractExpression $named = true; $name = $this->normalizeName($name); } elseif ($named) { - throw new SyntaxError(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + throw new SyntaxError(\sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); } $parameters[$name] = $node; } - $isVariadic = $this->hasAttribute('is_variadic') && $this->getAttribute('is_variadic'); + $isVariadic = $this->getAttribute('twig_callable')->isVariadic(); if (!$named && !$isVariadic) { return $parameters; } if (!$callable) { if ($named) { - $message = sprintf('Named arguments are not supported for %s "%s".', $callType, $callName); + $message = \sprintf('Named arguments are not supported for %s "%s".', $callType, $callName); } else { - $message = sprintf('Arbitrary positional arguments are not supported for %s "%s".', $callType, $callName); + $message = \sprintf('Arbitrary positional arguments are not supported for %s "%s".', $callType, $callName); } throw new \LogicException($message); } - list($callableParameters, $isPhpVariadic) = $this->getCallableParameters($callable, $isVariadic); + [$callableParameters, $isPhpVariadic] = $this->getCallableParameters($callable, $isVariadic); $arguments = []; $names = []; $missingArguments = []; @@ -160,11 +185,11 @@ abstract class CallExpression extends AbstractExpression if (\array_key_exists($name, $parameters)) { if (\array_key_exists($pos, $parameters)) { - throw new SyntaxError(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + throw new SyntaxError(\sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); } if (\count($missingArguments)) { - throw new SyntaxError(sprintf( + throw new SyntaxError(\sprintf( 'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".', $name, $callType, $callName, implode(', ', $names), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments) ), $this->getTemplateLine(), $this->getSourceContext()); @@ -189,7 +214,7 @@ abstract class CallExpression extends AbstractExpression $missingArguments[] = $name; } } else { - throw new SyntaxError(sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + throw new SyntaxError(\sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); } } @@ -220,7 +245,7 @@ abstract class CallExpression extends AbstractExpression } throw new SyntaxError( - sprintf( + \sprintf( 'Unknown argument%s "%s" for %s "%s(%s)".', \count($parameters) > 1 ? 's' : '', implode('", "', array_keys($parameters)), $callType, $callName, implode(', ', $names) ), @@ -232,88 +257,106 @@ abstract class CallExpression extends AbstractExpression return $arguments; } + /** + * @deprecated since 3.12 + */ protected function normalizeName(string $name): string { + trigger_deprecation('twig/twig', '3.12', 'The "%s()" method is deprecated.', __METHOD__); + return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name)); } + // To be removed in 4.0 private function getCallableParameters($callable, bool $isVariadic): array { - [$r, , $callableName] = $this->reflectCallable($callable); + $twigCallable = $this->getAttribute('twig_callable'); + $rc = $this->reflectCallable($twigCallable); + $r = $rc->getReflector(); + $callableName = $rc->getName(); $parameters = $r->getParameters(); if ($this->hasNode('node')) { array_shift($parameters); } - if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { + if ($twigCallable->needsCharset()) { array_shift($parameters); } - if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { + if ($twigCallable->needsEnvironment()) { array_shift($parameters); } - if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) { - foreach ($this->getAttribute('arguments') as $argument) { - array_shift($parameters); - } + if ($twigCallable->needsContext()) { + array_shift($parameters); } + foreach ($twigCallable->getArguments() as $argument) { + array_shift($parameters); + } + $isPhpVariadic = false; if ($isVariadic) { $argument = end($parameters); - $isArray = $argument && $argument->hasType() && 'array' === $argument->getType()->getName(); + $isArray = $argument && $argument->hasType() && $argument->getType() instanceof \ReflectionNamedType && 'array' === $argument->getType()->getName(); if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) { array_pop($parameters); } elseif ($argument && $argument->isVariadic()) { array_pop($parameters); $isPhpVariadic = true; } else { - throw new \LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $this->getAttribute('name'))); + throw new \LogicException(\sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $twigCallable->getName())); } } return [$parameters, $isPhpVariadic]; } - private function reflectCallable($callable) + private function reflectCallable(TwigCallableInterface $callable): ReflectionCallable { - if (null !== $this->reflector) { - return $this->reflector; + if (!$this->reflector) { + $this->reflector = new ReflectionCallable($callable); } - if (\is_string($callable) && false !== $pos = strpos($callable, '::')) { - $callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)]; - } + return $this->reflector; + } - if (\is_array($callable) && method_exists($callable[0], $callable[1])) { - $r = new \ReflectionMethod($callable[0], $callable[1]); + /** + * Overrides the Twig callable based on attributes (as potentially, attributes changed between the creation and the compilation of the node). + * + * To be removed in 4.0 and replace by $this->getAttribute('twig_callable'). + */ + private function getTwigCallable(): TwigCallableInterface + { + $current = $this->getAttribute('twig_callable'); - return $this->reflector = [$r, $callable, $r->class.'::'.$r->name]; - } + $this->setAttribute('twig_callable', match ($this->getAttribute('type')) { + 'test' => (new TwigTest( + $this->getAttribute('name'), + $this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(), + [ + 'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(), + ], + ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ?: $current->getArguments()), + 'function' => (new TwigFunction( + $this->hasAttribute('name') ? $this->getAttribute('name') : $current->getName(), + $this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(), + [ + 'needs_environment' => $this->hasAttribute('needs_environment') ? $this->getAttribute('needs_environment') : $current->needsEnvironment(), + 'needs_context' => $this->hasAttribute('needs_context') ? $this->getAttribute('needs_context') : $current->needsContext(), + 'needs_charset' => $this->hasAttribute('needs_charset') ? $this->getAttribute('needs_charset') : $current->needsCharset(), + 'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(), + ], + ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ?: $current->getArguments()), + 'filter' => (new TwigFilter( + $this->getAttribute('name'), + $this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(), + [ + 'needs_environment' => $this->hasAttribute('needs_environment') ? $this->getAttribute('needs_environment') : $current->needsEnvironment(), + 'needs_context' => $this->hasAttribute('needs_context') ? $this->getAttribute('needs_context') : $current->needsContext(), + 'needs_charset' => $this->hasAttribute('needs_charset') ? $this->getAttribute('needs_charset') : $current->needsCharset(), + 'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(), + ], + ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ?: $current->getArguments()), + }); - $checkVisibility = $callable instanceof \Closure; - try { - $closure = \Closure::fromCallable($callable); - } catch (\TypeError $e) { - throw new \LogicException(sprintf('Callback for %s "%s" is not callable in the current scope.', $this->getAttribute('type'), $this->getAttribute('name')), 0, $e); - } - $r = new \ReflectionFunction($closure); - - if (false !== strpos($r->name, '{closure}')) { - return $this->reflector = [$r, $callable, 'Closure']; - } - - if ($object = $r->getClosureThis()) { - $callable = [$object, $r->name]; - $callableName = (\function_exists('get_debug_type') ? get_debug_type($object) : \get_class($object)).'::'.$r->name; - } elseif ($class = $r->getClosureScopeClass()) { - $callableName = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name; - } else { - $callable = $callableName = $r->name; - } - - if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) { - $callable = $r->getClosure(); - } - - return $this->reflector = [$r, $callable, $callableName]; + return $this->getAttribute('twig_callable'); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php index 2c7bd0a27..d7db99357 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php @@ -23,14 +23,23 @@ class ConditionalExpression extends AbstractExpression public function compile(Compiler $compiler): void { - $compiler - ->raw('((') - ->subcompile($this->getNode('expr1')) - ->raw(') ? (') - ->subcompile($this->getNode('expr2')) - ->raw(') : (') - ->subcompile($this->getNode('expr3')) - ->raw('))') - ; + // Ternary with no then uses Elvis operator + if ($this->getNode('expr1') === $this->getNode('expr2')) { + $compiler + ->raw('((') + ->subcompile($this->getNode('expr1')) + ->raw(') ?: (') + ->subcompile($this->getNode('expr3')) + ->raw('))'); + } else { + $compiler + ->raw('((') + ->subcompile($this->getNode('expr1')) + ->raw(') ? (') + ->subcompile($this->getNode('expr2')) + ->raw(') : (') + ->subcompile($this->getNode('expr3')) + ->raw('))'); + } } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConstantExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConstantExpression.php index 7ddbcc6fa..2a8909d54 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConstantExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConstantExpression.php @@ -14,6 +14,9 @@ namespace Twig\Node\Expression; use Twig\Compiler; +/** + * @final + */ class ConstantExpression extends AbstractExpression { public function __construct($value, int $lineno) diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php index 6a572d488..75b6d18c2 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php @@ -11,7 +11,9 @@ namespace Twig\Node\Expression\Filter; +use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Compiler; +use Twig\Extension\CoreExtension; use Twig\Node\Expression\ConditionalExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; @@ -19,6 +21,8 @@ use Twig\Node\Expression\GetAttrExpression; use Twig\Node\Expression\NameExpression; use Twig\Node\Expression\Test\DefinedTest; use Twig\Node\Node; +use Twig\TwigFilter; +use Twig\TwigTest; /** * Returns the value or the default value when it is undefined or empty. @@ -29,20 +33,27 @@ use Twig\Node\Node; */ class DefaultFilter extends FilterExpression { - public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null) + #[FirstClassTwigCallableReady] + public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno) { - $default = new FilterExpression($node, new ConstantExpression('default', $node->getTemplateLine()), $arguments, $node->getTemplateLine()); + if ($filter instanceof TwigFilter) { + $name = $filter->getName(); + $default = new FilterExpression($node, $filter, $arguments, $node->getTemplateLine()); + } else { + $name = $filter->getAttribute('value'); + $default = new FilterExpression($node, new TwigFilter('default', [CoreExtension::class, 'default']), $arguments, $node->getTemplateLine()); + } - if ('default' === $filterName->getAttribute('value') && ($node instanceof NameExpression || $node instanceof GetAttrExpression)) { - $test = new DefinedTest(clone $node, 'defined', new Node(), $node->getTemplateLine()); - $false = \count($arguments) ? $arguments->getNode(0) : new ConstantExpression('', $node->getTemplateLine()); + if ('default' === $name && ($node instanceof NameExpression || $node instanceof GetAttrExpression)) { + $test = new DefinedTest(clone $node, new TwigTest('defined'), new Node(), $node->getTemplateLine()); + $false = \count($arguments) ? $arguments->getNode('0') : new ConstantExpression('', $node->getTemplateLine()); $node = new ConditionalExpression($test, $default, $false, $node->getTemplateLine()); } else { $node = $default; } - parent::__construct($node, $filterName, $arguments, $lineno, $tag); + parent::__construct($node, $filter, $arguments, $lineno); } public function compile(Compiler $compiler): void diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Filter/RawFilter.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Filter/RawFilter.php new file mode 100644 index 000000000..e115ab194 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Filter/RawFilter.php @@ -0,0 +1,36 @@ + + */ +class RawFilter extends FilterExpression +{ + #[FirstClassTwigCallableReady] + public function __construct(Node $node, TwigFilter|ConstantExpression|null $filter = null, ?Node $arguments = null, int $lineno = 0) + { + parent::__construct($node, $filter ?: new TwigFilter('raw', null, ['is_safe' => ['all']]), $arguments ?: new Node(), $lineno ?: $node->getTemplateLine()); + } + + public function compile(Compiler $compiler): void + { + $compiler->subcompile($this->getNode('node')); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FilterExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FilterExpression.php index 0fc158869..efc91193e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FilterExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FilterExpression.php @@ -12,28 +12,61 @@ namespace Twig\Node\Expression; +use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Compiler; +use Twig\Node\NameDeprecation; use Twig\Node\Node; +use Twig\TwigFilter; class FilterExpression extends CallExpression { - public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null) + #[FirstClassTwigCallableReady] + public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno) { - parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], [], $lineno, $tag); + if ($filter instanceof TwigFilter) { + $name = $filter->getName(); + $filterName = new ConstantExpression($name, $lineno); + } else { + $name = $filter->getAttribute('value'); + $filterName = $filter; + trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of "TwigFilter" when creating a "%s" filter of type "%s" is deprecated.', $name, static::class); + } + + parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], ['name' => $name, 'type' => 'filter'], $lineno); + + if ($filter instanceof TwigFilter) { + $this->setAttribute('twig_callable', $filter); + } + + $this->deprecateNode('filter', new NameDeprecation('twig/twig', '3.12')); + + $this->deprecateAttribute('needs_charset', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('needs_environment', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('needs_context', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('arguments', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('callable', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('is_variadic', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('dynamic_name', new NameDeprecation('twig/twig', '3.12')); } public function compile(Compiler $compiler): void { - $name = $this->getNode('filter')->getAttribute('value'); - $filter = $compiler->getEnvironment()->getFilter($name); + $name = $this->getNode('filter', false)->getAttribute('value'); + if ($name !== $this->getAttribute('name')) { + trigger_deprecation('twig/twig', '3.11', 'Changing the value of a "filter" node in a NodeVisitor class is not supported anymore.'); + $this->removeAttribute('twig_callable'); + } + if ('raw' === $name) { + trigger_deprecation('twig/twig', '3.11', 'Creating the "raw" filter via "FilterExpression" is deprecated; use "RawFilter" instead.'); - $this->setAttribute('name', $name); - $this->setAttribute('type', 'filter'); - $this->setAttribute('needs_environment', $filter->needsEnvironment()); - $this->setAttribute('needs_context', $filter->needsContext()); - $this->setAttribute('arguments', $filter->getArguments()); - $this->setAttribute('callable', $filter->getCallable()); - $this->setAttribute('is_variadic', $filter->isVariadic()); + $compiler->subcompile($this->getNode('node')); + + return; + } + + if (!$this->hasAttribute('twig_callable')) { + $this->setAttribute('twig_callable', $compiler->getEnvironment()->getFilter($name)); + } $this->compileCallable($compiler); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionExpression.php index 71269775c..6215c6abf 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionExpression.php @@ -11,32 +11,57 @@ namespace Twig\Node\Expression; +use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Compiler; +use Twig\Node\NameDeprecation; use Twig\Node\Node; +use Twig\TwigFunction; class FunctionExpression extends CallExpression { - public function __construct(string $name, Node $arguments, int $lineno) + #[FirstClassTwigCallableReady] + public function __construct(TwigFunction|string $function, Node $arguments, int $lineno) { - parent::__construct(['arguments' => $arguments], ['name' => $name, 'is_defined_test' => false], $lineno); + if ($function instanceof TwigFunction) { + $name = $function->getName(); + } else { + $name = $function; + trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of "TwigFunction" when creating a "%s" function of type "%s" is deprecated.', $name, static::class); + } + + parent::__construct(['arguments' => $arguments], ['name' => $name, 'type' => 'function', 'is_defined_test' => false], $lineno); + + if ($function instanceof TwigFunction) { + $this->setAttribute('twig_callable', $function); + } + + $this->deprecateAttribute('needs_charset', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('needs_environment', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('needs_context', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('arguments', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('callable', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('is_variadic', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('dynamic_name', new NameDeprecation('twig/twig', '3.12')); } public function compile(Compiler $compiler) { $name = $this->getAttribute('name'); - $function = $compiler->getEnvironment()->getFunction($name); - - $this->setAttribute('name', $name); - $this->setAttribute('type', 'function'); - $this->setAttribute('needs_environment', $function->needsEnvironment()); - $this->setAttribute('needs_context', $function->needsContext()); - $this->setAttribute('arguments', $function->getArguments()); - $callable = $function->getCallable(); - if ('constant' === $name && $this->getAttribute('is_defined_test')) { - $callable = 'twig_constant_is_defined'; + if ($this->hasAttribute('twig_callable')) { + $name = $this->getAttribute('twig_callable')->getName(); + if ($name !== $this->getAttribute('name')) { + trigger_deprecation('twig/twig', '3.12', 'Changing the value of a "function" node in a NodeVisitor class is not supported anymore.'); + $this->removeAttribute('twig_callable'); + } + } + + if (!$this->hasAttribute('twig_callable')) { + $this->setAttribute('twig_callable', $compiler->getEnvironment()->getFunction($name)); + } + + if ('constant' === $name && $this->getAttribute('is_defined_test')) { + $this->getNode('arguments')->setNode('checkDefined', new ConstantExpression(true, $this->getTemplateLine())); } - $this->setAttribute('callable', $callable); - $this->setAttribute('is_variadic', $function->isVariadic()); $this->compileCallable($compiler); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionNode/EnumCasesFunction.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionNode/EnumCasesFunction.php new file mode 100644 index 000000000..7e5c25ff4 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionNode/EnumCasesFunction.php @@ -0,0 +1,41 @@ +getNode('arguments'); + if ($arguments->hasNode('enum')) { + $firstArgument = $arguments->getNode('enum'); + } elseif ($arguments->hasNode('0')) { + $firstArgument = $arguments->getNode('0'); + } else { + $firstArgument = null; + } + + if (!$firstArgument instanceof ConstantExpression || 1 !== \count($arguments)) { + parent::compile($compiler); + + return; + } + + $value = $firstArgument->getAttribute('value'); + + if (!\is_string($value)) { + throw new SyntaxError('The first argument of the "enum_cases" function must be a string.', $this->getTemplateLine(), $this->getSourceContext()); + } + + if (!enum_exists($value)) { + throw new SyntaxError(\sprintf('The first argument of the "enum_cases" function must be the name of an enum, "%s" given.', $value), $this->getTemplateLine(), $this->getSourceContext()); + } + + $compiler->raw(\sprintf('%s::cases()', $value)); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php index e6a75ce94..29a446b88 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php @@ -57,7 +57,7 @@ class GetAttrExpression extends AbstractExpression return; } - $compiler->raw('twig_get_attribute($this->env, $this->source, '); + $compiler->raw('CoreExtension::getAttribute($this->env, $this->source, '); if ($this->getAttribute('ignore_strict_check')) { $this->getNode('node')->setAttribute('ignore_strict_check', true); diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/InlinePrint.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/InlinePrint.php index 1ad4751e4..0a3c2e4f9 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/InlinePrint.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/InlinePrint.php @@ -27,9 +27,8 @@ final class InlinePrint extends AbstractExpression public function compile(Compiler $compiler): void { $compiler - ->raw('print (') + ->raw('yield ') ->subcompile($this->getNode('node')) - ->raw(')') ; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php index d5ec0b6ef..01806f91d 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php @@ -39,14 +39,16 @@ class MethodCallExpression extends AbstractExpression } $compiler - ->raw('twig_call_macro($macros[') + ->raw('CoreExtension::callMacro($macros[') ->repr($this->getNode('node')->getAttribute('name')) ->raw('], ') ->repr($this->getAttribute('method')) ->raw(', [') ; $first = true; - foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) { + /** @var ArrayExpression */ + $args = $this->getNode('arguments'); + foreach ($args->getKeyValuePairs() as $pair) { if (!$first) { $compiler->raw(', '); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NameExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NameExpression.php index c3563f012..286aa5ae2 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NameExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NameExpression.php @@ -34,7 +34,7 @@ class NameExpression extends AbstractExpression $compiler->addDebugInfo($this); if ($this->getAttribute('is_defined_test')) { - if ($this->isSpecial()) { + if (isset($this->specialVars[$name])) { $compiler->repr(true); } elseif (\PHP_VERSION_ID >= 70400) { $compiler @@ -51,7 +51,7 @@ class NameExpression extends AbstractExpression ->raw(', $context))') ; } - } elseif ($this->isSpecial()) { + } elseif (isset($this->specialVars[$name])) { $compiler->raw($this->specialVars[$name]); } elseif ($this->getAttribute('always_defined')) { $compiler @@ -85,13 +85,23 @@ class NameExpression extends AbstractExpression } } + /** + * @deprecated since Twig 3.11 (to be removed in 4.0) + */ public function isSpecial() { + trigger_deprecation('twig/twig', '3.11', 'The "%s()" method is deprecated and will be removed in Twig 4.0.', __METHOD__); + return isset($this->specialVars[$this->getAttribute('name')]); } + /** + * @deprecated since Twig 3.11 (to be removed in 4.0) + */ public function isSimple() { + trigger_deprecation('twig/twig', '3.11', 'The "%s()" method is deprecated and will be removed in Twig 4.0.', __METHOD__); + return !$this->isSpecial() && !$this->getAttribute('is_defined_test'); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php index a72bc4fc6..98630f7f0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php @@ -17,17 +17,18 @@ use Twig\Node\Expression\Test\DefinedTest; use Twig\Node\Expression\Test\NullTest; use Twig\Node\Expression\Unary\NotUnary; use Twig\Node\Node; +use Twig\TwigTest; class NullCoalesceExpression extends ConditionalExpression { public function __construct(Node $left, Node $right, int $lineno) { - $test = new DefinedTest(clone $left, 'defined', new Node(), $left->getTemplateLine()); + $test = new DefinedTest(clone $left, new TwigTest('defined'), new Node(), $left->getTemplateLine()); // for "block()", we don't need the null test as the return value is always a string if (!$left instanceof BlockReferenceExpression) { $test = new AndBinary( $test, - new NotUnary(new NullTest($left, 'null', new Node(), $left->getTemplateLine()), $left->getTemplateLine()), + new NotUnary(new NullTest($left, new TwigTest('null'), new Node(), $left->getTemplateLine()), $left->getTemplateLine()), $left->getTemplateLine() ); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ParentExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ParentExpression.php index 254919718..22fe38f6a 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ParentExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ParentExpression.php @@ -21,9 +21,9 @@ use Twig\Compiler; */ class ParentExpression extends AbstractExpression { - public function __construct(string $name, int $lineno, string $tag = null) + public function __construct(string $name, int $lineno) { - parent::__construct([], ['output' => false, 'name' => $name], $lineno, $tag); + parent::__construct([], ['output' => false, 'name' => $name], $lineno); } public function compile(Compiler $compiler): void @@ -31,7 +31,7 @@ class ParentExpression extends AbstractExpression if ($this->getAttribute('output')) { $compiler ->addDebugInfo($this) - ->write('$this->displayParentBlock(') + ->write('yield from $this->yieldParentBlock(') ->string($this->getAttribute('name')) ->raw(", \$context, \$blocks);\n") ; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php index 57e9319d5..867fd0951 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php @@ -33,16 +33,16 @@ class ConstantTest extends TestExpression ->raw(' === constant(') ; - if ($this->getNode('arguments')->hasNode(1)) { + if ($this->getNode('arguments')->hasNode('1')) { $compiler ->raw('get_class(') - ->subcompile($this->getNode('arguments')->getNode(1)) + ->subcompile($this->getNode('arguments')->getNode('1')) ->raw(')."::".') ; } $compiler - ->subcompile($this->getNode('arguments')->getNode(0)) + ->subcompile($this->getNode('arguments')->getNode('0')) ->raw('))') ; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php index 3953bbbe2..24d3ee82c 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php @@ -11,6 +11,7 @@ namespace Twig\Node\Expression\Test; +use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Compiler; use Twig\Error\SyntaxError; use Twig\Node\Expression\ArrayExpression; @@ -22,6 +23,7 @@ use Twig\Node\Expression\MethodCallExpression; use Twig\Node\Expression\NameExpression; use Twig\Node\Expression\TestExpression; use Twig\Node\Node; +use Twig\TwigTest; /** * Checks if a variable is defined in the current context. @@ -35,7 +37,8 @@ use Twig\Node\Node; */ class DefinedTest extends TestExpression { - public function __construct(Node $node, string $name, ?Node $arguments, int $lineno) + #[FirstClassTwigCallableReady] + public function __construct(Node $node, TwigTest|string $name, ?Node $arguments, int $lineno) { if ($node instanceof NameExpression) { $node->setAttribute('is_defined_test', true); @@ -54,6 +57,10 @@ class DefinedTest extends TestExpression throw new SyntaxError('The "defined" test only works with simple variables.', $lineno); } + if (\is_string($name) && 'defined' !== $name) { + trigger_deprecation('twig/twig', '3.12', 'Creating a "DefinedTest" instance with a test name that is not "defined" is deprecated.'); + } + parent::__construct($node, $name, $arguments, $lineno); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php index 4cb3ee096..90d58a49a 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php @@ -29,7 +29,7 @@ class DivisiblebyTest extends TestExpression ->raw('(0 == ') ->subcompile($this->getNode('node')) ->raw(' % ') - ->subcompile($this->getNode('arguments')->getNode(0)) + ->subcompile($this->getNode('arguments')->getNode('0')) ->raw(')') ; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php index c96d2bc01..f1e24db6f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php @@ -27,7 +27,7 @@ class SameasTest extends TestExpression ->raw('(') ->subcompile($this->getNode('node')) ->raw(' === ') - ->subcompile($this->getNode('arguments')->getNode(0)) + ->subcompile($this->getNode('arguments')->getNode('0')) ->raw(')') ; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/TestExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/TestExpression.php index e518bd8f1..080d85aaa 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/TestExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/TestExpression.php @@ -11,31 +11,55 @@ namespace Twig\Node\Expression; +use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Compiler; +use Twig\Node\NameDeprecation; use Twig\Node\Node; +use Twig\TwigTest; class TestExpression extends CallExpression { - public function __construct(Node $node, string $name, ?Node $arguments, int $lineno) + #[FirstClassTwigCallableReady] + public function __construct(Node $node, string|TwigTest $test, ?Node $arguments, int $lineno) { $nodes = ['node' => $node]; if (null !== $arguments) { $nodes['arguments'] = $arguments; } - parent::__construct($nodes, ['name' => $name], $lineno); + if ($test instanceof TwigTest) { + $name = $test->getName(); + } else { + $name = $test; + trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of "TwigTest" when creating a "%s" test of type "%s" is deprecated.', $name, static::class); + } + + parent::__construct($nodes, ['name' => $name, 'type' => 'test'], $lineno); + + if ($test instanceof TwigTest) { + $this->setAttribute('twig_callable', $test); + } + + $this->deprecateAttribute('arguments', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('callable', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('is_variadic', new NameDeprecation('twig/twig', '3.12')); + $this->deprecateAttribute('dynamic_name', new NameDeprecation('twig/twig', '3.12')); } public function compile(Compiler $compiler): void { $name = $this->getAttribute('name'); - $test = $compiler->getEnvironment()->getTest($name); + if ($this->hasAttribute('twig_callable')) { + $name = $this->getAttribute('twig_callable')->getName(); + if ($name !== $this->getAttribute('name')) { + trigger_deprecation('twig/twig', '3.12', 'Changing the value of a "test" node in a NodeVisitor class is not supported anymore.'); + $this->removeAttribute('twig_callable'); + } + } - $this->setAttribute('name', $name); - $this->setAttribute('type', 'test'); - $this->setAttribute('arguments', $test->getArguments()); - $this->setAttribute('callable', $test->getCallable()); - $this->setAttribute('is_variadic', $test->isVariadic()); + if (!$this->hasAttribute('twig_callable')) { + $this->setAttribute('twig_callable', $compiler->getEnvironment()->getTest($this->getAttribute('name'))); + } $this->compileCallable($compiler); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/FlushNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/FlushNode.php index fa50a88ee..ff3bd1cf1 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/FlushNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/FlushNode.php @@ -11,6 +11,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; /** @@ -18,18 +19,22 @@ use Twig\Compiler; * * @author Fabien Potencier */ +#[YieldReady] class FlushNode extends Node { - public function __construct(int $lineno, string $tag) + public function __construct(int $lineno) { - parent::__construct([], [], $lineno, $tag); + parent::__construct([], [], $lineno); } public function compile(Compiler $compiler): void { - $compiler - ->addDebugInfo($this) - ->write("flush();\n") - ; + $compiler->addDebugInfo($this); + + if ($compiler->getEnvironment()->useYield()) { + $compiler->write("yield '';\n"); + } + + $compiler->write("flush();\n"); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/ForLoopNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/ForLoopNode.php index d5ce845a7..1f0a4f321 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/ForLoopNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/ForLoopNode.php @@ -11,6 +11,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; /** @@ -18,11 +19,12 @@ use Twig\Compiler; * * @author Fabien Potencier */ +#[YieldReady] class ForLoopNode extends Node { - public function __construct(int $lineno, string $tag = null) + public function __construct(int $lineno) { - parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno, $tag); + parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno); } public function compile(Compiler $compiler): void @@ -36,7 +38,7 @@ class ForLoopNode extends Node ->write("++\$context['loop']['index0'];\n") ->write("++\$context['loop']['index'];\n") ->write("\$context['loop']['first'] = false;\n") - ->write("if (isset(\$context['loop']['length'])) {\n") + ->write("if (isset(\$context['loop']['revindex0'], \$context['loop']['revindex'])) {\n") ->indent() ->write("--\$context['loop']['revindex0'];\n") ->write("--\$context['loop']['revindex'];\n") diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/ForNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/ForNode.php index 04addfbfe..2fc014792 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/ForNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/ForNode.php @@ -12,6 +12,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\AssignNameExpression; @@ -21,20 +22,21 @@ use Twig\Node\Expression\AssignNameExpression; * * @author Fabien Potencier */ +#[YieldReady] class ForNode extends Node { private $loop; - public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, ?Node $ifexpr, Node $body, ?Node $else, int $lineno, string $tag = null) + public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, ?Node $ifexpr, Node $body, ?Node $else, int $lineno) { - $body = new Node([$body, $this->loop = new ForLoopNode($lineno, $tag)]); + $body = new Node([$body, $this->loop = new ForLoopNode($lineno)]); $nodes = ['key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body]; if (null !== $else) { $nodes['else'] = $else; } - parent::__construct($nodes, ['with_loop' => true], $lineno, $tag); + parent::__construct($nodes, ['with_loop' => true], $lineno); } public function compile(Compiler $compiler): void @@ -42,7 +44,7 @@ class ForNode extends Node $compiler ->addDebugInfo($this) ->write("\$context['_parent'] = \$context;\n") - ->write("\$context['_seq'] = twig_ensure_traversable(") + ->write("\$context['_seq'] = CoreExtension::ensureTraversable(") ->subcompile($this->getNode('seq')) ->raw(");\n") ; @@ -99,7 +101,14 @@ class ForNode extends Node $compiler->write("\$_parent = \$context['_parent'];\n"); // remove some "private" loop variables (needed for nested loops) - $compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\'], $context[\'loop\']);'."\n"); + $compiler->write('unset($context[\'_seq\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\']'); + if ($this->hasNode('else')) { + $compiler->raw(', $context[\'_iterated\']'); + } + if ($this->getAttribute('with_loop')) { + $compiler->raw(', $context[\'loop\']'); + } + $compiler->raw(");\n"); // keep the values set in the inner context for variables defined in the outer context $compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n"); diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/IfNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/IfNode.php index 5fa20082a..2af48fa81 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/IfNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/IfNode.php @@ -12,6 +12,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; /** @@ -19,16 +20,17 @@ use Twig\Compiler; * * @author Fabien Potencier */ +#[YieldReady] class IfNode extends Node { - public function __construct(Node $tests, ?Node $else, int $lineno, string $tag = null) + public function __construct(Node $tests, ?Node $else, int $lineno) { $nodes = ['tests' => $tests]; if (null !== $else) { $nodes['else'] = $else; } - parent::__construct($nodes, [], $lineno, $tag); + parent::__construct($nodes, [], $lineno); } public function compile(Compiler $compiler): void @@ -47,11 +49,14 @@ class IfNode extends Node } $compiler - ->subcompile($this->getNode('tests')->getNode($i)) + ->subcompile($this->getNode('tests')->getNode((string) $i)) ->raw(") {\n") ->indent() - ->subcompile($this->getNode('tests')->getNode($i + 1)) ; + // The node might not exists if the content is empty + if ($this->getNode('tests')->hasNode((string) ($i + 1))) { + $compiler->subcompile($this->getNode('tests')->getNode((string) ($i + 1))); + } } if ($this->hasNode('else')) { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/ImportNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/ImportNode.php index 5378d799e..9a6033f21 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/ImportNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/ImportNode.php @@ -11,6 +11,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\NameExpression; @@ -20,11 +21,22 @@ use Twig\Node\Expression\NameExpression; * * @author Fabien Potencier */ +#[YieldReady] class ImportNode extends Node { - public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, string $tag = null, bool $global = true) + /** + * @param bool $global + */ + public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, $global = true) { - parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno, $tag); + if (null === $global || \is_string($global)) { + trigger_deprecation('twig/twig', '3.12', 'Passing a tag to %s() is deprecated.', __METHOD__); + $global = \func_num_args() > 4 ? func_get_arg(4) : true; + } elseif (!\is_bool($global)) { + throw new \TypeError(\sprintf('Argument 4 passed to "%s()" must be a boolean, "%s" given.', __METHOD__, get_debug_type($global))); + } + + parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno); } public function compile(Compiler $compiler): void diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/IncludeNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/IncludeNode.php index d540d6b23..1c18292c5 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/IncludeNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/IncludeNode.php @@ -12,6 +12,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; @@ -20,16 +21,17 @@ use Twig\Node\Expression\AbstractExpression; * * @author Fabien Potencier */ +#[YieldReady] class IncludeNode extends Node implements NodeOutputInterface { - public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null) + public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno) { $nodes = ['expr' => $expr]; if (null !== $variables) { $nodes['variables'] = $variables; } - parent::__construct($nodes, ['only' => $only, 'ignore_missing' => $ignoreMissing], $lineno, $tag); + parent::__construct($nodes, ['only' => $only, 'ignore_missing' => $ignoreMissing], $lineno); } public function compile(Compiler $compiler): void @@ -40,10 +42,10 @@ class IncludeNode extends Node implements NodeOutputInterface $template = $compiler->getVarName(); $compiler - ->write(sprintf("$%s = null;\n", $template)) + ->write(\sprintf("$%s = null;\n", $template)) ->write("try {\n") ->indent() - ->write(sprintf('$%s = ', $template)) + ->write(\sprintf('$%s = ', $template)) ; $this->addGetTemplate($compiler); @@ -56,10 +58,11 @@ class IncludeNode extends Node implements NodeOutputInterface ->write("// ignore missing template\n") ->outdent() ->write("}\n") - ->write(sprintf("if ($%s) {\n", $template)) + ->write(\sprintf("if ($%s) {\n", $template)) ->indent() - ->write(sprintf('$%s->display(', $template)) + ->write(\sprintf('yield from $%s->unwrap()->yield(', $template)) ; + $this->addTemplateArguments($compiler); $compiler ->raw(");\n") @@ -67,8 +70,9 @@ class IncludeNode extends Node implements NodeOutputInterface ->write("}\n") ; } else { + $compiler->write('yield from '); $this->addGetTemplate($compiler); - $compiler->raw('->display('); + $compiler->raw('->unwrap()->yield('); $this->addTemplateArguments($compiler); $compiler->raw(");\n"); } @@ -93,12 +97,12 @@ class IncludeNode extends Node implements NodeOutputInterface $compiler->raw(false === $this->getAttribute('only') ? '$context' : '[]'); } elseif (false === $this->getAttribute('only')) { $compiler - ->raw('twig_array_merge($context, ') + ->raw('CoreExtension::merge($context, ') ->subcompile($this->getNode('variables')) ->raw(')') ; } else { - $compiler->raw('twig_to_array('); + $compiler->raw('CoreExtension::toArray('); $compiler->subcompile($this->getNode('variables')); $compiler->raw(')'); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/MacroNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/MacroNode.php index 7f1b24d53..5a2543a9f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/MacroNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/MacroNode.php @@ -11,6 +11,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Error\SyntaxError; @@ -19,26 +20,34 @@ use Twig\Error\SyntaxError; * * @author Fabien Potencier */ +#[YieldReady] class MacroNode extends Node { public const VARARGS_NAME = 'varargs'; - public function __construct(string $name, Node $body, Node $arguments, int $lineno, string $tag = null) + /** + * @param BodyNode $body + */ + public function __construct(string $name, Node $body, Node $arguments, int $lineno) { + if (!$body instanceof BodyNode) { + trigger_deprecation('twig/twig', '3.12', \sprintf('Not passing a "%s" instance as the "body" argument of the "%s" constructor is deprecated.', BodyNode::class, static::class)); + } + foreach ($arguments as $argumentName => $argument) { if (self::VARARGS_NAME === $argumentName) { - throw new SyntaxError(sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $argument->getTemplateLine(), $argument->getSourceContext()); + throw new SyntaxError(\sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $argument->getTemplateLine(), $argument->getSourceContext()); } } - parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno, $tag); + parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno); } public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) - ->write(sprintf('public function macro_%s(', $this->getAttribute('name'))) + ->write(\sprintf('public function macro_%s(', $this->getAttribute('name'))) ; $count = \count($this->getNode('arguments')); @@ -64,7 +73,7 @@ class MacroNode extends Node ->write("{\n") ->indent() ->write("\$macros = \$this->macros;\n") - ->write("\$context = \$this->env->mergeGlobals([\n") + ->write("\$context = [\n") ->indent() ; @@ -77,35 +86,19 @@ class MacroNode extends Node ; } + $node = new CaptureNode($this->getNode('body'), $this->getNode('body')->lineno); + $compiler ->write('') ->string(self::VARARGS_NAME) ->raw(' => ') - ; - - $compiler ->raw("\$__varargs__,\n") ->outdent() - ->write("]);\n\n") + ->write("] + \$this->env->getGlobals();\n\n") ->write("\$blocks = [];\n\n") - ; - if ($compiler->getEnvironment()->isDebug()) { - $compiler->write("ob_start();\n"); - } else { - $compiler->write("ob_start(function () { return ''; });\n"); - } - $compiler - ->write("try {\n") - ->indent() - ->subcompile($this->getNode('body')) + ->write('return ') + ->subcompile($node) ->raw("\n") - ->write("return ('' === \$tmp = ob_get_contents()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n") - ->outdent() - ->write("} finally {\n") - ->indent() - ->write("ob_end_clean();\n") - ->outdent() - ->write("}\n") ->outdent() ->write("}\n\n") ; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/ModuleNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/ModuleNode.php index e972b6ba5..d2fb216b1 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/ModuleNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/ModuleNode.php @@ -12,6 +12,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\ConstantExpression; @@ -20,16 +21,24 @@ use Twig\Source; /** * Represents a module node. * - * Consider this class as being final. If you need to customize the behavior of - * the generated class, consider adding nodes to the following nodes: display_start, - * display_end, constructor_start, constructor_end, and class_end. + * If you need to customize the behavior of the generated class, add nodes to + * the following nodes: display_start, display_end, constructor_start, + * constructor_end, and class_end. * * @author Fabien Potencier */ +#[YieldReady] final class ModuleNode extends Node { + /** + * @param BodyNode $body + */ public function __construct(Node $body, ?AbstractExpression $parent, Node $blocks, Node $macros, Node $traits, $embeddedTemplates, Source $source) { + if (!$body instanceof BodyNode) { + trigger_deprecation('twig/twig', '3.12', \sprintf('Not passing a "%s" instance as the "body" argument of the "%s" constructor is deprecated.', BodyNode::class, static::class)); + } + $nodes = [ 'body' => $body, 'blocks' => $blocks, @@ -106,7 +115,7 @@ final class ModuleNode extends Node $parent = $this->getNode('parent'); $compiler - ->write("protected function doGetParent(array \$context)\n", "{\n") + ->write("protected function doGetParent(array \$context): bool|string|Template|TemplateWrapper\n", "{\n") ->indent() ->addDebugInfo($parent) ->write('return ') @@ -143,6 +152,7 @@ final class ModuleNode extends Node ->write("use Twig\Environment;\n") ->write("use Twig\Error\LoaderError;\n") ->write("use Twig\Error\RuntimeError;\n") + ->write("use Twig\Extension\CoreExtension;\n") ->write("use Twig\Extension\SandboxExtension;\n") ->write("use Twig\Markup;\n") ->write("use Twig\Sandbox\SecurityError;\n") @@ -150,7 +160,9 @@ final class ModuleNode extends Node ->write("use Twig\Sandbox\SecurityNotAllowedFilterError;\n") ->write("use Twig\Sandbox\SecurityNotAllowedFunctionError;\n") ->write("use Twig\Source;\n") - ->write("use Twig\Template;\n\n") + ->write("use Twig\Template;\n") + ->write("use Twig\TemplateWrapper;\n") + ->write("\n") ; } $compiler @@ -160,8 +172,11 @@ final class ModuleNode extends Node ->raw(" extends Template\n") ->write("{\n") ->indent() - ->write("private \$source;\n") - ->write("private \$macros = [];\n\n") + ->write("private Source \$source;\n") + ->write("/**\n") + ->write(" * @var array\n") + ->write(" */\n") + ->write("private array \$macros = [];\n\n") ; } @@ -188,14 +203,14 @@ final class ModuleNode extends Node $compiler ->addDebugInfo($node) - ->write(sprintf('$_trait_%s = $this->loadTemplate(', $i)) + ->write(\sprintf('$_trait_%s = $this->loadTemplate(', $i)) ->subcompile($node) ->raw(', ') ->repr($node->getTemplateName()) ->raw(', ') ->repr($node->getTemplateLine()) ->raw(");\n") - ->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i)) + ->write(\sprintf("if (!\$_trait_%s->unwrap()->isTraitable()) {\n", $i)) ->indent() ->write("throw new RuntimeError('Template \"'.") ->subcompile($trait->getNode('template')) @@ -204,12 +219,12 @@ final class ModuleNode extends Node ->raw(", \$this->source);\n") ->outdent() ->write("}\n") - ->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i)) + ->write(\sprintf("\$_trait_%s_blocks = \$_trait_%s->unwrap()->getBlocks();\n\n", $i, $i)) ; foreach ($trait->getNode('targets') as $key => $value) { $compiler - ->write(sprintf('if (!isset($_trait_%s_blocks[', $i)) + ->write(\sprintf('if (!isset($_trait_%s_blocks[', $i)) ->string($key) ->raw("])) {\n") ->indent() @@ -223,11 +238,11 @@ final class ModuleNode extends Node ->outdent() ->write("}\n\n") - ->write(sprintf('$_trait_%s_blocks[', $i)) + ->write(\sprintf('$_trait_%s_blocks[', $i)) ->subcompile($value) - ->raw(sprintf('] = $_trait_%s_blocks[', $i)) + ->raw(\sprintf('] = $_trait_%s_blocks[', $i)) ->string($key) - ->raw(sprintf(']; unset($_trait_%s_blocks[', $i)) + ->raw(\sprintf(']; unset($_trait_%s_blocks[', $i)) ->string($key) ->raw("]);\n\n") ; @@ -242,7 +257,7 @@ final class ModuleNode extends Node for ($i = 0; $i < $countTraits; ++$i) { $compiler - ->write(sprintf('$_trait_%s_blocks'.($i == $countTraits - 1 ? '' : ',')."\n", $i)) + ->write(\sprintf('$_trait_%s_blocks'.($i == $countTraits - 1 ? '' : ',')."\n", $i)) ; } @@ -275,7 +290,7 @@ final class ModuleNode extends Node foreach ($this->getNode('blocks') as $name => $node) { $compiler - ->write(sprintf("'%s' => [\$this, 'block_%s'],\n", $name, $name)) + ->write(\sprintf("'%s' => [\$this, 'block_%s'],\n", $name, $name)) ; } @@ -303,7 +318,7 @@ final class ModuleNode extends Node protected function compileDisplay(Compiler $compiler) { $compiler - ->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n") + ->write("protected function doDisplay(array \$context, array \$blocks = []): iterable\n", "{\n") ->indent() ->write("\$macros = \$this->macros;\n") ->subcompile($this->getNode('display_start')) @@ -324,15 +339,24 @@ final class ModuleNode extends Node ->repr($parent->getTemplateLine()) ->raw(");\n") ; - $compiler->write('$this->parent'); - } else { - $compiler->write('$this->getParent($context)'); } - $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n"); + $compiler->write('yield from '); + + if ($parent instanceof ConstantExpression) { + $compiler->raw('$this->parent'); + } else { + $compiler->raw('$this->getParent($context)'); + } + $compiler->raw("->unwrap()->yield(\$context, array_merge(\$this->blocks, \$blocks));\n"); + } + + $compiler->subcompile($this->getNode('display_end')); + + if (!$this->hasNode('parent')) { + $compiler->write("yield from [];\n"); } $compiler - ->subcompile($this->getNode('display_end')) ->outdent() ->write("}\n\n") ; @@ -355,7 +379,10 @@ final class ModuleNode extends Node protected function compileGetTemplateName(Compiler $compiler) { $compiler - ->write("public function getTemplateName()\n", "{\n") + ->write("/**\n") + ->write(" * @codeCoverageIgnore\n") + ->write(" */\n") + ->write("public function getTemplateName(): string\n", "{\n") ->indent() ->write('return ') ->repr($this->getSourceContext()->getName()) @@ -377,7 +404,7 @@ final class ModuleNode extends Node $traitable = !$this->hasNode('parent') && 0 === \count($this->getNode('macros')); if ($traitable) { if ($this->getNode('body') instanceof BodyNode) { - $nodes = $this->getNode('body')->getNode(0); + $nodes = $this->getNode('body')->getNode('0'); } else { $nodes = $this->getNode('body'); } @@ -391,14 +418,6 @@ final class ModuleNode extends Node continue; } - if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) { - continue; - } - - if ($node instanceof BlockReferenceNode) { - continue; - } - $traitable = false; break; } @@ -409,9 +428,12 @@ final class ModuleNode extends Node } $compiler - ->write("public function isTraitable()\n", "{\n") + ->write("/**\n") + ->write(" * @codeCoverageIgnore\n") + ->write(" */\n") + ->write("public function isTraitable(): bool\n", "{\n") ->indent() - ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false')) + ->write("return false;\n") ->outdent() ->write("}\n\n") ; @@ -420,9 +442,12 @@ final class ModuleNode extends Node protected function compileDebugInfo(Compiler $compiler) { $compiler - ->write("public function getDebugInfo()\n", "{\n") + ->write("/**\n") + ->write(" * @codeCoverageIgnore\n") + ->write(" */\n") + ->write("public function getDebugInfo(): array\n", "{\n") ->indent() - ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true)))) + ->write(\sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true)))) ->outdent() ->write("}\n\n") ; @@ -431,7 +456,7 @@ final class ModuleNode extends Node protected function compileGetSourceContext(Compiler $compiler) { $compiler - ->write("public function getSourceContext()\n", "{\n") + ->write("public function getSourceContext(): Source\n", "{\n") ->indent() ->write('return new Source(') ->string($compiler->getEnvironment()->isDebug() ? $this->getSourceContext()->getCode() : '') @@ -449,7 +474,7 @@ final class ModuleNode extends Node { if ($node instanceof ConstantExpression) { $compiler - ->write(sprintf('%s = $this->loadTemplate(', $var)) + ->write(\sprintf('%s = $this->loadTemplate(', $var)) ->subcompile($node) ->raw(', ') ->repr($node->getTemplateName()) diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/NameDeprecation.php b/data/web/inc/lib/vendor/twig/twig/src/Node/NameDeprecation.php new file mode 100644 index 000000000..63ab28576 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/NameDeprecation.php @@ -0,0 +1,46 @@ + + */ +class NameDeprecation +{ + private $package; + private $version; + private $newName; + + public function __construct(string $package = '', string $version = '', string $newName = '') + { + $this->package = $package; + $this->version = $version; + $this->newName = $newName; + } + + public function getPackage(): string + { + return $this->package; + } + + public function getVersion(): string + { + return $this->version; + } + + public function getNewName(): string + { + return $this->newName; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Node.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Node.php index c0558b9af..2ccbcf610 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Node.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Node.php @@ -12,6 +12,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Source; @@ -20,61 +21,82 @@ use Twig\Source; * * @author Fabien Potencier */ +#[YieldReady] class Node implements \Countable, \IteratorAggregate { + /** + * @var array + */ protected $nodes; protected $attributes; protected $lineno; protected $tag; - private $name; private $sourceContext; + /** @var array */ + private $nodeNameDeprecations = []; + /** @var array */ + private $attributeNameDeprecations = []; /** - * @param array $nodes An array of named nodes - * @param array $attributes An array of attributes (should not be nodes) - * @param int $lineno The line number - * @param string $tag The tag name associated with the Node + * @param array $nodes An array of named nodes + * @param array $attributes An array of attributes (should not be nodes) + * @param int $lineno The line number */ - public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0, string $tag = null) + public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0) { foreach ($nodes as $name => $node) { if (!$node instanceof self) { - throw new \InvalidArgumentException(sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, static::class)); + throw new \InvalidArgumentException(\sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', \is_object($node) ? $node::class : (null === $node ? 'null' : \gettype($node)), $name, static::class)); } } $this->nodes = $nodes; $this->attributes = $attributes; $this->lineno = $lineno; - $this->tag = $tag; + + if (\func_num_args() > 3) { + trigger_deprecation('twig/twig', '3.12', \sprintf('The "tag" constructor argument of the "%s" class is deprecated and ignored (check which TokenParser class set it to "%s"), the tag is now automatically set by the Parser when needed.', static::class, func_get_arg(3) ?: 'null')); + } } public function __toString() { - $attributes = []; - foreach ($this->attributes as $name => $value) { - $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true))); + $repr = static::class; + + if ($this->tag) { + $repr .= \sprintf("\n tag: %s", $this->tag); } - $repr = [static::class.'('.implode(', ', $attributes)]; + $attributes = []; + foreach ($this->attributes as $name => $value) { + if (\is_callable($value)) { + $v = '\Closure'; + } elseif ($value instanceof \Stringable) { + $v = (string) $value; + } else { + $v = str_replace("\n", '', var_export($value, true)); + } + $attributes[] = \sprintf('%s: %s', $name, $v); + } + + if ($attributes) { + $repr .= \sprintf("\n attributes:\n %s", implode("\n ", $attributes)); + } if (\count($this->nodes)) { + $repr .= "\n nodes:"; foreach ($this->nodes as $name => $node) { - $len = \strlen($name) + 4; + $len = \strlen($name) + 6; $noderepr = []; foreach (explode("\n", (string) $node) as $line) { $noderepr[] = str_repeat(' ', $len).$line; } - $repr[] = sprintf(' %s: %s', $name, ltrim(implode("\n", $noderepr))); + $repr .= \sprintf("\n %s: %s", $name, ltrim(implode("\n", $noderepr))); } - - $repr[] = ')'; - } else { - $repr[0] .= ')'; } - return implode("\n", $repr); + return $repr; } /** @@ -83,7 +105,7 @@ class Node implements \Countable, \IteratorAggregate public function compile(Compiler $compiler) { foreach ($this->nodes as $node) { - $node->compile($compiler); + $compiler->subcompile($node); } } @@ -97,6 +119,18 @@ class Node implements \Countable, \IteratorAggregate return $this->tag; } + /** + * @internal + */ + public function setNodeTag(string $tag): void + { + if ($this->tag) { + throw new \LogicException('The tag of a node can only be set once.'); + } + + $this->tag = $tag; + } + public function hasAttribute(string $name): bool { return \array_key_exists($name, $this->attributes); @@ -105,7 +139,17 @@ class Node implements \Countable, \IteratorAggregate public function getAttribute(string $name) { if (!\array_key_exists($name, $this->attributes)) { - throw new \LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class)); + throw new \LogicException(\sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class)); + } + + $triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true; + if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) { + $dep = $this->attributeNameDeprecations[$name]; + if ($dep->getNewName()) { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute "%s" on a "%s" class is deprecated, get the "%s" attribute instead.', $name, static::class, $dep->getNewName()); + } else { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute "%s" on a "%s" class is deprecated.', $name, static::class); + } } return $this->attributes[$name]; @@ -113,38 +157,96 @@ class Node implements \Countable, \IteratorAggregate public function setAttribute(string $name, $value): void { + $triggerDeprecation = \func_num_args() > 2 ? func_get_arg(2) : true; + if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) { + $dep = $this->attributeNameDeprecations[$name]; + if ($dep->getNewName()) { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute "%s" on a "%s" class is deprecated, set the "%s" attribute instead.', $name, static::class, $dep->getNewName()); + } else { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute "%s" on a "%s" class is deprecated.', $name, static::class); + } + } + $this->attributes[$name] = $value; } + public function deprecateAttribute(string $name, NameDeprecation $dep): void + { + $this->attributeNameDeprecations[$name] = $dep; + } + public function removeAttribute(string $name): void { unset($this->attributes[$name]); } + /** + * @param string|int $name + */ public function hasNode(string $name): bool { return isset($this->nodes[$name]); } + /** + * @param string|int $name + */ public function getNode(string $name): self { if (!isset($this->nodes[$name])) { - throw new \LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, static::class)); + throw new \LogicException(\sprintf('Node "%s" does not exist for Node "%s".', $name, static::class)); + } + + $triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true; + if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) { + $dep = $this->nodeNameDeprecations[$name]; + if ($dep->getNewName()) { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node "%s" on a "%s" class is deprecated, get the "%s" node instead.', $name, static::class, $dep->getNewName()); + } else { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node "%s" on a "%s" class is deprecated.', $name, static::class); + } } return $this->nodes[$name]; } + /** + * @param string|int $name + */ public function setNode(string $name, self $node): void { + $triggerDeprecation = \func_num_args() > 2 ? func_get_arg(2) : true; + if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) { + $dep = $this->nodeNameDeprecations[$name]; + if ($dep->getNewName()) { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node "%s" on a "%s" class is deprecated, set the "%s" node instead.', $name, static::class, $dep->getNewName()); + } else { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node "%s" on a "%s" class is deprecated.', $name, static::class); + } + } + + if (null !== $this->sourceContext) { + $node->setSourceContext($this->sourceContext); + } $this->nodes[$name] = $node; } + /** + * @param string|int $name + */ public function removeNode(string $name): void { unset($this->nodes[$name]); } + /** + * @param string|int $name + */ + public function deprecateNode(string $name, NameDeprecation $dep): void + { + $this->nodeNameDeprecations[$name] = $dep; + } + /** * @return int */ diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/PrintNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/PrintNode.php index 60386d299..e3c23bbfa 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/PrintNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/PrintNode.php @@ -12,6 +12,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; @@ -20,19 +21,23 @@ use Twig\Node\Expression\AbstractExpression; * * @author Fabien Potencier */ +#[YieldReady] class PrintNode extends Node implements NodeOutputInterface { - public function __construct(AbstractExpression $expr, int $lineno, string $tag = null) + public function __construct(AbstractExpression $expr, int $lineno) { - parent::__construct(['expr' => $expr], [], $lineno, $tag); + parent::__construct(['expr' => $expr], [], $lineno); } public function compile(Compiler $compiler): void { + /** @var AbstractExpression */ + $expr = $this->getNode('expr'); + $compiler ->addDebugInfo($this) - ->write('echo ') - ->subcompile($this->getNode('expr')) + ->write($expr->isGenerator() ? 'yield from ' : 'yield ') + ->subcompile($expr) ->raw(";\n") ; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/SandboxNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/SandboxNode.php index 4d5666bff..d51cea44b 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/SandboxNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/SandboxNode.php @@ -11,6 +11,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; /** @@ -18,11 +19,12 @@ use Twig\Compiler; * * @author Fabien Potencier */ +#[YieldReady] class SandboxNode extends Node { - public function __construct(Node $body, int $lineno, string $tag = null) + public function __construct(Node $body, int $lineno) { - parent::__construct(['body' => $body], [], $lineno, $tag); + parent::__construct(['body' => $body], [], $lineno); } public function compile(Compiler $compiler): void diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/SetNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/SetNode.php index 96b6bd8bf..67725104e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/SetNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/SetNode.php @@ -11,6 +11,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\ConstantExpression; @@ -19,26 +20,28 @@ use Twig\Node\Expression\ConstantExpression; * * @author Fabien Potencier */ +#[YieldReady] class SetNode extends Node implements NodeCaptureInterface { - public function __construct(bool $capture, Node $names, Node $values, int $lineno, string $tag = null) + public function __construct(bool $capture, Node $names, Node $values, int $lineno) { - parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => false], $lineno, $tag); - /* * Optimizes the node when capture is used for a large block of text. * * {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig\Markup("foo"); */ - if ($this->getAttribute('capture')) { - $this->setAttribute('safe', true); - - $values = $this->getNode('values'); + $safe = false; + if ($capture) { + $safe = true; if ($values instanceof TextNode) { - $this->setNode('values', new ConstantExpression($values->getAttribute('data'), $values->getTemplateLine())); - $this->setAttribute('capture', false); + $values = new ConstantExpression($values->getAttribute('data'), $values->getTemplateLine()); + $capture = false; + } else { + $values = new CaptureNode($values, $values->getTemplateLine()); } } + + parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => $safe], $lineno); } public function compile(Compiler $compiler): void @@ -46,7 +49,7 @@ class SetNode extends Node implements NodeCaptureInterface $compiler->addDebugInfo($this); if (\count($this->getNode('names')) > 1) { - $compiler->write('list('); + $compiler->write('['); foreach ($this->getNode('names') as $idx => $node) { if ($idx) { $compiler->raw(', '); @@ -54,29 +57,15 @@ class SetNode extends Node implements NodeCaptureInterface $compiler->subcompile($node); } - $compiler->raw(')'); + $compiler->raw(']'); } else { - if ($this->getAttribute('capture')) { - if ($compiler->getEnvironment()->isDebug()) { - $compiler->write("ob_start();\n"); - } else { - $compiler->write("ob_start(function () { return ''; });\n"); - } - $compiler - ->subcompile($this->getNode('values')) - ; - } - $compiler->subcompile($this->getNode('names'), false); - - if ($this->getAttribute('capture')) { - $compiler->raw(" = ('' === \$tmp = ob_get_clean()) ? '' : new Markup(\$tmp, \$this->env->getCharset())"); - } } + $compiler->raw(' = '); - if (!$this->getAttribute('capture')) { - $compiler->raw(' = '); - + if ($this->getAttribute('capture')) { + $compiler->subcompile($this->getNode('values')); + } else { if (\count($this->getNode('names')) > 1) { $compiler->write('['); foreach ($this->getNode('values') as $idx => $value) { @@ -98,8 +87,10 @@ class SetNode extends Node implements NodeCaptureInterface $compiler->subcompile($this->getNode('values')); } } + + $compiler->raw(';'); } - $compiler->raw(";\n"); + $compiler->raw("\n"); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/TextNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/TextNode.php index d74ebe630..fae65fb2c 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/TextNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/TextNode.php @@ -12,6 +12,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; /** @@ -19,6 +20,7 @@ use Twig\Compiler; * * @author Fabien Potencier */ +#[YieldReady] class TextNode extends Node implements NodeOutputInterface { public function __construct(string $data, int $lineno) @@ -28,9 +30,10 @@ class TextNode extends Node implements NodeOutputInterface public function compile(Compiler $compiler): void { + $compiler->addDebugInfo($this); + $compiler - ->addDebugInfo($this) - ->write('echo ') + ->write('yield ') ->string($this->getAttribute('data')) ->raw(";\n") ; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/TypesNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/TypesNode.php new file mode 100644 index 000000000..ebb304d49 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/TypesNode.php @@ -0,0 +1,28 @@ + + */ +#[YieldReady] +class TypesNode extends Node +{ + /** + * @param array $types + */ + public function __construct(array $types, int $lineno) + { + parent::__construct([], ['mapping' => $types], $lineno); + } + + public function compile(Compiler $compiler) + { + // Don't compile anything. + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/WithNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/WithNode.php index 56a334496..487e2800b 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/WithNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/WithNode.php @@ -11,6 +11,7 @@ namespace Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; /** @@ -18,16 +19,17 @@ use Twig\Compiler; * * @author Fabien Potencier */ +#[YieldReady] class WithNode extends Node { - public function __construct(Node $body, ?Node $variables, bool $only, int $lineno, string $tag = null) + public function __construct(Node $body, ?Node $variables, bool $only, int $lineno) { $nodes = ['body' => $body]; if (null !== $variables) { $nodes['variables'] = $variables; } - parent::__construct($nodes, ['only' => $only], $lineno, $tag); + parent::__construct($nodes, ['only' => $only], $lineno); } public function compile(Compiler $compiler): void @@ -36,35 +38,35 @@ class WithNode extends Node $parentContextName = $compiler->getVarName(); - $compiler->write(sprintf("\$%s = \$context;\n", $parentContextName)); + $compiler->write(\sprintf("\$%s = \$context;\n", $parentContextName)); if ($this->hasNode('variables')) { $node = $this->getNode('variables'); $varsName = $compiler->getVarName(); $compiler - ->write(sprintf('$%s = ', $varsName)) + ->write(\sprintf('$%s = ', $varsName)) ->subcompile($node) ->raw(";\n") - ->write(sprintf("if (!twig_test_iterable(\$%s)) {\n", $varsName)) + ->write(\sprintf("if (!is_iterable(\$%s)) {\n", $varsName)) ->indent() - ->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a hash.', ") + ->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a mapping.', ") ->repr($node->getTemplateLine()) ->raw(", \$this->getSourceContext());\n") ->outdent() ->write("}\n") - ->write(sprintf("\$%s = twig_to_array(\$%s);\n", $varsName, $varsName)) + ->write(\sprintf("\$%s = CoreExtension::toArray(\$%s);\n", $varsName, $varsName)) ; if ($this->getAttribute('only')) { $compiler->write("\$context = [];\n"); } - $compiler->write(sprintf("\$context = \$this->env->mergeGlobals(array_merge(\$context, \$%s));\n", $varsName)); + $compiler->write(\sprintf("\$context = \$%s + \$context + \$this->env->getGlobals();\n", $varsName)); } $compiler ->subcompile($this->getNode('body')) - ->write(sprintf("\$context = \$%s;\n", $parentContextName)) + ->write(\sprintf("\$context = \$%s;\n", $parentContextName)) ; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php index d7036ae55..5de35fd09 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php @@ -17,9 +17,9 @@ use Twig\Node\Node; /** * Used to make node visitors compatible with Twig 1.x and 2.x. * - * To be removed in Twig 3.1. - * * @author Fabien Potencier + * + * @deprecated since 3.9 (to be removed in 4.0) */ abstract class AbstractNodeVisitor implements NodeVisitorInterface { diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php index fe56ea307..32f49ab1e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php @@ -57,7 +57,7 @@ final class EscaperNodeVisitor implements NodeVisitorInterface } elseif ($node instanceof AutoEscapeNode) { $this->statusStack[] = $node->getAttribute('value'); } elseif ($node instanceof BlockNode) { - $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env); + $this->statusStack[] = $this->blocks[$node->getAttribute('name')] ?? $this->needEscaping(); } elseif ($node instanceof ImportNode) { $this->safeVars[] = $node->getNode('var')->getAttribute('name'); } @@ -73,7 +73,7 @@ final class EscaperNodeVisitor implements NodeVisitorInterface $this->blocks = []; } elseif ($node instanceof FilterExpression) { return $this->preEscapeFilterNode($node, $env); - } elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping($env)) { + } elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping()) { $expression = $node->getNode('expr'); if ($expression instanceof ConditionalExpression && $this->shouldUnwrapConditional($expression, $env, $type)) { return new DoNode($this->unwrapConditional($expression, $env, $type), $expression->getTemplateLine()); @@ -85,7 +85,7 @@ final class EscaperNodeVisitor implements NodeVisitorInterface if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) { array_pop($this->statusStack); } elseif ($node instanceof BlockReferenceNode) { - $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env); + $this->blocks[$node->getAttribute('name')] = $this->needEscaping(); } return $node; @@ -126,15 +126,11 @@ final class EscaperNodeVisitor implements NodeVisitorInterface return $node; } - return new InlinePrint($this->getEscaperFilter($type, $expression), $node->getTemplateLine()); + return new InlinePrint($this->getEscaperFilter($env, $type, $expression), $node->getTemplateLine()); } private function escapePrintNode(PrintNode $node, Environment $env, string $type): Node { - if (false === $type) { - return $node; - } - $expression = $node->getNode('expr'); if ($this->isSafeFor($type, $expression, $env)) { @@ -143,14 +139,19 @@ final class EscaperNodeVisitor implements NodeVisitorInterface $class = \get_class($node); - return new $class($this->getEscaperFilter($type, $expression), $node->getTemplateLine()); + return new $class($this->getEscaperFilter($env, $type, $expression), $node->getTemplateLine()); } private function preEscapeFilterNode(FilterExpression $filter, Environment $env): FilterExpression { - $name = $filter->getNode('filter')->getAttribute('value'); + if ($filter->hasAttribute('twig_callable')) { + $type = $filter->getAttribute('twig_callable')->getPreEscape(); + } else { + // legacy + $name = $filter->getNode('filter', false)->getAttribute('value'); + $type = $env->getFilter($name)->getPreEscape(); + } - $type = $env->getFilter($name)->getPreEscape(); if (null === $type) { return $filter; } @@ -160,7 +161,7 @@ final class EscaperNodeVisitor implements NodeVisitorInterface return $filter; } - $filter->setNode('node', $this->getEscaperFilter($type, $node)); + $filter->setNode('node', $this->getEscaperFilter($env, $type, $node)); return $filter; } @@ -183,22 +184,22 @@ final class EscaperNodeVisitor implements NodeVisitorInterface return \in_array($type, $safe) || \in_array('all', $safe); } - private function needEscaping(Environment $env) + private function needEscaping() { if (\count($this->statusStack)) { return $this->statusStack[\count($this->statusStack) - 1]; } - return $this->defaultStrategy ? $this->defaultStrategy : false; + return $this->defaultStrategy ?: false; } - private function getEscaperFilter(string $type, Node $node): FilterExpression + private function getEscaperFilter(Environment $env, string $type, Node $node): FilterExpression { $line = $node->getTemplateLine(); - $name = new ConstantExpression('escape', $line); + $filter = $env->getFilter('escape'); $args = new Node([new ConstantExpression($type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]); - return new FilterExpression($node, $name, $args, $line); + return new FilterExpression($node, $filter, $args, $line); } public function getPriority(): int diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php index af477e653..01d5a997f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php @@ -46,14 +46,14 @@ final class MacroAutoImportNodeVisitor implements NodeVisitorInterface if ($node instanceof ModuleNode) { $this->inAModule = false; if ($this->hasMacroCalls) { - $node->getNode('constructor_end')->setNode('_auto_macro_import', new ImportNode(new NameExpression('_self', 0), new AssignNameExpression('_self', 0), 0, 'import', true)); + $node->getNode('constructor_end')->setNode('_auto_macro_import', new ImportNode(new NameExpression('_self', 0), new AssignNameExpression('_self', 0), 0, true)); } } elseif ($this->inAModule) { if ( - $node instanceof GetAttrExpression && - $node->getNode('node') instanceof NameExpression && - '_self' === $node->getNode('node')->getAttribute('name') && - $node->getNode('attribute') instanceof ConstantExpression + $node instanceof GetAttrExpression + && $node->getNode('node') instanceof NameExpression + && '_self' === $node->getNode('node')->getAttribute('name') + && $node->getNode('attribute') instanceof ConstantExpression ) { $this->hasMacroCalls = true; diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php index 7ac75e41a..a943f45c3 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php @@ -15,7 +15,6 @@ use Twig\Environment; use Twig\Node\BlockReferenceNode; use Twig\Node\Expression\BlockReferenceExpression; use Twig\Node\Expression\ConstantExpression; -use Twig\Node\Expression\FilterExpression; use Twig\Node\Expression\FunctionExpression; use Twig\Node\Expression\GetAttrExpression; use Twig\Node\Expression\NameExpression; @@ -24,6 +23,7 @@ use Twig\Node\ForNode; use Twig\Node\IncludeNode; use Twig\Node\Node; use Twig\Node\PrintNode; +use Twig\Node\TextNode; /** * Tries to optimize the AST. @@ -43,27 +43,34 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface public const OPTIMIZE_NONE = 0; public const OPTIMIZE_FOR = 2; public const OPTIMIZE_RAW_FILTER = 4; + public const OPTIMIZE_TEXT_NODES = 8; private $loops = []; private $loopsTargets = []; - private $optimizers; /** * @param int $optimizers The optimizer mode */ - public function __construct(int $optimizers = -1) - { - if ($optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER)) { - throw new \InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers)); + public function __construct( + private int $optimizers = -1, + ) { + if ($optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_TEXT_NODES)) { + throw new \InvalidArgumentException(\sprintf('Optimizer mode "%s" is not valid.', $optimizers)); } - $this->optimizers = $optimizers; + if (-1 !== $optimizers && self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $optimizers)) { + trigger_deprecation('twig/twig', '3.11', 'The "Twig\NodeVisitor\OptimizerNodeVisitor::OPTIMIZE_RAW_FILTER" option is deprecated and does nothing.'); + } + + if (-1 !== $optimizers && self::OPTIMIZE_TEXT_NODES === (self::OPTIMIZE_TEXT_NODES & $optimizers)) { + trigger_deprecation('twig/twig', '3.12', 'The "Twig\NodeVisitor\OptimizerNodeVisitor::OPTIMIZE_TEXT_NODES" option is deprecated and does nothing.'); + } } public function enterNode(Node $node, Environment $env): Node { if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { - $this->enterOptimizeFor($node, $env); + $this->enterOptimizeFor($node); } return $node; @@ -72,14 +79,10 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface public function leaveNode(Node $node, Environment $env): ?Node { if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { - $this->leaveOptimizeFor($node, $env); + $this->leaveOptimizeFor($node); } - if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) { - $node = $this->optimizeRawFilter($node, $env); - } - - $node = $this->optimizePrintNode($node, $env); + $node = $this->optimizePrintNode($node); return $node; } @@ -91,16 +94,21 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface * * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()" */ - private function optimizePrintNode(Node $node, Environment $env): Node + private function optimizePrintNode(Node $node): Node { if (!$node instanceof PrintNode) { return $node; } $exprNode = $node->getNode('expr'); + + if ($exprNode instanceof ConstantExpression && \is_string($exprNode->getAttribute('value'))) { + return new TextNode($exprNode->getAttribute('value'), $exprNode->getTemplateLine()); + } + if ( - $exprNode instanceof BlockReferenceExpression || - $exprNode instanceof ParentExpression + $exprNode instanceof BlockReferenceExpression + || $exprNode instanceof ParentExpression ) { $exprNode->setAttribute('output', true); @@ -110,22 +118,10 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface return $node; } - /** - * Removes "raw" filters. - */ - private function optimizeRawFilter(Node $node, Environment $env): Node - { - if ($node instanceof FilterExpression && 'raw' == $node->getNode('filter')->getAttribute('value')) { - return $node->getNode('node'); - } - - return $node; - } - /** * Optimizes "for" tag by removing the "loop" variable creation whenever possible. */ - private function enterOptimizeFor(Node $node, Environment $env): void + private function enterOptimizeFor(Node $node): void { if ($node instanceof ForNode) { // disable the loop variable by default @@ -166,7 +162,7 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface && 'include' === $node->getAttribute('name') && (!$node->getNode('arguments')->hasNode('with_context') || false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value') - ) + ) ) { $this->addLoopToAll(); } @@ -175,12 +171,12 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface elseif ($node instanceof GetAttrExpression && (!$node->getNode('attribute') instanceof ConstantExpression || 'parent' === $node->getNode('attribute')->getAttribute('value') - ) + ) && (true === $this->loops[0]->getAttribute('with_loop') - || ($node->getNode('node') instanceof NameExpression - && 'loop' === $node->getNode('node')->getAttribute('name') - ) - ) + || ($node->getNode('node') instanceof NameExpression + && 'loop' === $node->getNode('node')->getAttribute('name') + ) + ) ) { $this->addLoopToAll(); } @@ -189,7 +185,7 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface /** * Optimizes "for" tag by removing the "loop" variable creation whenever possible. */ - private function leaveOptimizeFor(Node $node, Environment $env): void + private function leaveOptimizeFor(Node $node): void { if ($node instanceof ForNode) { array_shift($this->loops); diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php index 90d6f2e0f..07672164e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php @@ -96,10 +96,15 @@ final class SafeAnalysisNodeVisitor implements NodeVisitorInterface $this->setSafe($node, $safe); } elseif ($node instanceof FilterExpression) { // filter expression is safe when the filter is safe - $name = $node->getNode('filter')->getAttribute('value'); - $args = $node->getNode('arguments'); - if ($filter = $env->getFilter($name)) { - $safe = $filter->getSafe($args); + if ($node->hasAttribute('twig_callable')) { + $filter = $node->getAttribute('twig_callable'); + } else { + // legacy + $filter = $env->getFilter($node->getAttribute('name')); + } + + if ($filter) { + $safe = $filter->getSafe($node->getNode('arguments')); if (null === $safe) { $safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety()); } @@ -109,10 +114,15 @@ final class SafeAnalysisNodeVisitor implements NodeVisitorInterface } } elseif ($node instanceof FunctionExpression) { // function expression is safe when the function is safe - $name = $node->getAttribute('name'); - $args = $node->getNode('arguments'); - if ($function = $env->getFunction($name)) { - $this->setSafe($node, $function->getSafe($args)); + if ($node->hasAttribute('twig_callable')) { + $function = $node->getAttribute('twig_callable'); + } else { + // legacy + $function = $env->getFunction($node->getAttribute('name')); + } + + if ($function) { + $this->setSafe($node, $function->getSafe($node->getNode('arguments'))); } else { $this->setSafe($node, []); } @@ -136,7 +146,7 @@ final class SafeAnalysisNodeVisitor implements NodeVisitorInterface return $node; } - private function intersectSafe(array $a = null, array $b = null): array + private function intersectSafe(?array $a = null, ?array $b = null): array { if (null === $a || null === $b) { return []; diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php index 1446cee6b..37e184a3e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php @@ -34,8 +34,11 @@ use Twig\Node\SetNode; final class SandboxNodeVisitor implements NodeVisitorInterface { private $inAModule = false; + /** @var array */ private $tags; + /** @var array */ private $filters; + /** @var array */ private $functions; private $needsToStringWrap = false; @@ -51,22 +54,22 @@ final class SandboxNodeVisitor implements NodeVisitorInterface } elseif ($this->inAModule) { // look for tags if ($node->getNodeTag() && !isset($this->tags[$node->getNodeTag()])) { - $this->tags[$node->getNodeTag()] = $node; + $this->tags[$node->getNodeTag()] = $node->getTemplateLine(); } // look for filters - if ($node instanceof FilterExpression && !isset($this->filters[$node->getNode('filter')->getAttribute('value')])) { - $this->filters[$node->getNode('filter')->getAttribute('value')] = $node; + if ($node instanceof FilterExpression && !isset($this->filters[$node->getAttribute('name')])) { + $this->filters[$node->getAttribute('name')] = $node->getTemplateLine(); } // look for functions if ($node instanceof FunctionExpression && !isset($this->functions[$node->getAttribute('name')])) { - $this->functions[$node->getAttribute('name')] = $node; + $this->functions[$node->getAttribute('name')] = $node->getTemplateLine(); } // the .. operator is equivalent to the range() function if ($node instanceof RangeBinary && !isset($this->functions['range'])) { - $this->functions['range'] = $node; + $this->functions['range'] = $node->getTemplateLine(); } if ($node instanceof PrintNode) { @@ -116,7 +119,7 @@ final class SandboxNodeVisitor implements NodeVisitorInterface private function wrapNode(Node $node, string $name): void { $expr = $node->getNode($name); - if ($expr instanceof NameExpression || $expr instanceof GetAttrExpression) { + if (($expr instanceof NameExpression || $expr instanceof GetAttrExpression) && !$expr->isGenerator()) { $node->setNode($name, new CheckToStringNode($expr)); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/YieldNotReadyNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/YieldNotReadyNodeVisitor.php new file mode 100644 index 000000000..4b190b414 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/YieldNotReadyNodeVisitor.php @@ -0,0 +1,59 @@ +yieldReadyNodes[$class])) { + return $node; + } + + if (!$this->yieldReadyNodes[$class] = (bool) (new \ReflectionClass($class))->getAttributes(YieldReady::class)) { + if ($this->useYield) { + throw new \LogicException(\sprintf('You cannot enable the "use_yield" option of Twig as node "%s" is not marked as ready for it; please make it ready and then flag it with the #[YieldReady] attribute.', $class)); + } + + trigger_deprecation('twig/twig', '3.9', 'Twig node "%s" is not marked as ready for using "yield" instead of "echo"; please make it ready and then flag it with the #[YieldReady] attribute.', $class); + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + return $node; + } + + public function getPriority(): int + { + return 255; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Parser.php b/data/web/inc/lib/vendor/twig/twig/src/Parser.php index 4428208fe..40370bb1b 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Parser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Parser.php @@ -25,6 +25,7 @@ use Twig\Node\NodeOutputInterface; use Twig\Node\PrintNode; use Twig\Node\TextNode; use Twig\TokenParser\TokenParserInterface; +use Twig\Util\ReflectionCallable; /** * @author Fabien Potencier @@ -39,20 +40,19 @@ class Parser private $blocks; private $blockStack; private $macros; - private $env; private $importedSymbols; private $traits; private $embeddedTemplates = []; private $varNameSalt = 0; - public function __construct(Environment $env) - { - $this->env = $env; + public function __construct( + private Environment $env, + ) { } public function getVarName(): string { - return sprintf('__internal_parse_%d', $this->varNameSalt++); + return \sprintf('__internal_parse_%d', $this->varNameSalt++); } public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode @@ -91,7 +91,7 @@ class Parser } if (!$e->getTemplateLine()) { - $e->setTemplateLine($this->stream->getCurrent()->getLine()); + $e->setTemplateLine($this->getCurrentToken()->getLine()); } throw $e; @@ -101,6 +101,9 @@ class Parser $traverser = new NodeTraverser($this->env, $this->visitors); + /** + * @var ModuleNode $node + */ $node = $traverser->traverse($node); // restore previous stack so previous parse() call can resume working @@ -117,23 +120,23 @@ class Parser $rv = []; while (!$this->stream->isEOF()) { switch ($this->getCurrentToken()->getType()) { - case /* Token::TEXT_TYPE */ 0: + case Token::TEXT_TYPE: $token = $this->stream->next(); $rv[] = new TextNode($token->getValue(), $token->getLine()); break; - case /* Token::VAR_START_TYPE */ 2: + case Token::VAR_START_TYPE: $token = $this->stream->next(); $expr = $this->expressionParser->parseExpression(); - $this->stream->expect(/* Token::VAR_END_TYPE */ 4); + $this->stream->expect(Token::VAR_END_TYPE); $rv[] = new PrintNode($expr, $token->getLine()); break; - case /* Token::BLOCK_START_TYPE */ 1: + case Token::BLOCK_START_TYPE: $this->stream->next(); $token = $this->getCurrentToken(); - if (/* Token::NAME_TYPE */ 5 !== $token->getType()) { + if (Token::NAME_TYPE !== $token->getType()) { throw new SyntaxError('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext()); } @@ -151,13 +154,14 @@ class Parser if (!$subparser = $this->env->getTokenParser($token->getValue())) { if (null !== $test) { - $e = new SyntaxError(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); + $e = new SyntaxError(\sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); - if (\is_array($test) && isset($test[0]) && $test[0] instanceof TokenParserInterface) { - $e->appendMessage(sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno)); + $callable = (new ReflectionCallable(new TwigTest('decision', $test)))->getCallable(); + if (\is_array($callable) && $callable[0] instanceof TokenParserInterface) { + $e->appendMessage(\sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $callable[0]->getTag(), $lineno)); } } else { - $e = new SyntaxError(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); + $e = new SyntaxError(\sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); $e->addSuggestions($token->getValue(), array_keys($this->env->getTokenParsers())); } @@ -168,13 +172,16 @@ class Parser $subparser->setParser($this); $node = $subparser->parse($token); - if (null !== $node) { + if (!$node) { + trigger_deprecation('twig/twig', '3.12', 'Returning "null" from "%s" is deprecated and forbidden by "TokenParserInterface".', $subparser::class); + } else { + $node->setNodeTag($subparser->getTag()); $rv[] = $node; } break; default: - throw new SyntaxError('Lexer or parser ended up in unsupported state.', $this->getCurrentToken()->getLine(), $this->stream->getSourceContext()); + throw new SyntaxError('The lexer or the parser ended up in an unsupported state.', $this->getCurrentToken()->getLine(), $this->stream->getSourceContext()); } } @@ -187,6 +194,8 @@ class Parser public function getBlockStack(): array { + trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__); + return $this->blockStack; } @@ -207,21 +216,31 @@ class Parser public function hasBlock(string $name): bool { + trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__); + return isset($this->blocks[$name]); } public function getBlock(string $name): Node { + trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__); + return $this->blocks[$name]; } public function setBlock(string $name, BlockNode $value): void { + if (isset($this->blocks[$name])) { + throw new SyntaxError(\sprintf("The block '%s' has already been defined line %d.", $name, $this->blocks[$name]->getTemplateLine()), $this->getCurrentToken()->getLine(), $this->blocks[$name]->getSourceContext()); + } + $this->blocks[$name] = new BodyNode([$value], [], $value->getTemplateLine()); } public function hasMacro(string $name): bool { + trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__); + return isset($this->macros[$name]); } @@ -237,6 +256,8 @@ class Parser public function hasTraits(): bool { + trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__); + return \count($this->traits) > 0; } @@ -247,7 +268,7 @@ class Parser $this->embeddedTemplates[] = $template; } - public function addImportedSymbol(string $type, string $alias, string $name = null, AbstractExpression $node = null): void + public function addImportedSymbol(string $type, string $alias, ?string $name = null, ?AbstractExpression $node = null): void { $this->importedSymbols[0][$type][$alias] = ['name' => $name, 'node' => $node]; } @@ -280,11 +301,26 @@ class Parser public function getParent(): ?Node { + trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__); + return $this->parent; } + public function hasInheritance() + { + return $this->parent || 0 < \count($this->traits); + } + public function setParent(?Node $parent): void { + if (null === $parent) { + trigger_deprecation('twig/twig', '3.12', 'Passing "null" to "%s()" is deprecated.', __METHOD__); + } + + if (null !== $this->parent) { + throw new SyntaxError('Multiple extends tags are forbidden.', $parent->getTemplateLine(), $parent->getSourceContext()); + } + $this->parent = $parent; } @@ -303,10 +339,9 @@ class Parser // check that the body does not contain non-empty output nodes if ( ($node instanceof TextNode && !ctype_space($node->getAttribute('data'))) - || - (!$node instanceof TextNode && !$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface) + || (!$node instanceof TextNode && !$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface) ) { - if (false !== strpos((string) $node, \chr(0xEF).\chr(0xBB).\chr(0xBF))) { + if (str_contains((string) $node, \chr(0xEF).\chr(0xBB).\chr(0xBF))) { $t = substr($node->getAttribute('data'), 3); if ('' === $t || ctype_space($t)) { // bypass empty nodes starting with a BOM diff --git a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php index 4da43e475..267718c1f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php @@ -50,7 +50,7 @@ abstract class BaseDumper if ($profile->getDuration() * 1000 < 1) { $str = $start."\n"; } else { - $str = sprintf("%s %s\n", $start, $this->formatTime($profile, $percent)); + $str = \sprintf("%s %s\n", $start, $this->formatTime($profile, $percent)); } $nCount = \count($profile->getProfiles()); diff --git a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php index 03abe0fa0..bb3fbb52a 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php @@ -24,7 +24,7 @@ final class BlackfireDumper $this->dumpProfile('main()', $profile, $data); $this->dumpChildren('main()', $profile, $data); - $start = sprintf('%f', microtime(true)); + $start = \sprintf('%f', microtime(true)); $str = <<isTemplate()) { $name = $p->getTemplate(); } else { - $name = sprintf('%s::%s(%s)', $p->getTemplate(), $p->getType(), $p->getName()); + $name = \sprintf('%s::%s(%s)', $p->getTemplate(), $p->getType(), $p->getName()); } - $this->dumpProfile(sprintf('%s==>%s', $parent, $name), $p, $data); + $this->dumpProfile(\sprintf('%s==>%s', $parent, $name), $p, $data); $this->dumpChildren($name, $p, $data); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php index 1f2433b4d..cdab2de59 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php @@ -32,16 +32,16 @@ final class HtmlDumper extends BaseDumper protected function formatTemplate(Profile $profile, $prefix): string { - return sprintf('%s└ %s', $prefix, self::$colors['template'], $profile->getTemplate()); + return \sprintf('%s└ %s', $prefix, self::$colors['template'], $profile->getTemplate()); } protected function formatNonTemplate(Profile $profile, $prefix): string { - return sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), isset(self::$colors[$profile->getType()]) ? self::$colors[$profile->getType()] : 'auto', $profile->getName()); + return \sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), self::$colors[$profile->getType()] ?? 'auto', $profile->getName()); } protected function formatTime(Profile $profile, $percent): string { - return sprintf('%.2fms/%.0f%%', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent); + return \sprintf('%.2fms/%.0f%%', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php index 31561c466..1c1f77e94 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php @@ -20,16 +20,16 @@ final class TextDumper extends BaseDumper { protected function formatTemplate(Profile $profile, $prefix): string { - return sprintf('%s└ %s', $prefix, $profile->getTemplate()); + return \sprintf('%s└ %s', $prefix, $profile->getTemplate()); } protected function formatNonTemplate(Profile $profile, $prefix): string { - return sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), $profile->getName()); + return \sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), $profile->getName()); } protected function formatTime(Profile $profile, $percent): string { - return sprintf('%.2fms/%.0f%%', $profile->getDuration() * 1000, $percent); + return \sprintf('%.2fms/%.0f%%', $profile->getDuration() * 1000, $percent); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php index 1494baf44..4d8e504d1 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php @@ -11,6 +11,7 @@ namespace Twig\Profiler\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Node; @@ -19,6 +20,7 @@ use Twig\Node\Node; * * @author Fabien Potencier */ +#[YieldReady] class EnterProfileNode extends Node { public function __construct(string $extensionName, string $type, string $name, string $varName) @@ -29,10 +31,10 @@ class EnterProfileNode extends Node public function compile(Compiler $compiler): void { $compiler - ->write(sprintf('$%s = $this->extensions[', $this->getAttribute('var_name'))) + ->write(\sprintf('$%s = $this->extensions[', $this->getAttribute('var_name'))) ->repr($this->getAttribute('extension_name')) ->raw("];\n") - ->write(sprintf('$%s->enter($%s = new \Twig\Profiler\Profile($this->getTemplateName(), ', $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) + ->write(\sprintf('$%s->enter($%s = new \Twig\Profiler\Profile($this->getTemplateName(), ', $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) ->repr($this->getAttribute('type')) ->raw(', ') ->repr($this->getAttribute('name')) diff --git a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php index 94cebbaa8..bd9227e52 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php @@ -11,6 +11,7 @@ namespace Twig\Profiler\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Node; @@ -19,6 +20,7 @@ use Twig\Node\Node; * * @author Fabien Potencier */ +#[YieldReady] class LeaveProfileNode extends Node { public function __construct(string $varName) @@ -30,7 +32,7 @@ class LeaveProfileNode extends Node { $compiler ->write("\n") - ->write(sprintf("\$%s->leave(\$%s);\n\n", $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) + ->write(\sprintf("\$%s->leave(\$%s);\n\n", $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) ; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php index 91abee807..1458bc5fc 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php @@ -27,13 +27,12 @@ use Twig\Profiler\Profile; */ final class ProfilerNodeVisitor implements NodeVisitorInterface { - private $extensionName; private $varName; - public function __construct(string $extensionName) - { - $this->extensionName = $extensionName; - $this->varName = sprintf('__internal_%s', hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $extensionName)); + public function __construct( + private string $extensionName, + ) { + $this->varName = \sprintf('__internal_%s', hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $extensionName)); } public function enterNode(Node $node, Environment $env): Node diff --git a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Profile.php b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Profile.php index 252ca9b0c..2928e1646 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Profile.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Profile.php @@ -20,19 +20,16 @@ final class Profile implements \IteratorAggregate, \Serializable public const BLOCK = 'block'; public const TEMPLATE = 'template'; public const MACRO = 'macro'; - - private $template; - private $name; - private $type; private $starts = []; private $ends = []; private $profiles = []; - public function __construct(string $template = 'main', string $type = self::ROOT, string $name = 'main') - { - $this->template = $template; - $this->type = $type; - $this->name = 0 === strpos($name, '__internal_') ? 'INTERNAL' : $name; + public function __construct( + private string $template = 'main', + private string $type = self::ROOT, + private string $name = 'main', + ) { + $this->name = str_starts_with($name, '__internal_') ? 'INTERNAL' : $name; $this->enter(); } @@ -176,6 +173,6 @@ final class Profile implements \IteratorAggregate, \Serializable */ public function __unserialize(array $data): void { - list($this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles) = $data; + [$this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles] = $data; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Resources/core.php b/data/web/inc/lib/vendor/twig/twig/src/Resources/core.php new file mode 100644 index 000000000..6e2fcfb07 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Resources/core.php @@ -0,0 +1,541 @@ +getCharset(), $values, $max); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_date_format_filter(Environment $env, $date, $format = null, $timezone = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return $env->getExtension(CoreExtension::class)->formatDate($date, $format, $timezone); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_date_modify_filter(Environment $env, $date, $modifier) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return $env->getExtension(CoreExtension::class)->modifyDate($date, $modifier); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_sprintf($format, ...$values) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::sprintf($format, ...$values); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_date_converter(Environment $env, $date = null, $timezone = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return $env->getExtension(CoreExtension::class)->convertDate($date, $timezone); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_replace_filter($str, $from) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::replace($str, $from); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_round($value, $precision = 0, $method = 'common') +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::round($value, $precision, $method); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_number_format_filter(Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return $env->getExtension(CoreExtension::class)->formatNumber($number, $decimal, $decimalPoint, $thousandSep); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_urlencode_filter($url) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::urlencode($url); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_merge(...$arrays) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::merge(...$arrays); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_slice(Environment $env, $item, $start, $length = null, $preserveKeys = false) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::slice($env->getCharset(), $item, $start, $length, $preserveKeys); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_first(Environment $env, $item) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::first($env->getCharset(), $item); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_last(Environment $env, $item) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::last($env->getCharset(), $item); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_join_filter($value, $glue = '', $and = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::join($value, $glue, $and); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_split_filter(Environment $env, $value, $delimiter, $limit = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::split($env->getCharset(), $value, $delimiter, $limit); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_get_array_keys_filter($array) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::keys($array); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_reverse_filter(Environment $env, $item, $preserveKeys = false) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::reverse($env->getCharset(), $item, $preserveKeys); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_sort_filter(Environment $env, $array, $arrow = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::sort($env, $array, $arrow); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_matches(string $regexp, ?string $str) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::matches($regexp, $str); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_trim_filter($string, $characterMask = null, $side = 'both') +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::trim($string, $characterMask, $side); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_nl2br($string) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::nl2br($string); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_spaceless($content) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::spaceless($content); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_convert_encoding($string, $to, $from) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::convertEncoding($string, $to, $from); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_length_filter(Environment $env, $thing) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::length($env->getCharset(), $thing); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_upper_filter(Environment $env, $string) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::upper($env->getCharset(), $string); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_lower_filter(Environment $env, $string) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::lower($env->getCharset(), $string); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_striptags($string, $allowable_tags = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::striptags($string, $allowable_tags); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_title_string_filter(Environment $env, $string) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::titleCase($env->getCharset(), $string); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_capitalize_string_filter(Environment $env, $string) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::capitalize($env->getCharset(), $string); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_test_empty($value) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::testEmpty($value); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_test_iterable($value) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return is_iterable($value); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::include($env, $context, $template, $variables, $withContext, $ignoreMissing, $sandboxed); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_source(Environment $env, $name, $ignoreMissing = false) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::source($env, $name, $ignoreMissing); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_constant($constant, $object = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::constant($constant, $object); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_constant_is_defined($constant, $object = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::constant($constant, $object, true); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_batch($items, $size, $fill = null, $preserveKeys = true) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::batch($items, $size, $fill, $preserveKeys); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_column($array, $name, $index = null): array +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::column($array, $name, $index); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_filter(Environment $env, $array, $arrow) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::filter($env, $array, $arrow); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_map(Environment $env, $array, $arrow) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::map($env, $array, $arrow); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_reduce(Environment $env, $array, $arrow, $initial = null) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::reduce($env, $array, $arrow, $initial); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_some(Environment $env, $array, $arrow) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::arraySome($env, $array, $arrow); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_array_every(Environment $env, $array, $arrow) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::arrayEvery($env, $array, $arrow); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_check_arrow_in_sandbox(Environment $env, $arrow, $thing, $type) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return CoreExtension::checkArrowInSandbox($env, $arrow, $thing, $type); +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Resources/debug.php b/data/web/inc/lib/vendor/twig/twig/src/Resources/debug.php new file mode 100644 index 000000000..104b4f4e0 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Resources/debug.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Twig\Environment; +use Twig\Extension\DebugExtension; + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_var_dump(Environment $env, $context, ...$vars) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + DebugExtension::dump($env, $context, ...$vars); +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Resources/escaper.php b/data/web/inc/lib/vendor/twig/twig/src/Resources/escaper.php new file mode 100644 index 000000000..a2ee8e7aa --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Resources/escaper.php @@ -0,0 +1,51 @@ +getRuntime(EscaperRuntime::class)->escape($string, $strategy, $charset, $autoescape); +} + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_escape_filter_is_safe(Node $filterArgs) +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return EscaperExtension::escapeFilterIsSafe($filterArgs); +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Resources/string_loader.php b/data/web/inc/lib/vendor/twig/twig/src/Resources/string_loader.php new file mode 100644 index 000000000..8f0e6492a --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Resources/string_loader.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Twig\Environment; +use Twig\Extension\StringLoaderExtension; +use Twig\TemplateWrapper; + +/** + * @internal + * + * @deprecated since Twig 3.9 + */ +function twig_template_from_string(Environment $env, $template, ?string $name = null): TemplateWrapper +{ + trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); + + return StringLoaderExtension::templateFromString($env, $template, $name); +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Runtime/EscaperRuntime.php b/data/web/inc/lib/vendor/twig/twig/src/Runtime/EscaperRuntime.php new file mode 100644 index 000000000..a3ce17146 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Runtime/EscaperRuntime.php @@ -0,0 +1,327 @@ + */ + private $escapers = []; + + /** @internal */ + public $safeClasses = []; + + /** @internal */ + public $safeLookup = []; + + public function __construct( + private $charset = 'UTF-8', + ) { + } + + /** + * Defines a new escaper to be used via the escape filter. + * + * @param string $strategy The strategy name that should be used as a strategy in the escape call + * @param callable(string $string, string $charset): string $callable A valid PHP callable + */ + public function setEscaper($strategy, callable $callable) + { + $this->escapers[$strategy] = $callable; + } + + /** + * Gets all defined escapers. + * + * @return array An array of escapers + */ + public function getEscapers() + { + return $this->escapers; + } + + public function setSafeClasses(array $safeClasses = []) + { + $this->safeClasses = []; + $this->safeLookup = []; + foreach ($safeClasses as $class => $strategies) { + $this->addSafeClass($class, $strategies); + } + } + + public function addSafeClass(string $class, array $strategies) + { + $class = ltrim($class, '\\'); + if (!isset($this->safeClasses[$class])) { + $this->safeClasses[$class] = []; + } + $this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies); + + foreach ($strategies as $strategy) { + $this->safeLookup[$strategy][$class] = true; + } + } + + /** + * Escapes a string. + * + * @param mixed $string The value to be escaped + * @param string $strategy The escaping strategy + * @param string|null $charset The charset + * @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) + * + * @throws RuntimeError + */ + public function escape($string, string $strategy = 'html', ?string $charset = null, bool $autoescape = false) + { + if ($autoescape && $string instanceof Markup) { + return $string; + } + + if (!\is_string($string)) { + if ($string instanceof \Stringable) { + if ($autoescape) { + $c = \get_class($string); + if (!isset($this->safeClasses[$c])) { + $this->safeClasses[$c] = []; + foreach (class_parents($string) + class_implements($string) as $class) { + if (isset($this->safeClasses[$class])) { + $this->safeClasses[$c] = array_unique(array_merge($this->safeClasses[$c], $this->safeClasses[$class])); + foreach ($this->safeClasses[$class] as $s) { + $this->safeLookup[$s][$c] = true; + } + } + } + } + if (isset($this->safeLookup[$strategy][$c]) || isset($this->safeLookup['all'][$c])) { + return (string) $string; + } + } + + $string = (string) $string; + } elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) { + // we return the input as is (which can be of any type) + return $string; + } + } + + if ('' === $string) { + return ''; + } + + $charset = $charset ?: $this->charset; + + switch ($strategy) { + case 'html': + // see https://www.php.net/htmlspecialchars + + // Using a static variable to avoid initializing the array + // each time the function is called. Moving the declaration on the + // top of the function slow downs other escaping strategies. + static $htmlspecialcharsCharsets = [ + 'ISO-8859-1' => true, 'ISO8859-1' => true, + 'ISO-8859-15' => true, 'ISO8859-15' => true, + 'utf-8' => true, 'UTF-8' => true, + 'CP866' => true, 'IBM866' => true, '866' => true, + 'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true, + '1251' => true, + 'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true, + 'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true, + 'BIG5' => true, '950' => true, + 'GB2312' => true, '936' => true, + 'BIG5-HKSCS' => true, + 'SHIFT_JIS' => true, 'SJIS' => true, '932' => true, + 'EUC-JP' => true, 'EUCJP' => true, + 'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true, + ]; + + if (isset($htmlspecialcharsCharsets[$charset])) { + return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset); + } + + if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) { + // cache the lowercase variant for future iterations + $htmlspecialcharsCharsets[$charset] = true; + + return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset); + } + + $string = $this->convertEncoding($string, 'UTF-8', $charset); + $string = htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'); + + return iconv('UTF-8', $charset, $string); + + case 'js': + // escape all non-alphanumeric characters + // into their \x or \uHHHH representations + if ('UTF-8' !== $charset) { + $string = $this->convertEncoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) { + $char = $matches[0]; + + /* + * A few characters have short escape sequences in JSON and JavaScript. + * Escape sequences supported only by JavaScript, not JSON, are omitted. + * \" is also supported but omitted, because the resulting string is not HTML safe. + */ + static $shortMap = [ + '\\' => '\\\\', + '/' => '\\/', + "\x08" => '\b', + "\x0C" => '\f', + "\x0A" => '\n', + "\x0D" => '\r', + "\x09" => '\t', + ]; + + if (isset($shortMap[$char])) { + return $shortMap[$char]; + } + + $codepoint = mb_ord($char, 'UTF-8'); + if (0x10000 > $codepoint) { + return \sprintf('\u%04X', $codepoint); + } + + // Split characters outside the BMP into surrogate pairs + // https://tools.ietf.org/html/rfc2781.html#section-2.1 + $u = $codepoint - 0x10000; + $high = 0xD800 | ($u >> 10); + $low = 0xDC00 | ($u & 0x3FF); + + return \sprintf('\u%04X\u%04X', $high, $low); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'css': + if ('UTF-8' !== $charset) { + $string = $this->convertEncoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) { + $char = $matches[0]; + + return \sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8')); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'html_attr': + if ('UTF-8' !== $charset) { + $string = $this->convertEncoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) { + /** + * This function is adapted from code coming from Zend Framework. + * + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com) + * @license https://framework.zend.com/license/new-bsd New BSD License + */ + $chr = $matches[0]; + $ord = \ord($chr); + + /* + * The following replaces characters undefined in HTML with the + * hex entity for the Unicode replacement character. + */ + if (($ord <= 0x1F && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7F && $ord <= 0x9F)) { + return '�'; + } + + /* + * Check if the current character to escape has a name entity we should + * replace it with while grabbing the hex value of the character. + */ + if (1 === \strlen($chr)) { + /* + * While HTML supports far more named entities, the lowest common denominator + * has become HTML5's XML Serialisation which is restricted to the those named + * entities that XML supports. Using HTML entities would result in this error: + * XML Parsing Error: undefined entity + */ + static $entityMap = [ + 34 => '"', /* quotation mark */ + 38 => '&', /* ampersand */ + 60 => '<', /* less-than sign */ + 62 => '>', /* greater-than sign */ + ]; + + if (isset($entityMap[$ord])) { + return $entityMap[$ord]; + } + + return \sprintf('&#x%02X;', $ord); + } + + /* + * Per OWASP recommendations, we'll use hex entities for any other + * characters where a named entity does not exist. + */ + return \sprintf('&#x%04X;', mb_ord($chr, 'UTF-8')); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'url': + return rawurlencode($string); + + default: + if (\array_key_exists($strategy, $this->escapers)) { + return $this->escapers[$strategy]($string, $charset); + } + + $validStrategies = implode('", "', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($this->escapers))); + + throw new RuntimeError(\sprintf('Invalid escaping strategy "%s" (valid ones: "%s").', $strategy, $validStrategies)); + } + } + + private function convertEncoding(string $string, string $to, string $from) + { + if (!\function_exists('iconv')) { + throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); + } + + return iconv($from, $to, $string); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php b/data/web/inc/lib/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php index b360d7bea..05106680c 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php +++ b/data/web/inc/lib/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php @@ -23,11 +23,9 @@ use Psr\Container\ContainerInterface; */ class ContainerRuntimeLoader implements RuntimeLoaderInterface { - private $container; - - public function __construct(ContainerInterface $container) - { - $this->container = $container; + public function __construct( + private ContainerInterface $container, + ) { } public function load(string $class) diff --git a/data/web/inc/lib/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php b/data/web/inc/lib/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php index 130648392..5d4e70b92 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php +++ b/data/web/inc/lib/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php @@ -18,14 +18,12 @@ namespace Twig\RuntimeLoader; */ class FactoryRuntimeLoader implements RuntimeLoaderInterface { - private $map; - /** * @param array $map An array where keys are class names and values factory callables */ - public function __construct(array $map = []) - { - $this->map = $map; + public function __construct( + private array $map = [], + ) { } public function load(string $class) diff --git a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityPolicy.php b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityPolicy.php index 2fc0d0131..988e37216 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityPolicy.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityPolicy.php @@ -68,19 +68,25 @@ final class SecurityPolicy implements SecurityPolicyInterface { foreach ($tags as $tag) { if (!\in_array($tag, $this->allowedTags)) { - throw new SecurityNotAllowedTagError(sprintf('Tag "%s" is not allowed.', $tag), $tag); + if ('extends' === $tag) { + trigger_deprecation('twig/twig', '3.12', 'The "extends" tag is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed.'); + } elseif ('use' === $tag) { + trigger_deprecation('twig/twig', '3.12', 'The "use" tag is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed.'); + } else { + throw new SecurityNotAllowedTagError(\sprintf('Tag "%s" is not allowed.', $tag), $tag); + } } } foreach ($filters as $filter) { if (!\in_array($filter, $this->allowedFilters)) { - throw new SecurityNotAllowedFilterError(sprintf('Filter "%s" is not allowed.', $filter), $filter); + throw new SecurityNotAllowedFilterError(\sprintf('Filter "%s" is not allowed.', $filter), $filter); } } foreach ($functions as $function) { if (!\in_array($function, $this->allowedFunctions)) { - throw new SecurityNotAllowedFunctionError(sprintf('Function "%s" is not allowed.', $function), $function); + throw new SecurityNotAllowedFunctionError(\sprintf('Function "%s" is not allowed.', $function), $function); } } } @@ -94,16 +100,15 @@ final class SecurityPolicy implements SecurityPolicyInterface $allowed = false; $method = strtr($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); foreach ($this->allowedMethods as $class => $methods) { - if ($obj instanceof $class) { - $allowed = \in_array($method, $methods); - + if ($obj instanceof $class && \in_array($method, $methods)) { + $allowed = true; break; } } if (!$allowed) { $class = \get_class($obj); - throw new SecurityNotAllowedMethodError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method); + throw new SecurityNotAllowedMethodError(\sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method); } } @@ -111,16 +116,15 @@ final class SecurityPolicy implements SecurityPolicyInterface { $allowed = false; foreach ($this->allowedProperties as $class => $properties) { - if ($obj instanceof $class) { - $allowed = \in_array($property, \is_array($properties) ? $properties : [$properties]); - + if ($obj instanceof $class && \in_array($property, \is_array($properties) ? $properties : [$properties])) { + $allowed = true; break; } } if (!$allowed) { $class = \get_class($obj); - throw new SecurityNotAllowedPropertyError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property); + throw new SecurityNotAllowedPropertyError(\sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property); } } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SourcePolicyInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SourcePolicyInterface.php new file mode 100644 index 000000000..b952f1ea6 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SourcePolicyInterface.php @@ -0,0 +1,24 @@ +code = $code; - $this->name = $name; - $this->path = $path; + public function __construct( + private string $code, + private string $name, + private string $path = '', + ) { } public function getCode(): string diff --git a/data/web/inc/lib/vendor/twig/twig/src/Template.php b/data/web/inc/lib/vendor/twig/twig/src/Template.php index e04bd04a6..7b3ce8161 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Template.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Template.php @@ -35,38 +35,36 @@ abstract class Template protected $parent; protected $parents = []; - protected $env; protected $blocks = []; protected $traits = []; protected $extensions = []; protected $sandbox; - public function __construct(Environment $env) - { - $this->env = $env; + private $useYield; + + public function __construct( + protected Environment $env, + ) { + $this->useYield = $env->useYield(); $this->extensions = $env->getExtensions(); } /** * Returns the template name. - * - * @return string The template name */ - abstract public function getTemplateName(); + abstract public function getTemplateName(): string; /** * Returns debug information about the template. * - * @return array Debug information + * @return array Debug information */ - abstract public function getDebugInfo(); + abstract public function getDebugInfo(): array; /** * Returns information about the original template source code. - * - * @return Source */ - abstract public function getSourceContext(); + abstract public function getSourceContext(): Source; /** * Returns the parent template. @@ -74,18 +72,16 @@ abstract class Template * This method is for internal use only and should never be called * directly. * - * @return Template|TemplateWrapper|false The parent template or false if there is no parent + * @return self|TemplateWrapper|false The parent template or false if there is no parent */ - public function getParent(array $context) + public function getParent(array $context): self|TemplateWrapper|false { if (null !== $this->parent) { return $this->parent; } try { - $parent = $this->doGetParent($context); - - if (false === $parent) { + if (!$parent = $this->doGetParent($context)) { return false; } @@ -106,12 +102,12 @@ abstract class Template return $this->parents[$parent]; } - protected function doGetParent(array $context) + protected function doGetParent(array $context): bool|string|self|TemplateWrapper { return false; } - public function isTraitable() + public function isTraitable(): bool { return true; } @@ -126,14 +122,10 @@ abstract class Template * @param array $context The context * @param array $blocks The current set of blocks */ - public function displayParentBlock($name, array $context, array $blocks = []) + public function displayParentBlock($name, array $context, array $blocks = []): void { - if (isset($this->traits[$name])) { - $this->traits[$name][0]->displayBlock($name, $context, $blocks, false); - } elseif (false !== $parent = $this->getParent($context)) { - $parent->displayBlock($name, $context, $blocks, false); - } else { - throw new RuntimeError(sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext()); + foreach ($this->yieldParentBlock($name, $context, $blocks) as $data) { + echo $data; } } @@ -148,51 +140,10 @@ abstract class Template * @param array $blocks The current set of blocks * @param bool $useBlocks Whether to use the current set of blocks */ - public function displayBlock($name, array $context, array $blocks = [], $useBlocks = true, self $templateContext = null) + public function displayBlock($name, array $context, array $blocks = [], $useBlocks = true, ?self $templateContext = null): void { - if ($useBlocks && isset($blocks[$name])) { - $template = $blocks[$name][0]; - $block = $blocks[$name][1]; - } elseif (isset($this->blocks[$name])) { - $template = $this->blocks[$name][0]; - $block = $this->blocks[$name][1]; - } else { - $template = null; - $block = null; - } - - // avoid RCEs when sandbox is enabled - if (null !== $template && !$template instanceof self) { - throw new \LogicException('A block must be a method on a \Twig\Template instance.'); - } - - if (null !== $template) { - try { - $template->$block($context, $blocks); - } catch (Error $e) { - if (!$e->getSourceContext()) { - $e->setSourceContext($template->getSourceContext()); - } - - // this is mostly useful for \Twig\Error\LoaderError exceptions - // see \Twig\Error\LoaderError - if (-1 === $e->getTemplateLine()) { - $e->guess(); - } - - throw $e; - } catch (\Exception $e) { - $e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e); - $e->guess(); - - throw $e; - } - } elseif (false !== $parent = $this->getParent($context)) { - $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false, $templateContext ?? $this); - } elseif (isset($blocks[$name])) { - throw new RuntimeError(sprintf('Block "%s" should not call parent() in "%s" as the block does not exist in the parent template "%s".', $name, $blocks[$name][0]->getTemplateName(), $this->getTemplateName()), -1, $blocks[$name][0]->getSourceContext()); - } else { - throw new RuntimeError(sprintf('Block "%s" on template "%s" does not exist.', $name, $this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext()); + foreach ($this->yieldBlock($name, $context, $blocks, $useBlocks, $templateContext) as $data) { + echo $data; } } @@ -208,16 +159,25 @@ abstract class Template * * @return string The rendered block */ - public function renderParentBlock($name, array $context, array $blocks = []) + public function renderParentBlock($name, array $context, array $blocks = []): string { - if ($this->env->isDebug()) { - ob_start(); - } else { - ob_start(function () { return ''; }); - } - $this->displayParentBlock($name, $context, $blocks); + if (!$this->useYield) { + if ($this->env->isDebug()) { + ob_start(); + } else { + ob_start(function () { return ''; }); + } + $this->displayParentBlock($name, $context, $blocks); - return ob_get_clean(); + return ob_get_clean(); + } + + $content = ''; + foreach ($this->yieldParentBlock($name, $context, $blocks) as $data) { + $content .= $data; + } + + return $content; } /** @@ -233,16 +193,34 @@ abstract class Template * * @return string The rendered block */ - public function renderBlock($name, array $context, array $blocks = [], $useBlocks = true) + public function renderBlock($name, array $context, array $blocks = [], $useBlocks = true): string { - if ($this->env->isDebug()) { - ob_start(); - } else { - ob_start(function () { return ''; }); - } - $this->displayBlock($name, $context, $blocks, $useBlocks); + if (!$this->useYield) { + $level = ob_get_level(); + if ($this->env->isDebug()) { + ob_start(); + } else { + ob_start(function () { return ''; }); + } + try { + $this->displayBlock($name, $context, $blocks, $useBlocks); + } catch (\Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } - return ob_get_clean(); + throw $e; + } + + return ob_get_clean(); + } + + $content = ''; + foreach ($this->yieldBlock($name, $context, $blocks, $useBlocks) as $data) { + $content .= $data; + } + + return $content; } /** @@ -257,7 +235,7 @@ abstract class Template * * @return bool true if the block exists, false otherwise */ - public function hasBlock($name, array $context, array $blocks = []) + public function hasBlock($name, array $context, array $blocks = []): bool { if (isset($blocks[$name])) { return $blocks[$name][0] instanceof self; @@ -267,7 +245,7 @@ abstract class Template return true; } - if (false !== $parent = $this->getParent($context)) { + if ($parent = $this->getParent($context)) { return $parent->hasBlock($name, $context); } @@ -283,13 +261,13 @@ abstract class Template * @param array $context The context * @param array $blocks The current set of blocks * - * @return array An array of block names + * @return array An array of block names */ - public function getBlockNames(array $context, array $blocks = []) + public function getBlockNames(array $context, array $blocks = []): array { $names = array_merge(array_keys($blocks), array_keys($this->blocks)); - if (false !== $parent = $this->getParent($context)) { + if ($parent = $this->getParent($context)) { $names = array_merge($names, $parent->getBlockNames($context)); } @@ -297,16 +275,22 @@ abstract class Template } /** - * @return Template|TemplateWrapper + * @param string|TemplateWrapper|array $template */ - protected function loadTemplate($template, $templateName = null, $line = null, $index = null) + protected function loadTemplate($template, $templateName = null, $line = null, $index = null): self|TemplateWrapper { try { if (\is_array($template)) { return $this->env->resolveTemplate($template); } - if ($template instanceof self || $template instanceof TemplateWrapper) { + if ($template instanceof TemplateWrapper) { + return $template; + } + + if ($template instanceof self) { + trigger_deprecation('twig/twig', '3.9', 'Passing a "%s" instance to "%s" is deprecated.', self::class, __METHOD__); + return $template; } @@ -341,10 +325,8 @@ abstract class Template /** * @internal - * - * @return Template */ - public function unwrap() + public function unwrap(): self { return $this; } @@ -357,41 +339,58 @@ abstract class Template * * @return array An array of blocks */ - public function getBlocks() + public function getBlocks(): array { return $this->blocks; } - public function display(array $context, array $blocks = []) + public function display(array $context, array $blocks = []): void { - $this->displayWithErrorHandling($this->env->mergeGlobals($context), array_merge($this->blocks, $blocks)); + foreach ($this->yield($context, $blocks) as $data) { + echo $data; + } } - public function render(array $context) + public function render(array $context): string { - $level = ob_get_level(); - if ($this->env->isDebug()) { - ob_start(); - } else { - ob_start(function () { return ''; }); - } - try { - $this->display($context); - } catch (\Throwable $e) { - while (ob_get_level() > $level) { - ob_end_clean(); + if (!$this->useYield) { + $level = ob_get_level(); + if ($this->env->isDebug()) { + ob_start(); + } else { + ob_start(function () { return ''; }); + } + try { + $this->display($context); + } catch (\Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; } - throw $e; + return ob_get_clean(); } - return ob_get_clean(); + $content = ''; + foreach ($this->yield($context) as $data) { + $content .= $data; + } + + return $content; } - protected function displayWithErrorHandling(array $context, array $blocks = []) + /** + * @return iterable + */ + public function yield(array $context, array $blocks = []): iterable { + $context += $this->env->getGlobals(); + $blocks = array_merge($this->blocks, $blocks); + try { - $this->doDisplay($context, $blocks); + yield from $this->doDisplay($context, $blocks); } catch (Error $e) { if (!$e->getSourceContext()) { $e->setSourceContext($this->getSourceContext()); @@ -404,19 +403,95 @@ abstract class Template } throw $e; - } catch (\Exception $e) { - $e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e); + } catch (\Throwable $e) { + $e = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e); $e->guess(); throw $e; } } + /** + * @return iterable + */ + public function yieldBlock($name, array $context, array $blocks = [], $useBlocks = true, ?self $templateContext = null): iterable + { + if ($useBlocks && isset($blocks[$name])) { + $template = $blocks[$name][0]; + $block = $blocks[$name][1]; + } elseif (isset($this->blocks[$name])) { + $template = $this->blocks[$name][0]; + $block = $this->blocks[$name][1]; + } else { + $template = null; + $block = null; + } + + // avoid RCEs when sandbox is enabled + if (null !== $template && !$template instanceof self) { + throw new \LogicException('A block must be a method on a \Twig\Template instance.'); + } + + if (null !== $template) { + try { + yield from $template->$block($context, $blocks); + } catch (Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($template->getSourceContext()); + } + + // this is mostly useful for \Twig\Error\LoaderError exceptions + // see \Twig\Error\LoaderError + if (-1 === $e->getTemplateLine()) { + $e->guess(); + } + + throw $e; + } catch (\Throwable $e) { + $e = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e); + $e->guess(); + + throw $e; + } + } elseif ($parent = $this->getParent($context)) { + yield from $parent->unwrap()->yieldBlock($name, $context, array_merge($this->blocks, $blocks), false, $templateContext ?? $this); + } elseif (isset($blocks[$name])) { + throw new RuntimeError(\sprintf('Block "%s" should not call parent() in "%s" as the block does not exist in the parent template "%s".', $name, $blocks[$name][0]->getTemplateName(), $this->getTemplateName()), -1, $blocks[$name][0]->getSourceContext()); + } else { + throw new RuntimeError(\sprintf('Block "%s" on template "%s" does not exist.', $name, $this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext()); + } + } + + /** + * Yields a parent block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to display from the parent + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return iterable + */ + public function yieldParentBlock($name, array $context, array $blocks = []): iterable + { + if (isset($this->traits[$name])) { + yield from $this->traits[$name][0]->yieldBlock($name, $context, $blocks, false); + } elseif ($parent = $this->getParent($context)) { + yield from $parent->unwrap()->yieldBlock($name, $context, $blocks, false); + } else { + throw new RuntimeError(\sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext()); + } + } + /** * Auto-generated method to display the template with the given context. * * @param array $context An array of parameters to pass to the template * @param array $blocks An array of blocks to pass to the template + * + * @return iterable */ - abstract protected function doDisplay(array $context, array $blocks = []); + abstract protected function doDisplay(array $context, array $blocks = []): iterable; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/TemplateWrapper.php b/data/web/inc/lib/vendor/twig/twig/src/TemplateWrapper.php index c9c6b07c6..135c59188 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TemplateWrapper.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TemplateWrapper.php @@ -18,26 +18,21 @@ namespace Twig; */ final class TemplateWrapper { - private $env; - private $template; - /** * This method is for internal use only and should never be called * directly (use Twig\Environment::load() instead). * * @internal */ - public function __construct(Environment $env, Template $template) - { - $this->env = $env; - $this->template = $template; + public function __construct( + private Environment $env, + private Template $template, + ) { } public function render(array $context = []): string { - // using func_get_args() allows to not expose the blocks argument - // as it should only be used by internal code - return $this->template->render($context, \func_get_args()[1] ?? []); + return $this->template->render($context); } public function display(array $context = []) @@ -62,29 +57,15 @@ final class TemplateWrapper public function renderBlock(string $name, array $context = []): string { - $context = $this->env->mergeGlobals($context); - $level = ob_get_level(); - if ($this->env->isDebug()) { - ob_start(); - } else { - ob_start(function () { return ''; }); - } - try { - $this->template->displayBlock($name, $context); - } catch (\Throwable $e) { - while (ob_get_level() > $level) { - ob_end_clean(); - } - - throw $e; - } - - return ob_get_clean(); + return $this->template->renderBlock($name, $context + $this->env->getGlobals()); } public function displayBlock(string $name, array $context = []) { - $this->template->displayBlock($name, $this->env->mergeGlobals($context)); + $context += $this->env->getGlobals(); + foreach ($this->template->yieldBlock($name, $context) as $data) { + echo $data; + } } public function getSourceContext(): Source diff --git a/data/web/inc/lib/vendor/twig/twig/src/Test/IntegrationTestCase.php b/data/web/inc/lib/vendor/twig/twig/src/Test/IntegrationTestCase.php index 307302bb6..8690a809e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Test/IntegrationTestCase.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Test/IntegrationTestCase.php @@ -30,9 +30,19 @@ use Twig\TwigTest; abstract class IntegrationTestCase extends TestCase { /** + * @deprecated since Twig 3.13, use getFixturesDirectory() instead. + * * @return string */ - abstract protected function getFixturesDir(); + protected function getFixturesDir() + { + throw new \BadMethodCallException('Not implemented.'); + } + + protected static function getFixturesDirectory(): string + { + throw new \BadMethodCallException('Not implemented.'); + } /** * @return RuntimeLoaderInterface[] @@ -84,6 +94,7 @@ abstract class IntegrationTestCase extends TestCase /** * @dataProvider getLegacyTests + * * @group legacy */ public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') @@ -91,9 +102,19 @@ abstract class IntegrationTestCase extends TestCase $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation); } + /** + * @final since Twig 3.13 + */ public function getTests($name, $legacyTests = false) { - $fixturesDir = realpath($this->getFixturesDir()); + try { + $fixturesDir = static::getFixturesDirectory(); + } catch (\BadMethodCallException) { + trigger_deprecation('twig/twig', '3.13', 'Not overriding "%s::getFixturesDirectory()" in "%s" is deprecated. This method will be abstract in 4.0.', self::class, static::class); + $fixturesDir = $this->getFixturesDir(); + } + + $fixturesDir = realpath($fixturesDir); $tests = []; foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { @@ -101,7 +122,7 @@ abstract class IntegrationTestCase extends TestCase continue; } - if ($legacyTests xor false !== strpos($file->getRealpath(), '.legacy.test')) { + if ($legacyTests xor str_contains($file->getRealpath(), '.legacy.test')) { continue; } @@ -122,7 +143,7 @@ abstract class IntegrationTestCase extends TestCase $exception = false; preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $outputs, \PREG_SET_ORDER); } else { - throw new \InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file))); + throw new \InvalidArgumentException(\sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file))); } $tests[] = [str_replace($fixturesDir.'/', '', $file), $message, $condition, $templates, $exception, $outputs, $deprecation]; @@ -136,6 +157,9 @@ abstract class IntegrationTestCase extends TestCase return $tests; } + /** + * @final since Twig 3.13 + */ public function getLegacyTests() { return $this->getTests('testLegacyIntegration', true); @@ -148,19 +172,23 @@ abstract class IntegrationTestCase extends TestCase } if ($condition) { + $ret = ''; eval('$ret = '.$condition.';'); if (!$ret) { $this->markTestSkipped($condition); } } - $loader = new ArrayLoader($templates); - foreach ($outputs as $i => $match) { $config = array_merge([ 'cache' => false, 'strict_variables' => true, ], $match[2] ? eval($match[2].';') : []); + // make sure that template are always compiled even if they are the same (useful when testing with more than one data/expect sections) + foreach ($templates as $j => $template) { + $templates[$j] = $template.str_repeat(' ', $i); + } + $loader = new ArrayLoader($templates); $twig = new Environment($loader, $config); $twig->addGlobal('global', 'global'); foreach ($this->getRuntimeLoaders() as $runtimeLoader) { @@ -183,11 +211,6 @@ abstract class IntegrationTestCase extends TestCase $twig->addFunction($function); } - // avoid using the same PHP class name for different cases - $p = new \ReflectionProperty($twig, 'templateClassPrefix'); - $p->setAccessible(true); - $p->setValue($twig, '__TwigTemplate_'.hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', uniqid(mt_rand(), true), false).'_'); - $deprecations = []; try { $prevHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$deprecations, &$prevHandler) { @@ -204,14 +227,14 @@ abstract class IntegrationTestCase extends TestCase } catch (\Exception $e) { if (false !== $exception) { $message = $e->getMessage(); - $this->assertSame(trim($exception), trim(sprintf('%s: %s', \get_class($e), $message))); + $this->assertSame(trim($exception), trim(\sprintf('%s: %s', \get_class($e), $message))); $last = substr($message, \strlen($message) - 1); $this->assertTrue('.' === $last || '?' === $last, 'Exception message must end with a dot or a question mark.'); return; } - throw new Error(sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e); + throw new Error(\sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e); } finally { restore_error_handler(); } @@ -222,18 +245,18 @@ abstract class IntegrationTestCase extends TestCase $output = trim($template->render(eval($match[1].';')), "\n "); } catch (\Exception $e) { if (false !== $exception) { - $this->assertSame(trim($exception), trim(sprintf('%s: %s', \get_class($e), $e->getMessage()))); + $this->assertSame(trim($exception), trim(\sprintf('%s: %s', \get_class($e), $e->getMessage()))); return; } - $e = new Error(sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e); + $e = new Error(\sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e); - $output = trim(sprintf('%s: %s', \get_class($e), $e->getMessage())); + $output = trim(\sprintf('%s: %s', \get_class($e), $e->getMessage())); } if (false !== $exception) { - list($class) = explode(':', $exception); + [$class] = explode(':', $exception); $constraintClass = class_exists('PHPUnit\Framework\Constraint\Exception') ? 'PHPUnit\Framework\Constraint\Exception' : 'PHPUnit_Framework_Constraint_Exception'; $this->assertThat(null, new $constraintClass($class)); } @@ -257,7 +280,7 @@ abstract class IntegrationTestCase extends TestCase $templates = []; preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $test, $matches, \PREG_SET_ORDER); foreach ($matches as $match) { - $templates[($match[1] ?: 'index.twig')] = $match[2]; + $templates[$match[1] ?: 'index.twig'] = $match[2]; } return $templates; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Test/NodeTestCase.php b/data/web/inc/lib/vendor/twig/twig/src/Test/NodeTestCase.php index 3b8b2c86c..bac0ea6d0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Test/NodeTestCase.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Test/NodeTestCase.php @@ -11,6 +11,8 @@ namespace Twig\Test; +use PHPUnit\Framework\Attributes\BeforeClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Twig\Compiler; use Twig\Environment; @@ -19,17 +21,37 @@ use Twig\Node\Node; abstract class NodeTestCase extends TestCase { - abstract public function getTests(); + /** + * @var Environment + */ + private $currentEnv; + + public function getTests() + { + return []; + } + + /** + * @return iterable + */ + public static function provideTests(): iterable + { + trigger_deprecation('twig/twig', '3.13', 'Not implementing "%s()" in "%s" is deprecated. This method will be abstract in 4.0.', __METHOD__, static::class); + + return []; + } /** * @dataProvider getTests + * @dataProvider provideTests */ + #[DataProvider('getTests'), DataProvider('provideTests')] public function testCompile($node, $source, $environment = null, $isPattern = false) { $this->assertNodeCompilation($source, $node, $environment, $isPattern); } - public function assertNodeCompilation($source, Node $node, Environment $environment = null, $isPattern = false) + public function assertNodeCompilation($source, Node $node, ?Environment $environment = null, $isPattern = false) { $compiler = $this->getCompiler($environment); $compiler->compile($node); @@ -41,25 +63,63 @@ abstract class NodeTestCase extends TestCase } } - protected function getCompiler(Environment $environment = null) + protected function getCompiler(?Environment $environment = null) { - return new Compiler(null === $environment ? $this->getEnvironment() : $environment); + return new Compiler($environment ?? $this->getEnvironment()); } + /** + * @final since Twig 3.13 + */ protected function getEnvironment() { - return new Environment(new ArrayLoader([])); + return $this->currentEnv ??= static::createEnvironment(); } + protected static function createEnvironment(): Environment + { + return new Environment(new ArrayLoader()); + } + + /** + * @deprecated since Twig 3.13, use createVariableGetter() instead. + */ protected function getVariableGetter($name, $line = false) + { + trigger_deprecation('twig/twig', '3.13', 'Method "%s()" is deprecated, use "createVariableGetter()" instead.', __METHOD__); + + return self::createVariableGetter($name, $line); + } + + final protected static function createVariableGetter(string $name, bool $line = false): string { $line = $line > 0 ? "// line $line\n" : ''; - return sprintf('%s($context["%s"] ?? null)', $line, $name); + return \sprintf('%s($context["%s"] ?? null)', $line, $name); } + /** + * @deprecated since Twig 3.13, use createAttributeGetter() instead. + */ protected function getAttributeGetter() { - return 'twig_get_attribute($this->env, $this->source, '; + trigger_deprecation('twig/twig', '3.13', 'Method "%s()" is deprecated, use "createAttributeGetter()" instead.', __METHOD__); + + return self::createAttributeGetter(); + } + + final protected static function createAttributeGetter(): string + { + return 'CoreExtension::getAttribute($this->env, $this->source, '; + } + + /** @beforeClass */ + #[BeforeClass] + final public static function checkDataProvider(): void + { + $r = new \ReflectionMethod(static::class, 'getTests'); + if (self::class !== $r->getDeclaringClass()->getName()) { + trigger_deprecation('twig/twig', '3.13', 'Implementing "%s::getTests()" in "%s" is deprecated, implement "provideTests()" instead.', self::class, static::class); + } } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Token.php b/data/web/inc/lib/vendor/twig/twig/src/Token.php index 53a6cafc3..237634ad1 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Token.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Token.php @@ -17,10 +17,6 @@ namespace Twig; */ final class Token { - private $value; - private $type; - private $lineno; - public const EOF_TYPE = -1; public const TEXT_TYPE = 0; public const BLOCK_START_TYPE = 1; @@ -35,17 +31,18 @@ final class Token public const INTERPOLATION_START_TYPE = 10; public const INTERPOLATION_END_TYPE = 11; public const ARROW_TYPE = 12; + public const SPREAD_TYPE = 13; - public function __construct(int $type, $value, int $lineno) - { - $this->type = $type; - $this->value = $value; - $this->lineno = $lineno; + public function __construct( + private int $type, + private $value, + private int $lineno, + ) { } public function __toString() { - return sprintf('%s(%s)', self::typeToString($this->type, true), $this->value); + return \sprintf('%s(%s)', self::typeToString($this->type, true), $this->value); } /** @@ -67,9 +64,9 @@ final class Token } return ($this->type === $type) && ( - null === $values || - (\is_array($values) && \in_array($this->value, $values)) || - $this->value == $values + null === $values + || (\is_array($values) && \in_array($this->value, $values)) + || $this->value == $values ); } @@ -133,8 +130,11 @@ final class Token case self::ARROW_TYPE: $name = 'ARROW_TYPE'; break; + case self::SPREAD_TYPE: + $name = 'SPREAD_TYPE'; + break; default: - throw new \LogicException(sprintf('Token of type "%s" does not exist.', $type)); + throw new \LogicException(\sprintf('Token of type "%s" does not exist.', $type)); } return $short ? $name : 'Twig\Token::'.$name; @@ -171,8 +171,10 @@ final class Token return 'end of string interpolation'; case self::ARROW_TYPE: return 'arrow function'; + case self::SPREAD_TYPE: + return 'spread operator'; default: - throw new \LogicException(sprintf('Token of type "%s" does not exist.', $type)); + throw new \LogicException(\sprintf('Token of type "%s" does not exist.', $type)); } } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php index 4dbf30406..0a6c1afb5 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php @@ -36,16 +36,16 @@ final class ApplyTokenParser extends AbstractTokenParser $ref = new TempNameExpression($name, $lineno); $ref->setAttribute('always_defined', true); - $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag()); + $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref); $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); $body = $this->parser->subparse([$this, 'decideApplyEnd'], true); $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); return new Node([ - new SetNode(true, $ref, $body, $lineno, $this->getTag()), - new PrintNode($filter, $lineno, $this->getTag()), - ]); + new SetNode(true, $ref, $body, $lineno), + new PrintNode($filter, $lineno), + ], [], $lineno); } public function decideApplyEnd(Token $token): bool diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php index b674bea4a..b50b29e65 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php @@ -29,7 +29,7 @@ final class AutoEscapeTokenParser extends AbstractTokenParser $lineno = $token->getLine(); $stream = $this->parser->getStream(); - if ($stream->test(/* Token::BLOCK_END_TYPE */ 3)) { + if ($stream->test(Token::BLOCK_END_TYPE)) { $value = 'html'; } else { $expr = $this->parser->getExpressionParser()->parseExpression(); @@ -39,11 +39,11 @@ final class AutoEscapeTokenParser extends AbstractTokenParser $value = $expr->getAttribute('value'); } - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); - return new AutoEscapeNode($value, $body, $lineno, $this->getTag()); + return new AutoEscapeNode($value, $body, $lineno); } public function decideBlockEnd(Token $token): bool diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/BlockTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/BlockTokenParser.php index 5878131be..81d675db0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/BlockTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/BlockTokenParser.php @@ -35,21 +35,18 @@ final class BlockTokenParser extends AbstractTokenParser { $lineno = $token->getLine(); $stream = $this->parser->getStream(); - $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); - if ($this->parser->hasBlock($name)) { - throw new SyntaxError(sprintf("The block '%s' has already been defined line %d.", $name, $this->parser->getBlock($name)->getTemplateLine()), $stream->getCurrent()->getLine(), $stream->getSourceContext()); - } + $name = $stream->expect(Token::NAME_TYPE)->getValue(); $this->parser->setBlock($name, $block = new BlockNode($name, new Node([]), $lineno)); $this->parser->pushLocalScope(); $this->parser->pushBlockStack($name); - if ($stream->nextIf(/* Token::BLOCK_END_TYPE */ 3)) { + if ($stream->nextIf(Token::BLOCK_END_TYPE)) { $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); - if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) { + if ($token = $stream->nextIf(Token::NAME_TYPE)) { $value = $token->getValue(); if ($value != $name) { - throw new SyntaxError(sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + throw new SyntaxError(\sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); } } } else { @@ -57,13 +54,13 @@ final class BlockTokenParser extends AbstractTokenParser new PrintNode($this->parser->getExpressionParser()->parseExpression(), $lineno), ]); } - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); $block->setNode('body', $body); $this->parser->popBlockStack(); $this->parser->popLocalScope(); - return new BlockReferenceNode($name, $lineno, $this->getTag()); + return new BlockReferenceNode($name, $lineno); } public function decideBlockEnd(Token $token): bool diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php index 31416c79c..164ef26ee 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php @@ -11,6 +11,7 @@ namespace Twig\TokenParser; +use Twig\Error\SyntaxError; use Twig\Node\DeprecatedNode; use Twig\Node\Node; use Twig\Token; @@ -21,6 +22,8 @@ use Twig\Token; * {% deprecated 'The "base.twig" template is deprecated, use "layout.twig" instead.' %} * {% extends 'layout.html.twig' %} * + * {% deprecated 'The "base.twig" template is deprecated, use "layout.twig" instead.' package="foo/bar" version="1.1" %} + * * @author Yonel Ceruto * * @internal @@ -29,11 +32,31 @@ final class DeprecatedTokenParser extends AbstractTokenParser { public function parse(Token $token): Node { - $expr = $this->parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + $expressionParser = $this->parser->getExpressionParser(); + $expr = $expressionParser->parseExpression(); + $node = new DeprecatedNode($expr, $token->getLine()); - $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + while ($stream->test(Token::NAME_TYPE)) { + $k = $stream->getCurrent()->getValue(); + $stream->next(); + $stream->expect(Token::OPERATOR_TYPE, '='); - return new DeprecatedNode($expr, $token->getLine(), $this->getTag()); + switch ($k) { + case 'package': + $node->setNode('package', $expressionParser->parseExpression()); + break; + case 'version': + $node->setNode('version', $expressionParser->parseExpression()); + break; + default: + throw new SyntaxError(\sprintf('Unknown "%s" option.', $k), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return $node; } public function getTag(): string diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DoTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DoTokenParser.php index 32c8f12ff..8afd48559 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DoTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DoTokenParser.php @@ -26,9 +26,9 @@ final class DoTokenParser extends AbstractTokenParser { $expr = $this->parser->getExpressionParser()->parseExpression(); - $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); - return new DoNode($expr, $token->getLine(), $this->getTag()); + return new DoNode($expr, $token->getLine()); } public function getTag(): string diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php index 64b4f296f..7bf3233e2 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php @@ -30,21 +30,21 @@ final class EmbedTokenParser extends IncludeTokenParser $parent = $this->parser->getExpressionParser()->parseExpression(); - list($variables, $only, $ignoreMissing) = $this->parseArguments(); + [$variables, $only, $ignoreMissing] = $this->parseArguments(); - $parentToken = $fakeParentToken = new Token(/* Token::STRING_TYPE */ 7, '__parent__', $token->getLine()); + $parentToken = $fakeParentToken = new Token(Token::STRING_TYPE, '__parent__', $token->getLine()); if ($parent instanceof ConstantExpression) { - $parentToken = new Token(/* Token::STRING_TYPE */ 7, $parent->getAttribute('value'), $token->getLine()); + $parentToken = new Token(Token::STRING_TYPE, $parent->getAttribute('value'), $token->getLine()); } elseif ($parent instanceof NameExpression) { - $parentToken = new Token(/* Token::NAME_TYPE */ 5, $parent->getAttribute('name'), $token->getLine()); + $parentToken = new Token(Token::NAME_TYPE, $parent->getAttribute('name'), $token->getLine()); } // inject a fake parent to make the parent() function work $stream->injectTokens([ - new Token(/* Token::BLOCK_START_TYPE */ 1, '', $token->getLine()), - new Token(/* Token::NAME_TYPE */ 5, 'extends', $token->getLine()), + new Token(Token::BLOCK_START_TYPE, '', $token->getLine()), + new Token(Token::NAME_TYPE, 'extends', $token->getLine()), $parentToken, - new Token(/* Token::BLOCK_END_TYPE */ 3, '', $token->getLine()), + new Token(Token::BLOCK_END_TYPE, '', $token->getLine()), ]); $module = $this->parser->parse($stream, [$this, 'decideBlockEnd'], true); @@ -56,9 +56,9 @@ final class EmbedTokenParser extends IncludeTokenParser $this->parser->embedTemplate($module); - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); - return new EmbedNode($module->getTemplateName(), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + return new EmbedNode($module->getTemplateName(), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine()); } public function decideBlockEnd(Token $token): bool diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php index 0ca46dd29..86ddfdfba 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php @@ -35,14 +35,11 @@ final class ExtendsTokenParser extends AbstractTokenParser throw new SyntaxError('Cannot use "extend" in a macro.', $token->getLine(), $stream->getSourceContext()); } - if (null !== $this->parser->getParent()) { - throw new SyntaxError('Multiple extends tags are forbidden.', $token->getLine(), $stream->getSourceContext()); - } $this->parser->setParent($this->parser->getExpressionParser()->parseExpression()); - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); - return new Node(); + return new Node([], [], $token->getLine()); } public function getTag(): string diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/FlushTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/FlushTokenParser.php index 02c74aa13..0d2388745 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/FlushTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/FlushTokenParser.php @@ -26,9 +26,9 @@ final class FlushTokenParser extends AbstractTokenParser { public function parse(Token $token): Node { - $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); - return new FlushNode($token->getLine(), $this->getTag()); + return new FlushNode($token->getLine()); } public function getTag(): string diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ForTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ForTokenParser.php index bac8ba2da..cf655f842 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ForTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ForTokenParser.php @@ -35,30 +35,30 @@ final class ForTokenParser extends AbstractTokenParser $lineno = $token->getLine(); $stream = $this->parser->getStream(); $targets = $this->parser->getExpressionParser()->parseAssignmentExpression(); - $stream->expect(/* Token::OPERATOR_TYPE */ 8, 'in'); + $stream->expect(Token::OPERATOR_TYPE, 'in'); $seq = $this->parser->getExpressionParser()->parseExpression(); - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); $body = $this->parser->subparse([$this, 'decideForFork']); if ('else' == $stream->next()->getValue()) { - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); $else = $this->parser->subparse([$this, 'decideForEnd'], true); } else { $else = null; } - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); if (\count($targets) > 1) { - $keyTarget = $targets->getNode(0); + $keyTarget = $targets->getNode('0'); $keyTarget = new AssignNameExpression($keyTarget->getAttribute('name'), $keyTarget->getTemplateLine()); - $valueTarget = $targets->getNode(1); + $valueTarget = $targets->getNode('1'); } else { $keyTarget = new AssignNameExpression('_key', $lineno); - $valueTarget = $targets->getNode(0); + $valueTarget = $targets->getNode('0'); } $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); - return new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, $lineno, $this->getTag()); + return new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, $lineno); } public function decideForFork(Token $token): bool diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/FromTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/FromTokenParser.php index 35098c267..2ccff5fbe 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/FromTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/FromTokenParser.php @@ -29,28 +29,28 @@ final class FromTokenParser extends AbstractTokenParser { $macro = $this->parser->getExpressionParser()->parseExpression(); $stream = $this->parser->getStream(); - $stream->expect(/* Token::NAME_TYPE */ 5, 'import'); + $stream->expect(Token::NAME_TYPE, 'import'); $targets = []; - do { - $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + while (true) { + $name = $stream->expect(Token::NAME_TYPE)->getValue(); $alias = $name; if ($stream->nextIf('as')) { - $alias = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + $alias = $stream->expect(Token::NAME_TYPE)->getValue(); } $targets[$name] = $alias; - if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { break; } - } while (true); + } - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); $var = new AssignNameExpression($this->parser->getVarName(), $token->getLine()); - $node = new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope()); + $node = new ImportNode($macro, $var, $token->getLine(), $this->parser->isMainScope()); foreach ($targets as $name => $alias) { $this->parser->addImportedSymbol('function', $alias, 'macro_'.$name, $var); diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IfTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IfTokenParser.php index c0fe6df0d..4ea6f3df9 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IfTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IfTokenParser.php @@ -37,7 +37,7 @@ final class IfTokenParser extends AbstractTokenParser $lineno = $token->getLine(); $expr = $this->parser->getExpressionParser()->parseExpression(); $stream = $this->parser->getStream(); - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); $body = $this->parser->subparse([$this, 'decideIfFork']); $tests = [$expr, $body]; $else = null; @@ -46,13 +46,13 @@ final class IfTokenParser extends AbstractTokenParser while (!$end) { switch ($stream->next()->getValue()) { case 'else': - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); $else = $this->parser->subparse([$this, 'decideIfEnd']); break; case 'elseif': $expr = $this->parser->getExpressionParser()->parseExpression(); - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); $body = $this->parser->subparse([$this, 'decideIfFork']); $tests[] = $expr; $tests[] = $body; @@ -63,13 +63,13 @@ final class IfTokenParser extends AbstractTokenParser break; default: - throw new SyntaxError(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d).', $lineno), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + throw new SyntaxError(\sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d).', $lineno), $stream->getCurrent()->getLine(), $stream->getSourceContext()); } } - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); - return new IfNode(new Node($tests), $else, $lineno, $this->getTag()); + return new IfNode(new Node($tests), $else, $lineno); } public function decideIfFork(Token $token): bool diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ImportTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ImportTokenParser.php index 44cb4dad7..f20f35ab3 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ImportTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ImportTokenParser.php @@ -28,13 +28,13 @@ final class ImportTokenParser extends AbstractTokenParser public function parse(Token $token): Node { $macro = $this->parser->getExpressionParser()->parseExpression(); - $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5, 'as'); - $var = new AssignNameExpression($this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5)->getValue(), $token->getLine()); - $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); + $this->parser->getStream()->expect(Token::NAME_TYPE, 'as'); + $var = new AssignNameExpression($this->parser->getStream()->expect(Token::NAME_TYPE)->getValue(), $token->getLine()); + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); $this->parser->addImportedSymbol('template', $var->getAttribute('name')); - return new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope()); + return new ImportNode($macro, $var, $token->getLine(), $this->parser->isMainScope()); } public function getTag(): string diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php index 28beb8ae4..466f2288c 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php @@ -31,9 +31,9 @@ class IncludeTokenParser extends AbstractTokenParser { $expr = $this->parser->getExpressionParser()->parseExpression(); - list($variables, $only, $ignoreMissing) = $this->parseArguments(); + [$variables, $only, $ignoreMissing] = $this->parseArguments(); - return new IncludeNode($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + return new IncludeNode($expr, $variables, $only, $ignoreMissing, $token->getLine()); } protected function parseArguments() @@ -41,23 +41,23 @@ class IncludeTokenParser extends AbstractTokenParser $stream = $this->parser->getStream(); $ignoreMissing = false; - if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'ignore')) { - $stream->expect(/* Token::NAME_TYPE */ 5, 'missing'); + if ($stream->nextIf(Token::NAME_TYPE, 'ignore')) { + $stream->expect(Token::NAME_TYPE, 'missing'); $ignoreMissing = true; } $variables = null; - if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'with')) { + if ($stream->nextIf(Token::NAME_TYPE, 'with')) { $variables = $this->parser->getExpressionParser()->parseExpression(); } $only = false; - if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'only')) { + if ($stream->nextIf(Token::NAME_TYPE, 'only')) { $only = true; } - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); return [$variables, $only, $ignoreMissing]; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/MacroTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/MacroTokenParser.php index f584927e9..c7762075c 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/MacroTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/MacroTokenParser.php @@ -32,26 +32,26 @@ final class MacroTokenParser extends AbstractTokenParser { $lineno = $token->getLine(); $stream = $this->parser->getStream(); - $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + $name = $stream->expect(Token::NAME_TYPE)->getValue(); $arguments = $this->parser->getExpressionParser()->parseArguments(true, true); - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); $this->parser->pushLocalScope(); $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); - if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) { + if ($token = $stream->nextIf(Token::NAME_TYPE)) { $value = $token->getValue(); if ($value != $name) { - throw new SyntaxError(sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + throw new SyntaxError(\sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); } } $this->parser->popLocalScope(); - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); - $this->parser->setMacro($name, new MacroNode($name, new BodyNode([$body]), $arguments, $lineno, $this->getTag())); + $this->parser->setMacro($name, new MacroNode($name, new BodyNode([$body]), $arguments, $lineno)); - return new Node(); + return new Node([], [], $lineno); } public function decideBlockEnd(Token $token): bool diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php index c919556ec..70869fbc5 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php @@ -34,9 +34,9 @@ final class SandboxTokenParser extends AbstractTokenParser public function parse(Token $token): Node { $stream = $this->parser->getStream(); - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); // in a sandbox tag, only include tags are allowed if (!$body instanceof IncludeNode) { @@ -51,7 +51,7 @@ final class SandboxTokenParser extends AbstractTokenParser } } - return new SandboxNode($body, $token->getLine(), $this->getTag()); + return new SandboxNode($body, $token->getLine()); } public function decideBlockEnd(Token $token): bool diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SetTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SetTokenParser.php index 2fbdfe090..bb43907bd 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SetTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SetTokenParser.php @@ -37,10 +37,10 @@ final class SetTokenParser extends AbstractTokenParser $names = $this->parser->getExpressionParser()->parseAssignmentExpression(); $capture = false; - if ($stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) { + if ($stream->nextIf(Token::OPERATOR_TYPE, '=')) { $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); if (\count($names) !== \count($values)) { throw new SyntaxError('When using set, you must have the same number of variables and assignments.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); @@ -52,13 +52,13 @@ final class SetTokenParser extends AbstractTokenParser throw new SyntaxError('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); } - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); $values = $this->parser->subparse([$this, 'decideBlockEnd'], true); - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); } - return new SetNode($capture, $names, $values, $lineno, $this->getTag()); + return new SetNode($capture, $names, $values, $lineno); } public function decideBlockEnd(Token $token): bool diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/TypesTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/TypesTokenParser.php new file mode 100644 index 000000000..2e0850e7e --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/TypesTokenParser.php @@ -0,0 +1,86 @@ + + * + * @internal + */ +final class TypesTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $stream = $this->parser->getStream(); + + $types = $this->parseSimpleMappingExpression($stream); + + $stream->expect(Token::BLOCK_END_TYPE); + + return new TypesNode($types, $token->getLine()); + } + + /** + * @return array + * + * @throws SyntaxError + */ + private function parseSimpleMappingExpression(TokenStream $stream): array + { + $stream->expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected'); + + $types = []; + + $first = true; + while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) { + if (!$first) { + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A type string must be followed by a comma'); + + // trailing ,? + if ($stream->test(Token::PUNCTUATION_TYPE, '}')) { + break; + } + } + $first = false; + + $nameToken = $stream->expect(Token::NAME_TYPE); + $isOptional = null !== $stream->nextIf(Token::PUNCTUATION_TYPE, '?'); + + $stream->expect(Token::PUNCTUATION_TYPE, ':', 'A type name must be followed by a colon (:)'); + + $valueToken = $stream->expect(Token::STRING_TYPE); + + $types[$nameToken->getValue()] = [ + 'type' => $valueToken->getValue(), + 'optional' => $isOptional, + ]; + } + $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed'); + + return $types; + } + + public function getTag(): string + { + return 'types'; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/UseTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/UseTokenParser.php index d0a2de41a..1b96b4047 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/UseTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/UseTokenParser.php @@ -43,27 +43,27 @@ final class UseTokenParser extends AbstractTokenParser $targets = []; if ($stream->nextIf('with')) { - do { - $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + while (true) { + $name = $stream->expect(Token::NAME_TYPE)->getValue(); $alias = $name; if ($stream->nextIf('as')) { - $alias = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + $alias = $stream->expect(Token::NAME_TYPE)->getValue(); } $targets[$name] = new ConstantExpression($alias, -1); - if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { break; } - } while (true); + } } - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); $this->parser->addTrait(new Node(['template' => $template, 'targets' => new Node($targets)])); - return new Node(); + return new Node([], [], $token->getLine()); } public function getTag(): string diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/WithTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/WithTokenParser.php index 7d8cbe261..8ce4f02b2 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/WithTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/WithTokenParser.php @@ -30,18 +30,18 @@ final class WithTokenParser extends AbstractTokenParser $variables = null; $only = false; - if (!$stream->test(/* Token::BLOCK_END_TYPE */ 3)) { + if (!$stream->test(Token::BLOCK_END_TYPE)) { $variables = $this->parser->getExpressionParser()->parseExpression(); - $only = (bool) $stream->nextIf(/* Token::NAME_TYPE */ 5, 'only'); + $only = (bool) $stream->nextIf(Token::NAME_TYPE, 'only'); } - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); $body = $this->parser->subparse([$this, 'decideWithEnd'], true); - $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(Token::BLOCK_END_TYPE); - return new WithNode($body, $variables, $only, $token->getLine(), $this->getTag()); + return new WithNode($body, $variables, $only, $token->getLine()); } public function decideWithEnd(Token $token): bool diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenStream.php b/data/web/inc/lib/vendor/twig/twig/src/TokenStream.php index 1eac11a02..c91701bfe 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenStream.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenStream.php @@ -21,13 +21,12 @@ use Twig\Error\SyntaxError; */ final class TokenStream { - private $tokens; private $current = 0; - private $source; - public function __construct(array $tokens, Source $source = null) - { - $this->tokens = $tokens; + public function __construct( + private array $tokens, + private ?Source $source = null, + ) { $this->source = $source ?: new Source('', ''); } @@ -60,24 +59,22 @@ final class TokenStream */ public function nextIf($primary, $secondary = null) { - if ($this->tokens[$this->current]->test($primary, $secondary)) { - return $this->next(); - } + return $this->tokens[$this->current]->test($primary, $secondary) ? $this->next() : null; } /** * Tests a token and returns it or throws a syntax error. */ - public function expect($type, $value = null, string $message = null): Token + public function expect($type, $value = null, ?string $message = null): Token { $token = $this->tokens[$this->current]; if (!$token->test($type, $value)) { $line = $token->getLine(); - throw new SyntaxError(sprintf('%sUnexpected token "%s"%s ("%s" expected%s).', + throw new SyntaxError(\sprintf('%sUnexpected token "%s"%s ("%s" expected%s).', $message ? $message.'. ' : '', Token::typeToEnglish($token->getType()), - $token->getValue() ? sprintf(' of value "%s"', $token->getValue()) : '', - Token::typeToEnglish($type), $value ? sprintf(' with value "%s"', $value) : ''), + $token->getValue() ? \sprintf(' of value "%s"', $token->getValue()) : '', + Token::typeToEnglish($type), $value ? \sprintf(' with value "%s"', $value) : ''), $line, $this->source ); @@ -112,7 +109,7 @@ final class TokenStream */ public function isEOF(): bool { - return /* Token::EOF_TYPE */ -1 === $this->tokens[$this->current]->getType(); + return Token::EOF_TYPE === $this->tokens[$this->current]->getType(); } public function getCurrent(): Token diff --git a/data/web/inc/lib/vendor/twig/twig/src/TwigCallableInterface.php b/data/web/inc/lib/vendor/twig/twig/src/TwigCallableInterface.php new file mode 100644 index 000000000..2a8ff6116 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/TwigCallableInterface.php @@ -0,0 +1,53 @@ + + */ +interface TwigCallableInterface extends \Stringable +{ + public function getName(): string; + + public function getType(): string; + + public function getDynamicName(): string; + + /** + * @return callable|array{class-string, string}|null + */ + public function getCallable(); + + public function getNodeClass(): string; + + public function needsCharset(): bool; + + public function needsEnvironment(): bool; + + public function needsContext(): bool; + + public function withDynamicArguments(string $name, string $dynamicName, array $arguments): self; + + public function getArguments(): array; + + public function isVariadic(): bool; + + public function isDeprecated(): bool; + + public function getDeprecatingPackage(): string; + + public function getDeprecatedVersion(): string; + + public function getAlternative(): ?string; + + public function getMinimalNumberOfRequiredArguments(): int; +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/TwigFilter.php b/data/web/inc/lib/vendor/twig/twig/src/TwigFilter.php index 94e5f9b01..70b1f8f3f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TwigFilter.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TwigFilter.php @@ -21,72 +21,27 @@ use Twig\Node\Node; * * @see https://twig.symfony.com/doc/templates.html#filters */ -final class TwigFilter +final class TwigFilter extends AbstractTwigCallable { - private $name; - private $callable; - private $options; - private $arguments = []; - /** - * @param callable|null $callable A callable implementing the filter. If null, you need to overwrite the "node_class" option to customize compilation. + * @param callable|array{class-string, string}|null $callable A callable implementing the filter. If null, you need to overwrite the "node_class" option to customize compilation. */ public function __construct(string $name, $callable = null, array $options = []) { - $this->name = $name; - $this->callable = $callable; + parent::__construct($name, $callable, $options); + $this->options = array_merge([ - 'needs_environment' => false, - 'needs_context' => false, - 'is_variadic' => false, 'is_safe' => null, 'is_safe_callback' => null, 'pre_escape' => null, 'preserves_safety' => null, 'node_class' => FilterExpression::class, - 'deprecated' => false, - 'alternative' => null, - ], $options); + ], $this->options); } - public function getName(): string + public function getType(): string { - return $this->name; - } - - /** - * Returns the callable to execute for this filter. - * - * @return callable|null - */ - public function getCallable() - { - return $this->callable; - } - - public function getNodeClass(): string - { - return $this->options['node_class']; - } - - public function setArguments(array $arguments): void - { - $this->arguments = $arguments; - } - - public function getArguments(): array - { - return $this->arguments; - } - - public function needsEnvironment(): bool - { - return $this->options['needs_environment']; - } - - public function needsContext(): bool - { - return $this->options['needs_context']; + return 'filter'; } public function getSafe(Node $filterArgs): ?array @@ -112,23 +67,8 @@ final class TwigFilter return $this->options['pre_escape']; } - public function isVariadic(): bool + public function getMinimalNumberOfRequiredArguments(): int { - return $this->options['is_variadic']; - } - - public function isDeprecated(): bool - { - return (bool) $this->options['deprecated']; - } - - public function getDeprecatedVersion(): string - { - return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; - } - - public function getAlternative(): ?string - { - return $this->options['alternative']; + return parent::getMinimalNumberOfRequiredArguments() + 1; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/TwigFunction.php b/data/web/inc/lib/vendor/twig/twig/src/TwigFunction.php index 494d45b08..4a10df95e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TwigFunction.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TwigFunction.php @@ -21,70 +21,31 @@ use Twig\Node\Node; * * @see https://twig.symfony.com/doc/templates.html#functions */ -final class TwigFunction +final class TwigFunction extends AbstractTwigCallable { - private $name; - private $callable; - private $options; - private $arguments = []; - /** - * @param callable|null $callable A callable implementing the function. If null, you need to overwrite the "node_class" option to customize compilation. + * @param callable|array{class-string, string}|null $callable A callable implementing the function. If null, you need to overwrite the "node_class" option to customize compilation. */ public function __construct(string $name, $callable = null, array $options = []) { - $this->name = $name; - $this->callable = $callable; + parent::__construct($name, $callable, $options); + $this->options = array_merge([ - 'needs_environment' => false, - 'needs_context' => false, - 'is_variadic' => false, 'is_safe' => null, 'is_safe_callback' => null, 'node_class' => FunctionExpression::class, - 'deprecated' => false, - 'alternative' => null, - ], $options); + 'parser_callable' => null, + ], $this->options); } - public function getName(): string + public function getType(): string { - return $this->name; + return 'function'; } - /** - * Returns the callable to execute for this function. - * - * @return callable|null - */ - public function getCallable() + public function getParserCallable(): ?callable { - return $this->callable; - } - - public function getNodeClass(): string - { - return $this->options['node_class']; - } - - public function setArguments(array $arguments): void - { - $this->arguments = $arguments; - } - - public function getArguments(): array - { - return $this->arguments; - } - - public function needsEnvironment(): bool - { - return $this->options['needs_environment']; - } - - public function needsContext(): bool - { - return $this->options['needs_context']; + return $this->options['parser_callable']; } public function getSafe(Node $functionArgs): ?array @@ -99,24 +60,4 @@ final class TwigFunction return []; } - - public function isVariadic(): bool - { - return (bool) $this->options['is_variadic']; - } - - public function isDeprecated(): bool - { - return (bool) $this->options['deprecated']; - } - - public function getDeprecatedVersion(): string - { - return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; - } - - public function getAlternative(): ?string - { - return $this->options['alternative']; - } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/TwigTest.php b/data/web/inc/lib/vendor/twig/twig/src/TwigTest.php index 4c18632f5..5e58ad8b0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TwigTest.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TwigTest.php @@ -20,81 +20,48 @@ use Twig\Node\Expression\TestExpression; * * @see https://twig.symfony.com/doc/templates.html#test-operator */ -final class TwigTest +final class TwigTest extends AbstractTwigCallable { - private $name; - private $callable; - private $options; - private $arguments = []; - /** - * @param callable|null $callable A callable implementing the test. If null, you need to overwrite the "node_class" option to customize compilation. + * @param callable|array{class-string, string}|null $callable A callable implementing the test. If null, you need to overwrite the "node_class" option to customize compilation. */ public function __construct(string $name, $callable = null, array $options = []) { - $this->name = $name; - $this->callable = $callable; + parent::__construct($name, $callable, $options); + $this->options = array_merge([ - 'is_variadic' => false, 'node_class' => TestExpression::class, - 'deprecated' => false, - 'alternative' => null, 'one_mandatory_argument' => false, - ], $options); + ], $this->options); } - public function getName(): string + public function getType(): string { - return $this->name; + return 'test'; } - /** - * Returns the callable to execute for this test. - * - * @return callable|null - */ - public function getCallable() + public function needsCharset(): bool { - return $this->callable; + return false; } - public function getNodeClass(): string + public function needsEnvironment(): bool { - return $this->options['node_class']; + return false; } - public function setArguments(array $arguments): void + public function needsContext(): bool { - $this->arguments = $arguments; - } - - public function getArguments(): array - { - return $this->arguments; - } - - public function isVariadic(): bool - { - return (bool) $this->options['is_variadic']; - } - - public function isDeprecated(): bool - { - return (bool) $this->options['deprecated']; - } - - public function getDeprecatedVersion(): string - { - return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; - } - - public function getAlternative(): ?string - { - return $this->options['alternative']; + return false; } public function hasOneMandatoryArgument(): bool { return (bool) $this->options['one_mandatory_argument']; } + + public function getMinimalNumberOfRequiredArguments(): int + { + return parent::getMinimalNumberOfRequiredArguments() + 1; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Util/CallableArgumentsExtractor.php b/data/web/inc/lib/vendor/twig/twig/src/Util/CallableArgumentsExtractor.php new file mode 100644 index 000000000..8811ca9c8 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Util/CallableArgumentsExtractor.php @@ -0,0 +1,204 @@ + + * + * @internal + */ +final class CallableArgumentsExtractor +{ + private ReflectionCallable $rc; + + public function __construct( + private Node $node, + private TwigCallableInterface $twigCallable, + ) { + $this->rc = new ReflectionCallable($twigCallable); + } + + /** + * @return array + */ + public function extractArguments(Node $arguments): array + { + $extractedArguments = []; + $named = false; + foreach ($arguments as $name => $node) { + if (!\is_int($name)) { + $named = true; + $name = $this->normalizeName($name); + } elseif ($named) { + throw new SyntaxError(\sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); + } + + $extractedArguments[$name] = $node; + } + + if (!$named && !$this->twigCallable->isVariadic()) { + $min = $this->twigCallable->getMinimalNumberOfRequiredArguments(); + if (\count($extractedArguments) < $this->rc->getReflector()->getNumberOfRequiredParameters() - $min) { + throw new SyntaxError(\sprintf('Value for argument "%s" is required for %s "%s".', $this->rc->getReflector()->getParameters()[$min + \count($extractedArguments)]->getName(), $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); + } + + return $extractedArguments; + } + + if (!$callable = $this->twigCallable->getCallable()) { + if ($named) { + throw new SyntaxError(\sprintf('Named arguments are not supported for %s "%s".', $this->twigCallable->getType(), $this->twigCallable->getName())); + } + + throw new SyntaxError(\sprintf('Arbitrary positional arguments are not supported for %s "%s".', $this->twigCallable->getType(), $this->twigCallable->getName())); + } + + [$callableParameters, $isPhpVariadic] = $this->getCallableParameters(); + $arguments = []; + $names = []; + $missingArguments = []; + $optionalArguments = []; + $pos = 0; + foreach ($callableParameters as $callableParameter) { + $name = $this->normalizeName($callableParameter->name); + if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) { + if ('start' === $name) { + $name = 'low'; + } elseif ('end' === $name) { + $name = 'high'; + } + } + + $names[] = $name; + + if (\array_key_exists($name, $extractedArguments)) { + if (\array_key_exists($pos, $extractedArguments)) { + throw new SyntaxError(\sprintf('Argument "%s" is defined twice for %s "%s".', $name, $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); + } + + if (\count($missingArguments)) { + throw new SyntaxError(\sprintf( + 'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".', + $name, $this->twigCallable->getType(), $this->twigCallable->getName(), implode(', ', $names), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments) + ), $this->node->getTemplateLine(), $this->node->getSourceContext()); + } + + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $extractedArguments[$name]; + unset($extractedArguments[$name]); + $optionalArguments = []; + } elseif (\array_key_exists($pos, $extractedArguments)) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $extractedArguments[$pos]; + unset($extractedArguments[$pos]); + $optionalArguments = []; + ++$pos; + } elseif ($callableParameter->isDefaultValueAvailable()) { + $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), $this->node->getTemplateLine()); + } elseif ($callableParameter->isOptional()) { + if (!$extractedArguments) { + break; + } + + $missingArguments[] = $name; + } else { + throw new SyntaxError(\sprintf('Value for argument "%s" is required for %s "%s".', $name, $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); + } + } + + if ($this->twigCallable->isVariadic()) { + $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], $this->node->getTemplateLine()) : new ArrayExpression([], $this->node->getTemplateLine()); + foreach ($extractedArguments as $key => $value) { + if (\is_int($key)) { + $arbitraryArguments->addElement($value); + } else { + $arbitraryArguments->addElement($value, new ConstantExpression($key, $this->node->getTemplateLine())); + } + unset($extractedArguments[$key]); + } + + if ($arbitraryArguments->count()) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $arbitraryArguments; + } + } + + if ($extractedArguments) { + $unknownArgument = null; + foreach ($extractedArguments as $extractedArgument) { + if ($extractedArgument instanceof Node) { + $unknownArgument = $extractedArgument; + break; + } + } + + throw new SyntaxError( + \sprintf( + 'Unknown argument%s "%s" for %s "%s(%s)".', + \count($extractedArguments) > 1 ? 's' : '', implode('", "', array_keys($extractedArguments)), $this->twigCallable->getType(), $this->twigCallable->getName(), implode(', ', $names) + ), + $unknownArgument ? $unknownArgument->getTemplateLine() : $this->node->getTemplateLine(), + $unknownArgument ? $unknownArgument->getSourceContext() : $this->node->getSourceContext() + ); + } + + return $arguments; + } + + private function normalizeName(string $name): string + { + return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name)); + } + + private function getCallableParameters(): array + { + $parameters = $this->rc->getReflector()->getParameters(); + if ($this->node->hasNode('node')) { + array_shift($parameters); + } + if ($this->twigCallable->needsCharset()) { + array_shift($parameters); + } + if ($this->twigCallable->needsEnvironment()) { + array_shift($parameters); + } + if ($this->twigCallable->needsContext()) { + array_shift($parameters); + } + foreach ($this->twigCallable->getArguments() as $argument) { + array_shift($parameters); + } + + $isPhpVariadic = false; + if ($this->twigCallable->isVariadic()) { + $argument = end($parameters); + $isArray = $argument && $argument->hasType() && $argument->getType() instanceof \ReflectionNamedType && 'array' === $argument->getType()->getName(); + if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) { + array_pop($parameters); + } elseif ($argument && $argument->isVariadic()) { + array_pop($parameters); + $isPhpVariadic = true; + } else { + throw new SyntaxError(\sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $this->rc->getName(), $this->twigCallable->getType(), $this->twigCallable->getName())); + } + } + + return [$parameters, $isPhpVariadic]; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Util/DeprecationCollector.php b/data/web/inc/lib/vendor/twig/twig/src/Util/DeprecationCollector.php index 378b666bd..0ea26ed4b 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Util/DeprecationCollector.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Util/DeprecationCollector.php @@ -20,11 +20,9 @@ use Twig\Source; */ final class DeprecationCollector { - private $twig; - - public function __construct(Environment $twig) - { - $this->twig = $twig; + public function __construct( + private Environment $twig, + ) { } /** @@ -60,6 +58,8 @@ final class DeprecationCollector if (\E_USER_DEPRECATED === $type) { $deprecations[] = $msg; } + + return false; }); foreach ($iterator as $name => $contents) { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Util/ReflectionCallable.php b/data/web/inc/lib/vendor/twig/twig/src/Util/ReflectionCallable.php new file mode 100644 index 000000000..16734d9df --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Util/ReflectionCallable.php @@ -0,0 +1,92 @@ + + * + * @internal + */ +final class ReflectionCallable +{ + private $reflector; + private $callable; + private $name; + + public function __construct( + TwigCallableInterface $twigCallable, + ) { + $callable = $twigCallable->getCallable(); + if (\is_string($callable) && false !== $pos = strpos($callable, '::')) { + $callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)]; + } + + if (\is_array($callable) && method_exists($callable[0], $callable[1])) { + $this->reflector = $r = new \ReflectionMethod($callable[0], $callable[1]); + $this->callable = $callable; + $this->name = $r->class.'::'.$r->name; + + return; + } + + $checkVisibility = $callable instanceof \Closure; + try { + $closure = \Closure::fromCallable($callable); + } catch (\TypeError $e) { + throw new \LogicException(\sprintf('Callback for %s "%s" is not callable in the current scope.', $twigCallable->getType(), $twigCallable->getName()), 0, $e); + } + $this->reflector = $r = new \ReflectionFunction($closure); + + if (str_contains($r->name, '{closure')) { + $this->callable = $callable; + $this->name = 'Closure'; + + return; + } + + if ($object = $r->getClosureThis()) { + $callable = [$object, $r->name]; + $this->name = get_debug_type($object).'::'.$r->name; + } elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) { + $callable = [$class->name, $r->name]; + $this->name = $class->name.'::'.$r->name; + } elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) { + $callable = [\is_array($callable) ? $callable[0] : $class->name, $r->name]; + $this->name = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name; + } else { + $callable = $this->name = $r->name; + } + + if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) { + $callable = $r->getClosure(); + } + + $this->callable = $callable; + } + + public function getReflector(): \ReflectionFunctionAbstract + { + return $this->reflector; + } + + public function getCallable() + { + return $this->callable; + } + + public function getName(): string + { + return $this->name; + } +} diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php index 8fa5dcd98..7f330ba1c 100644 --- a/data/web/inc/triggers.inc.php +++ b/data/web/inc/triggers.inc.php @@ -46,7 +46,7 @@ if (isset($_POST["pw_reset_request"]) && !empty($_POST['username'])) { if (isset($_POST["pw_reset"])) { $username = reset_password("check", $_POST['token']); $reset_result = reset_password("reset", array( - 'new_password' => $_POST['new_password'], + 'new_password' => $_POST['new_password'], 'new_password2' => $_POST['new_password2'], 'token' => $_POST['token'], 'username' => $username, @@ -66,7 +66,7 @@ if (isset($_POST["verify_tfa_login"])) { unset($_SESSION['pending_mailcow_cc_username']); unset($_SESSION['pending_mailcow_cc_role']); unset($_SESSION['pending_tfa_methods']); - + header("Location: /debug"); die(); } @@ -76,7 +76,7 @@ if (isset($_POST["verify_tfa_login"])) { unset($_SESSION['pending_mailcow_cc_username']); unset($_SESSION['pending_mailcow_cc_role']); unset($_SESSION['pending_tfa_methods']); - + header("Location: /mailbox"); die(); } @@ -92,7 +92,7 @@ if (isset($_POST["verify_tfa_login"])) { unset($_SESSION['pending_pw_new_password']); unset($_SESSION['pending_mailcow_cc_username']); unset($_SESSION['pending_tfa_methods']); - + header("Location: /"); die(); } else { @@ -141,11 +141,13 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { $_SESSION['mailcow_cc_username'] = $login_user; $_SESSION['mailcow_cc_role'] = "admin"; header("Location: /debug"); + die(); } elseif ($as == "domainadmin") { $_SESSION['mailcow_cc_username'] = $login_user; $_SESSION['mailcow_cc_role'] = "domainadmin"; header("Location: /mailbox"); + die(); } elseif ($as == "user") { set_user_loggedin_session($login_user); diff --git a/data/web/js/site/edit.js b/data/web/js/site/edit.js index d68954989..f9fe707c6 100644 --- a/data/web/js/site/edit.js +++ b/data/web/js/site/edit.js @@ -58,6 +58,11 @@ $(document).ready(function() { $('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val()); }); + $("#show_mailbox_rename_form").click(function() { + $("#rename_warning").hide(); + $("#rename_form").removeClass("d-none"); + }); + // load tags if ($('#tags').length){ var tagsEl = $('#tags').parent().find('.tag-values')[0]; diff --git a/data/web/json_api.php b/data/web/json_api.php index 70bda9383..a3103ffa1 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -509,7 +509,7 @@ if (isset($_GET['query'])) { print(json_encode($getArgs)); $_SESSION['challenge'] = $WebAuthn->getChallenge(); return; - break; + break; case "fail2ban": if (!isset($_SESSION['mailcow_cc_role'])){ switch ($object) { @@ -2036,6 +2036,9 @@ if (isset($_GET['query'])) { case "rl-mbox": process_edit_return(ratelimit('edit', 'mailbox', array_merge(array('object' => $items), $attr))); break; + case "rename-mbox": + process_edit_return(mailbox('edit', 'mailbox_rename', array_merge(array('mailbox' => $items), $attr))); + break; case "user-acl": process_edit_return(acl('edit', 'user', array_merge(array('username' => $items), $attr))); break; diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index ddbbc3426..dac01d4e2 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -643,6 +643,11 @@ "mailbox": "Mailbox bearbeiten", "mailbox_quota_def": "Standard-Quota einer Mailbox", "mailbox_relayhost_info": "Wird auf eine Mailbox und direkte Alias-Adressen angewendet. Überschreibt die Einstellung einer Domain.", + "mailbox_rename": "Mailbox umbenennen", + "mailbox_rename_agree": "Ich habe ein Backup erstellt.", + "mailbox_rename_warning": "WICHTIG! Vor dem Umbenennen der Mailbox ein Backup erstellen.", + "mailbox_rename_alias": "Alias automatisch erstellen", + "mailbox_rename_title": "Neuer Lokaler Mailbox Name", "max_aliases": "Max. Aliasse", "max_mailboxes": "Max. Mailboxanzahl", "max_quota": "Max. Größe per Mailbox (MiB)", @@ -766,7 +771,7 @@ "login": "Anmelden", "mobileconfig_info": "Bitte als Mailbox-Benutzer einloggen, um das Verbindungsprofil herunterzuladen.", "new_password": "Neues Passwort", - "new_password_confirm": "Neues Passwort bestätigen", + "new_password_confirm": "Neues Passwort bestätigen", "other_logins": "Key Login", "password": "Passwort", "reset_password": "Passwort zurücksetzen", @@ -1086,6 +1091,7 @@ "mailbox_added": "Mailbox %s wurde angelegt", "mailbox_modified": "Änderungen an Mailbox %s wurden gespeichert", "mailbox_removed": "Mailbox %s wurde entfernt", + "mailbox_renamed": "Mailbox wurde von %s in %s umbenannt", "nginx_reloaded": "Nginx wurde neu geladen", "object_modified": "Änderungen an Objekt %s wurden gespeichert", "password_policy_saved": "Passwortrichtlinie wurde erfolgreich gespeichert", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index 36d886f7f..5c9c1b21b 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -680,6 +680,11 @@ "mailbox": "Edit mailbox", "mailbox_quota_def": "Default mailbox quota", "mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.", + "mailbox_rename": "Rename mailbox", + "mailbox_rename_agree": "I have created a backup.", + "mailbox_rename_warning": "IMPORTANT! Create a backup before renaming the mailbox.", + "mailbox_rename_alias": "Create alias automatically", + "mailbox_rename_title": "New local mailbox name", "max_aliases": "Max. aliases", "max_mailboxes": "Max. possible mailboxes", "max_quota": "Max. quota per mailbox (MiB)", @@ -1132,6 +1137,7 @@ "mailbox_added": "Mailbox %s has been added", "mailbox_modified": "Changes to mailbox %s have been saved", "mailbox_removed": "Mailbox %s has been removed", + "mailbox_renamed": "Mailbox was renamed from %s to %s", "nginx_reloaded": "Nginx was reloaded", "object_modified": "Changes to object %s have been saved", "password_policy_saved": "Password policy was saved successfully", diff --git a/data/web/lang/lang.lv-lv.json b/data/web/lang/lang.lv-lv.json index 5f486d909..98cb04ae8 100644 --- a/data/web/lang/lang.lv-lv.json +++ b/data/web/lang/lang.lv-lv.json @@ -40,7 +40,7 @@ "exclude": "Izslēgt objektus (regex)", "full_name": "Pilns vārds", "goto_null": "Klusām dzēst pastu", - "hostname": "Hosta nosaukums", + "hostname": "Saimniekdators", "kind": "Veids", "mailbox_quota_m": "Maks. kvota pastkastei (MiB)", "mailbox_username": "Lietotājvārds (kriesā daļa no epasta adreses)", @@ -69,7 +69,9 @@ "username": "Lietotājvārds", "validate": "Apstiprināt", "validation_success": "Apstiprināts veiksmīgi", - "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." + "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)" }, "admin": { "access": "Pieeja", @@ -114,7 +116,7 @@ "forwarding_hosts": "Hostu pārsūtīšana", "forwarding_hosts_add_hint": "Var norādīt vai nu IPv4/IPv6 addreses, 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).", "forwarding_hosts_hint": "Ienākošie ziņojumi tiek bez nosacījumiem pieņemti no visiem šeit norādītajiem saimniekdatoriem. Tie tad netiek pārbaudīti pret DNSBL vai pakļauti ievietošanai pelēkajā sarakstā. No tiem saņemtās mēstules nekad netiek noraidītas, bet pēc izvēles tās var pārvietot mapē \"Nevēlams\". Visbiežāk to izmanto, lai norādītu pasta serverus, kuros ir uzstādīts nosacījums, kas pārsūta ienākošās e-pasta vēstules uz Tavu mailcow serveri.", - "help_text": "Ignorēt palīdzības tekstu zem pieteikšanās maskas (HTML ir atļauta)", + "help_text": "Pārrakstīt palīdzības tekstu zem pieteikšanās maskas (var izmantot HTML)", "host": "Hosts", "import": "Importēt", "import_private_key": "Importēt privātu atslēgu", @@ -165,7 +167,10 @@ "f2b_regex_info": "Vērā ņemtie žurnāli: SOGO, Postfix, Dovecot, PHP-FPM.", "sys_mails": "Sistēmas pasts", "ip_check_disabled": "IP pārbaude ir atspējota. To var iespējot
Sistēma > Konfigurācija > iespējas > Pielāgot", - "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ā" + "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" }, "danger": { "access_denied": "Piekļuve liegta, vai nepareizi dati", @@ -192,7 +197,7 @@ "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.", "is_spam_alias": "%s jau ir zināma kā pagaidu aizstājadrese (mēstuļu aizstājadrese)", "last_key": "Pēdējo atslēgu nevar izdzēst, tā vietā jāatspējo divpakāpju pārbaude.", - "login_failed": "Ielogošanās neveiksmīga", + "login_failed": "Pieteikšanās neizdevās", "mailbox_invalid": "Pastkastes vārds ir nederīgs", "mailbox_quota_exceeded": "Kvota pārsniedz domēna limitu (max. %d MiB)", "mailbox_quota_exceeds_domain_quota": "Pastkastes izmērs pārsniedz maksimāli pieļaujamo", @@ -216,7 +221,8 @@ "target_domain_invalid": "Goto domēns ir nepareizs", "targetd_not_found": "Mērķa domēns nav atrasts", "username_invalid": "Lietotājvārds nevar tikt izmantots", - "validity_missing": "Lūdzu piešķiriet derīguma termiņu" + "validity_missing": "Lūdzu piešķiriet derīguma termiņu", + "domain_cannot_match_hostname": "Domēns nevar atbilst saimniekdatora nosaukumam" }, "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.", @@ -245,17 +251,17 @@ "edit_alias_domain": "Labot aizstājdomēnu", "encryption": "Šifrēšana", "exclude": "Neiekļaut objektus (regex)", - "force_pw_update": "Piespiedu paroles atjaunošana pie nākošās pieslēgšanās", - "force_pw_update_info": "Šis lietotājs varēs pieslēgties tikai %s.", + "force_pw_update": "Piespiedu paroles atjaunošana nākamajā pieteikšanās reizē", + "force_pw_update_info": "Šis lietotājs varēs pieteikties tikai %s. Lietotņu paroles joprojām būs izmantojamas.", "full_name": "Pilns vārds", - "hostname": "Hosta nosaukums", + "hostname": "Saimniekdatora nosaukums", "inactive": "Neaktīvs", "kind": "Veids", "mailbox": "Rediģēt pastkasti", "max_aliases": "Lielākais aizstājvārdu skaits", "max_mailboxes": "Maks. iespējamās pastkastes", "max_quota": "Maks. kvota uz pastkasti (MiB)", - "maxage": "Maximum age of messages in days that will be polled from remote
(0 = ignore age)", + "maxage": "Lielākais 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,16 +298,19 @@ "mailbox_relayhost_info": "Tiek piemērots tikai pastkastei un tiešajiem aizstājvārdiem, aizstāj domēna retranslācijas saimniekdatoru.", "sender_acl_info": "Ja pastkastes lietotājam A ir ļauts sūtīt kā pastkastes lietotājam B, sūtītāja adrese SOGo netiek automātiski parādīta kā atlasāms lauks \"no\".
.\n Pastkastes lietotājam B SOGo ir jāizveido pilnvarojums, lai pastkastes lietotājs A varētu izvēlēties tā adresi kā sūtītāja. Lai SOGo pilnvarotu pastkasti, pasta skatā jāizmanto izvēlne (trīs punkti) pa labi no pastkastes nosaukuma augšējā kreisajā pusē. Šī darbība neattiecas uz aizstājadresēm.", "sogo_visible": "Aizstājvārds ir redzams SOGo", - "sogo_visible_info": "Šī iespēja ietekmē tikai tos objektus, kurus var parādīt SOGo (koplietojamās vai nekoplietojamās aizstājadreses, kas norāda uz vismaz vienu vietējo pastkasti). Ja paslēpts, netiks parādīts SOGo kā atlasāms sūtītājs." + "sogo_visible_info": "Šī iespēja ietekmē tikai tos objektus, kurus var parādīt SOGo (koplietojamās vai nekoplietojamās aizstājadreses, kas norāda uz vismaz vienu vietējo pastkasti). Ja paslēpts, netiks parādīts SOGo kā atlasāms sūtītājs.", + "mbox_rl_info": "Šis pieprasījumu ierobežojums tiek piemērots SASL pieteikšanās vārdam, tas atbilst jebkurai \"from\" adresei, ko izmanto lietotājs, kurš ir pieteicies. Pastkastes pieprasījumu ierobežojums pārraksta domēna mēroga pieprasījumu ierobežojumu.", + "sogo_access": "Nodrošināt tiešu pieteikšanās piekļuvi SOGo", + "disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)" }, "footer": { "cancel": "Atcelt", "confirm_delete": "Apstiprināt dzēšanu", "delete_now": "Dzēst tagad", "delete_these_items": "Lūgums apstiprināt izmaiņas šim objekta Id", - "loading": "Lūdzu uzgaidiet...", + "loading": "Lūgums uzgaidīt...", "restart_container": "Restartēt konteineri", - "restart_container_info": "Important: Piespiedu restartēšana var aizņemt ilgu laiku, lūdzu uzgaidiet.", + "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" }, "header": { @@ -318,14 +327,17 @@ "no_action": "No action applicable" }, "login": { - "delayed": "Pieteikšanās tika aizkavēta %s sekundēm.", - "login": "Pieslēgties", + "delayed": "Pieteikšanās tika aizkavēta %s sekundes.", + "login": "Pieteikties", "password": "Parole", - "username": "Lietotājvārds" + "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": "Pieteikšanās ar atslēgu" }, "mailbox": { "action": "Rīcība", - "activate": "Activate", + "activate": "Aktivizēt", "active": "Aktīvs", "add": "Pievienot", "add_alias": "Pievienot aizstājvārdu", @@ -367,7 +379,7 @@ "filter_table": "Filtra tabula", "filters": "Filtri", "fname": "Pilns vārds", - "force_pw_update": "Piespiedu paroles atjaunošana pie nākošās pieslēgšanās", + "force_pw_update": "Piespiedu paroles atjaunināšana nākamajā pieteikšanās reizē", "in_use": "Lietošanā (%)", "inactive": "Neaktīvs", "kind": "Veids", @@ -420,7 +432,8 @@ "sogo_visible_y": "Rādīt aizstājvārdu SOGo", "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" + "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)" }, "quarantine": { "action": "Darbības", @@ -449,12 +462,12 @@ "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." }, "queue": { - "queue_manager": "Queue Manager", + "queue_manager": "Rindas pārvaldnieks", "info": "Pasta rinda satur visus e-pastus, kas gaida piegādi. Ja e-pasts ir iestrēdzis pasta rindā ilgu laiku, sistēma to automātiski izdzēš.
Attiecīgā pasta kļūdas ziņojums sniedz informāciju par to, kāpēc pastu neizdevās piegādāt." }, "start": { - "help": "Rādīt/Paslēp palīdzības paneli", - "imap_smtp_server_auth_info": "Lūgums izmantot pilnu e-pasta adresi un PLAIN autentifikācijas mehānismu.
\nPieteikšanās dati tiks šifrēti ar servera puses obligātu šifrēšanu.", + "help": "Rādīt/Paslēpt palīdzības paneli", + "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.", "mailcow_apps_detail": "Izmantojiet lietotni mailcow, lai piekļūtu savam pastam, kalendāram, kontaktiem un citām lietām.", "mailcow_panel_detail": "Domēna pārvaldītāji izveido, maina vai izdzēš pastkastes un aizstājvārdus, maina domēnus un lasa papildu informāciju par piešķirtajiem domēniem.
\nPastkastes lietotāji var izveidot laikā ierobežotus aizstājvārdus (surogātpasta aizstājvārdus), mainīt savu paroli un surogātpasta atlasīšanas iestatījumus." }, @@ -489,18 +502,22 @@ "relayhost_added": "Relejhosts %s pievienots", "relayhost_removed": "Relejhosts %s noņemts", "reset_main_logo": "Atjaunot noklusējuma logotipu", - "resource_added": "Resource %s has been added", + "resource_added": "Avots %s tika pievienots", "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": "Faila augšupielāde veiksmīga", + "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" }, "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", "delete_tfa": "Atspējot TFA", - "disable_tfa": "Atspējot TFA līdz nākamajai veiksmīgai pieteikšanās", + "disable_tfa": "Atspējot TFA līdz nākamajai veiksmī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", @@ -513,7 +530,8 @@ "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.", - "yubi_otp": "Yubico OTP autentifikators" + "yubi_otp": "Yubico OTP autentifikators", + "authenticators": "Autentificētāji" }, "user": { "action": "Rīcība", @@ -557,7 +575,7 @@ "no_record": "Nav ieraksta", "password_now": "Pašreizējā parole (Apstiprināt izmaiņas)", "remove": "Noņemt", - "running": "Running", + "running": "Darbojas", "save_changes": "Saglabāt izmaiņas", "shared_aliases": "Kopīgotās aizstājadreses", "shared_aliases_desc": "Tādi lietotāja iestatījumi kā surogātpasta atlasīšana vai šifrēšanas nosacījumi neietekmē kopīgotos aizstājvārdus. Atbilstošu surogātpasta atlasi var izveidot tikai pārvaldītājs kā domēnu aptverošu nosacījumu.", @@ -591,15 +609,22 @@ "tls_enforce_in": "Piespiest TLS ienākošajiem", "tls_enforce_out": "Piespiest TLS izejošajiem", "tls_policy": "Šifrēšanas politika", - "tls_policy_warning": "Brīdinājums: Ja tiek izlemts ieviest šifrēta pasta nosūtīšanu, var tikt pazaudēti e-pasti.
Ziņojumi, kas neatbilst nosacījumiem, pasta sistēma atmetīs ar kļūdu.
Šī iespēja attiecas uz galveno e-pasta adresi (pieteikšanās vārdu), visām adresēm, kas atvasinātas no aizsājdomēniem, kā arī aizstājadreses, kas norāda tikai uz šo pastkasti.", + "tls_policy_warning": "Brīdinājums: ja tiek izlemts ieviest šifrēta pasta nosūtīšanu, var tikt pazaudēti e-pasta ziņojumi.
Ziņojumi, kas neatbilst nosacījumiem, pasta sistēma atmetīs ar kļūdu.
Šī iespēja attiecas uz galveno e-pasta adresi (pieteikšanās vārdu), visām adresēm, kas atvasinātas no aizstājdomēniem, kā arī aizstājadreses, kas norāda tikai uz šo pastkasti.", "user_settings": "Lietotāja iestatījumi", "username": "Lietotājvārds", - "waiting": "Waiting", + "waiting": "Gaida", "week": "Nedēļa", "weeks": "Nedēļas", "open_logs": "Atvērt žurnālus", "apple_connection_profile_mailonly": "Šis savienojuma profils iekļauj IMAP un SMTP konfigurācijas parametrus Apple ierīcei.", - "pushover_info": "Pašpiegādes paziņojumu iestatījumi attieksies uz visu tīro (ne surogātpasta) pastu, kas piegādāts uz %s, ieskaitot aizstājvārdus (kopīgotus, nekopīgotus, ar birkām)." + "pushover_info": "Pašpiegādes paziņojumu iestatījumi attieksies uz visu tīro (ne surogātpasta) pastu, kas piegādāts uz %s, ieskaitot aizstājvārdus (kopīgotus, nekopīgotus, ar birkām).", + "app_hint": "Lietotņu paroles ir aizstājējparoles, lai pieteiktos IMAP, SMTP, CalDAV, CardDAV un EAS. Lietotājvārds paliek nemainīgs. SOGo tīmekļa pasts nav pieejams ar lietotņu parolēm.", + "direct_protocol_access": "Šim pastkastes lietotājam ir tieša, ārēja piekļuve zemāk uzskaitītajiem protokoliem un lietotnēm. Šo iestatījumu pārrauga pārvaldītājs. Lietotņu paroles var izveidot, lai nodrošinātu piekļuvi atsevišķiem protokoliem un lietotnēm.
Poga \"Pieteikties tīmekļa pastā\" nodrošina vienotu pieteikšanos SOGo un vienmēr ir pieejama.", + "last_ui_login": "Pēdējā pieteikšanās saskarnē", + "login_history": "Pieteikšanās vēsture", + "no_last_login": "Nav informācijas par pēdējām pieteikšanās saskarnē reizēm", + "open_webmail_sso": "Pieteikšanās tīmekļa pastā", + "last_mail_login": "Pēdējā pasta pieteikšanās" }, "datatables": { "paginate": { @@ -615,11 +640,23 @@ "system_containers": "Sistēma un konteineri", "current_time": "Sistēmas laiks", "external_logs": "Ārējie žurnāli", - "logs": "Žurnāli" + "logs": "Žurnāli", + "architecture": "Arhitektūra", + "disk_usage": "Diska lietojums", + "jvm_memory_solr": "JVM atmiņas lietojums", + "memory": "Atmiņa", + "timezone": "Laika josla", + "uptime": "Darbošanās laiks" }, "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" + }, + "oauth2": { + "access_denied": "Lūgums pieteikties kā pastkastes īpašniekam, lai nodrošinātu piekļuvi ar OAuth2." + }, + "fido2": { + "fido2_auth": "Pieteikties ar FIDO2" } } diff --git a/data/web/lang/lang.pt-br.json b/data/web/lang/lang.pt-br.json index 1059ba994..2fbd43535 100644 --- a/data/web/lang/lang.pt-br.json +++ b/data/web/lang/lang.pt-br.json @@ -14,6 +14,7 @@ "prohibited": "Proibido pela ACL", "protocol_access": "Alterar o acesso ao protocolo", "pushover": "Pushover", + "pw_reset": "Permite resetar a senha do usuário do Mailcow", "quarantine": "Ações de quarentena", "quarantine_attachments": "Anexos de quarentena", "quarantine_category": "Alterar categoria de notificação de quarentena", @@ -28,8 +29,7 @@ "spam_score": "Pontuação de spam", "syncjobs": "Trabalhos de sincronização", "tls_policy": "Política de TLS", - "unlimited_quota": "Cota ilimitada para mailboxes", - "pw_reset": "Permite redefinir a senha do usuário" + "unlimited_quota": "Cota ilimitada para mailboxes" }, "add": { "activate_filter_warn": "Todos os outros filtros serão desativados quando a opção ativa estiver marcada.", @@ -155,6 +155,7 @@ "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!", "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", @@ -188,6 +189,8 @@ "f2b_blacklist": "Redes/hosts na lista negra", "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_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", "f2b_max_ban_time": "Tempo (s) máximo (s) de banimento", "f2b_netban_ipv4": "Tamanho da sub-rede IPv4 a ser proibida (8-32)", @@ -254,6 +257,11 @@ "password_policy_numbers": "Deve conter pelo menos um número", "password_policy_special_chars": "Deve conter caracteres especiais", "password_repeat": "Senha de confirmação (repetição)", + "password_reset_info": "Se nenhum email de recuperação for definido, esta função não poderá ser utilizada.", + "password_reset_settings": "Configurações de recuperação de senha", + "password_reset_tmpl_html": "Template HTML", + "password_reset_tmpl_text": "Template de Texto", + "password_settings": "Configurações de senha", "priority": "Prioridade", "private_key": "Chave privada", "quarantine": "Quarentena", @@ -294,6 +302,8 @@ "remove_row": "Remover linha", "reset_default": "Redefinir para o padrão", "reset_limit": "Remover o hash", + "reset_password_vars": "{{link}} Link de recuperação de senha gerado
{{username}} nome da caixa de email do usuário que requisitou a recuperação de senha
{{username2}} Nome da caixa de email recuperada
{{date}} Data que a requisição de recuperação de senha foi feita
{{token_lifetime}} O tempo de vida em minutos do token
{{hostname}} O servidor do mailcow", + "restore_template": "Deixe limpo para o template padrão de recuperação.", "routing": "Roteamento", "rsetting_add_rule": "Adicionar regra", "rsetting_content": "Conteúdo da regra", @@ -349,10 +359,7 @@ "username": "Nome de usuário", "validate_license_now": "Valide o GUID em relação ao servidor de licenças", "verify": "Verificar", - "yes": "✓", - "copy_to_clipboard": "Texto copiado para a área de transferência!", - "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." + "yes": "✓" }, "danger": { "access_denied": "Acesso negado ou dados de formulário inválidos", @@ -395,7 +402,9 @@ "goto_invalid": "O endereço Goto %s é inválido", "ham_learn_error": "Erro de aprendizado do Ham: %s", "imagick_exception": "Erro: exceção Imagick ao ler a imagem", + "img_dimensions_exceeded": "A imagem excede o tamanho máximo", "img_invalid": "Não é possível validar o arquivo de imagem", + "img_size_exceeded": "A imagem excede o tamanho máximo", "img_tmp_missing": "Não é possível validar o arquivo de imagem: Arquivo temporário não encontrado", "invalid_bcc_map_type": "Tipo de mapa BCC inválido", "invalid_destination": "O formato de destino “%s” é inválido", @@ -406,6 +415,7 @@ "invalid_nexthop_authenticated": "O próximo salto existe com credenciais diferentes. Primeiro, atualize as credenciais existentes para o próximo salto.", "invalid_recipient_map_new": "Novo destinatário inválido especificado: %s", "invalid_recipient_map_old": "Destinatário original inválido especificado: %s", + "invalid_reset_token": "Token de recuperação inválido", "ip_list_empty": "A lista de IPs permitidos não pode estar vazia", "is_alias": "%s já é conhecido como endereço de alias", "is_alias_or_mailbox": "%s já é conhecido como alias, mailbox ou alias de endereço expandido a partir de um domínio de alias.", @@ -435,6 +445,8 @@ "password_complexity": "A senha não atende à política", "password_empty": "A senha não deve estar vazia", "password_mismatch": "A senha de confirmação não corresponde", + "password_reset_invalid_user": "Caixa de email não encontrada ou email de recuperação não definido", + "password_reset_na": "A recuperação de senha está indisponível no momento. Por favor, contacte o administrador.", "policy_list_from_exists": "Existe um registro com nome próprio", "policy_list_from_invalid": "O registro tem formato inválido", "private_key_error": "Erro de chave privada: %s", @@ -443,10 +455,12 @@ "pushover_token": "O token Pushover tem um formato errado", "quota_not_0_not_numeric": "A cota deve ser numérica e >= 0", "recipient_map_entry_exists": "Existe uma entrada de mapa de destinatários “%s”", + "recovery_email_failed": "Não foi possível enviar um email de recuperação. Por favor, contacte seu administrador.", "redis_error": "Erro do Redis: %s", "relayhost_invalid": "A entrada de mapa %s é inválida", "release_send_failed": "A mensagem não pôde ser liberada: %s", "reset_f2b_regex": "O filtro Regex não pôde ser redefinido a tempo. Tente novamente ou aguarde mais alguns segundos e recarregue o site.", + "reset_token_limit_exceeded": "O limite de token de recuperação foi excedido. Por favor tente novamente mais tarde.", "resource_invalid": "O nome do recurso %s é inválido", "rl_timeframe": "O prazo do limite de taxa está incorreto", "rspamd_ui_pw_length": "A senha do Rspamd UI deve ter pelo menos 6 caracteres", @@ -469,6 +483,7 @@ "tls_policy_map_dest_invalid": "O destino da política é inválido", "tls_policy_map_entry_exists": "Existe uma entrada “%s” no mapa de políticas TLS", "tls_policy_map_parameter_invalid": "O parâmetro de política é inválido", + "to_invalid": "Destinatário não pode ser vazio", "totp_verification_failed": "Falha na verificação do TOTP", "transport_dest_exists": "O destino de transporte “%s” existe", "webauthn_verification_failed": "Falha na verificação do WebAuthn: %s", @@ -481,9 +496,7 @@ "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", - "img_dimensions_exceeded": "A imagem excede o tamanho máximo", - "img_size_exceeded": "A imagem excede o tamanho máximo" + "yotp_verification_failed": "Falha na verificação do Yubico OTP: %s" }, "datatables": { "collapse_all": "Recolher tudo", @@ -582,6 +595,7 @@ "client_secret": "Segredo do cliente", "comment_info": "Um comentário privado não é visível para o usuário, enquanto um comentário público é mostrado como dica de ferramenta ao passar o mouse sobre ele na visão geral do usuário", "created_on": "Criado em", + "custom_attributes": "Atributos personalizados", "delete1": "Excluir da fonte quando concluído", "delete2": "Excluir mensagens no destino que não estão na origem", "delete2duplicates": "Excluir duplicatas no destino", @@ -602,6 +616,7 @@ "custom": "{= foo =} - Se o mailbox tiver o atributo personalizado \"foo\" com valor \"bar\", retornará \"bar\"" }, "domain_footer_plain": "Rodapé simples", + "domain_footer_skip_replies": "Ignore o rodapé nos e-mails de resposta", "domain_quota": "Cota de domínio", "domains": "Domínios", "dont_check_sender_acl": "Desativar a verificação de remetente para o domínio %s (+ domínios de alias)", @@ -637,6 +652,7 @@ "none_inherit": "Nenhum/Herdar", "nexthop": "Próximo salto", "password": "Senha", + "password_recovery_email": "Email de recuperação de senha", "password_repeat": "Senha de confirmação (repetição)", "previous": "Página anterior", "private_comment": "Comentário privado", @@ -690,9 +706,7 @@ "title": "Editar objeto", "unchanged_if_empty": "Se inalterado, deixe em branco", "username": "Nome de usuário", - "validate_save": "Valide e salve", - "custom_attributes": "Atributos personalizados", - "domain_footer_skip_replies": "Ignore o rodapé nos e-mails de resposta" + "validate_save": "Valide e salve" }, "fido2": { "confirm": "Confirme", @@ -742,12 +756,19 @@ "session_expires": "Sua sessão expirará em cerca de 15 segundos" }, "login": { + "back_to_mailcow": "Voltar para o mailcow", "delayed": "O login foi atrasado em %s segundos.", "fido2_webauthn": "Login do FIDO2/WebAuthn", + "forgot_password": "> Esqueceu a senha?", + "invalid_pass_reset_token": "O token de recuperação de senha está inválido ou expirado.
Por favor solicite um novo link de recuperação de senha.", "login": "Login", "mobileconfig_info": "Faça login como usuário da mailbox para baixar o perfil de conexão Apple solicitado.", + "new_password": "Nova senha", + "new_password_confirm": "Confirmar nova senha", "other_logins": "Login com chave", "password": "Senha", + "reset_password": "Recuperar a senha", + "request_reset_password": "Solicitar troca de senha", "username": "Nome de usuário" }, "mailbox": { @@ -1053,6 +1074,7 @@ "domain_removed": "O domínio %s foi removido", "dovecot_restart_success": "O Dovecot foi reiniciado com sucesso", "eas_reset": "Os dispositivos ActiveSync para o usuário %s foram redefinidos", + "f2b_banlist_refreshed": "O Banlist ID foi atualizado com sucesso.", "f2b_modified": "As alterações nos parâmetros do Fail2ban foram salvas", "forwarding_host_added": "O host de encaminhamento %s foi adicionado", "forwarding_host_removed": "O host de encaminhamento %s foi removido", @@ -1072,11 +1094,13 @@ "nginx_reloaded": "O Nginx foi recarregado", "object_modified": "As alterações no objeto %s foram salvas", "password_policy_saved": "A política de senha foi salva com sucesso", + "password_changed_success": "Alteração de senha realizada com sucesso", "pushover_settings_edited": "Configurações de Pushover definidas com sucesso. Verifique as credenciais.", "qlearn_spam": "A ID da mensagem %s foi detectada como spam e excluída", "queue_command_success": "Comando de fila concluído com sucesso", "recipient_map_entry_deleted": "A ID do mapa do destinatário %s foi excluída", "recipient_map_entry_saved": "A entrada “%s” do mapa do destinatário foi salva", + "recovery_email_sent": "Email de recuperação enviado para %s", "relayhost_added": "A entrada de mapa %s foi adicionada", "relayhost_removed": "A entrada de mapa %s foi removida", "reset_main_logo": "Redefinir para o logotipo padrão", @@ -1099,10 +1123,10 @@ "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", - "f2b_banlist_refreshed": "O Banlist ID foi atualizado com sucesso." + "verified_yotp_login": "Login OTP verificado do Yubico" }, "tfa": { + "authenticators": "Autenticadores", "api_register": "%s usa a API Yubico Cloud. Obtenha uma chave de API para sua chave aqui", "confirm": "Confirme", "confirm_totp_token": "Confirme suas alterações inserindo o token gerado", @@ -1127,8 +1151,7 @@ "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", - "authenticators": "Autenticadores" + "yubi_otp": "Autenticação Yubico OTP" }, "user": { "action": "Ação", @@ -1153,6 +1176,7 @@ "apple_connection_profile_complete": "Esse perfil de conexão inclui parâmetros IMAP e SMTP, bem como caminhos CalDAV (calendários) e CardDAV (contatos) para um dispositivo Apple.", "apple_connection_profile_mailonly": "Esse perfil de conexão inclui parâmetros de configuração IMAP e SMTP para um dispositivo Apple.", "apple_connection_profile_with_app_password": "Uma nova senha de aplicativo é gerada e adicionada ao perfil para que nenhuma senha precise ser inserida ao configurar seu dispositivo. Não compartilhe o arquivo, pois ele concede acesso total à sua mailbox.", + "attribute": "Atributo", "change_password": "Alterar senha", "change_password_hint_app_passwords": "Sua conta tem %d senhas de aplicativos que não serão alteradas. Para gerenciá-las, acesse a guia Senhas do aplicativo.", "clear_recent_successful_connections": "Conexões bem-sucedidas e claras", @@ -1210,6 +1234,7 @@ "password": "Senha", "password_now": "Senha atual (confirme as alterações)", "password_repeat": "Senha (repetição)", + "password_reset_info": "Se nenhum email de recuperação for definido, esta função não poderá ser utilizada.", "pushover_evaluate_x_prio": "Escale e-mails de alta prioridade [X-Priority: 1]", "pushover_info": "As configurações de notificação push serão aplicadas a todos os e-mails limpos (sem spam) entregues a %s, incluindo aliases (compartilhados, não compartilhados, marcados).", "pushover_only_x_prio": "Considere somente e-mails de alta prioridade [X-Priority: 1]", @@ -1220,6 +1245,7 @@ "pushover_sound": "Som", "pushover_vars": "Quando nenhum filtro de remetente for definido, todos os e-mails serão considerados.
Os filtros Regex, bem como as verificações exatas do remetente, podem ser definidos individualmente e serão considerados sequencialmente. Eles não dependem um do outro.
Variáveis utilizáveis para texto e título (observe as políticas de proteção de dados)", "pushover_verify": "Verifique as credenciais", + "pw_recovery_email": "Email de recuperação de senha", "q_add_header": "Pasta de lixo eletrônico", "q_all": "Todas as categorias", "q_reject": "Rejeitado", @@ -1283,6 +1309,7 @@ "tls_policy_warning": "Aviso: Se você decidir impor a transferência de e-mail criptografada, poderá perder e-mails.
As mensagens que não satisfizerem a política serão devolvidas com uma falha grave pelo sistema de correio.
Essa opção se aplica ao seu endereço de e-mail principal (nome de login), a todos os endereços derivados de domínios de alias, bem como aos endereços de alias com apenas essa única mailbox como destino.", "user_settings": "Configurações do usuário", "username": "Nome de usuário", + "value": "Valor", "verify": "Verificar", "waiting": "Esperando", "week": "semana", @@ -1290,9 +1317,7 @@ "weeks": "semanas", "with_app_password": "com senha do aplicativo", "year": "ano", - "years": "anos", - "attribute": "Atributo", - "value": "Valor" + "years": "anos" }, "warning": { "cannot_delete_self": "Não é possível excluir o usuário conectado", diff --git a/data/web/lang/lang.ru-ru.json b/data/web/lang/lang.ru-ru.json index 928d80b9e..fb35d213b 100644 --- a/data/web/lang/lang.ru-ru.json +++ b/data/web/lang/lang.ru-ru.json @@ -476,6 +476,10 @@ "debug": { "chart_this_server": "Диаграмма (текущий сервер)", "containers_info": "Статус контейнеров Docker", + "container_running": "Работающий", + "container_disabled": "Контейнер остановлен или отключен", + "container_stopped": "Остановлен", + "current_time": "Системное время", "disk_usage": "Использование дискового пространства", "docs": "Проиндексировано объектов", "external_logs": "Внешние журналы", @@ -496,6 +500,7 @@ "started_on": "Запущен в", "static_logs": "Статические журналы", "success": "Успех", + "no_update_available": "Система обновлена до последней версии", "system_containers": "Система и контейнеры", "uptime": "Время работы", "username": "Имя пользователя" @@ -675,6 +680,7 @@ "apps": "Приложения", "debug": "Состояние сервера", "email": "E-Mail", + "mailcow_system": "Система", "mailcow_config": "Конфигурация", "quarantine": "Карантин", "restart_netfilter": "Перезапустить netfilter", diff --git a/data/web/lang/lang.tr-tr.json b/data/web/lang/lang.tr-tr.json index e7fb623ce..7049046c2 100644 --- a/data/web/lang/lang.tr-tr.json +++ b/data/web/lang/lang.tr-tr.json @@ -28,7 +28,8 @@ "spam_score": "Spam skoru", "syncjobs": "Görevleri senkronize et", "tls_policy": "TLS ilkesi", - "unlimited_quota": "E-postalar için sınırsız kota" + "unlimited_quota": "E-postalar için sınırsız kota", + "pw_reset": "Mailcow kullanıcı şifresini sıfırlamaya izin ver" }, "add": { "activate_filter_warn": "Aktif edilirse diğer tüm filtreler devre dışı bırakılacak.", @@ -350,7 +351,13 @@ "reset_limit": "Hashi kaldır", "routing": "Yönlendirme", "rsetting_add_rule": "Kural ekle", - "rsetting_content": "Kural içeriği" + "rsetting_content": "Kural içeriği", + "password_reset_info": "Herhangi bir kurtarma e-postası sağlanmamışsa, bu işlev kullanılamaz.", + "restore_template": "Varsayılan şablonu geri yüklemek için boş bırakın.", + "password_reset_settings": "Parola Kurtarma Ayarları", + "password_reset_tmpl_html": "HTML Şablonu", + "password_reset_tmpl_text": "Metin Şablonu", + "password_settings": "Şifre Ayarları" }, "warning": { "cannot_delete_self": "Cannot delete logged in user", @@ -661,7 +668,9 @@ "tls_policy_map_entry_exists": "Bir TLS ilke haritası girişi \\\"%s\\\" mevcut", "tls_policy_map_parameter_invalid": "Politika parametresi geçersiz", "totp_verification_failed": "TOTP doğrulaması başarısız oldu", - "transport_dest_exists": "\\\"%s\\\" aktarım hedefi mevcut" + "transport_dest_exists": "\\\"%s\\\" aktarım hedefi mevcut", + "password_reset_invalid_user": "Posta kutusu bulunamadı veya kurtarma e-postası ayarlanmadı", + "invalid_reset_token": "Geçersiz sıfırlama token'ı" }, "debug": { "container_disabled": "Container durduruldu veya devre dışı bırakıldı", diff --git a/data/web/lang/lang.zh-cn.json b/data/web/lang/lang.zh-cn.json index e7b1775e6..368056f5d 100644 --- a/data/web/lang/lang.zh-cn.json +++ b/data/web/lang/lang.zh-cn.json @@ -28,7 +28,8 @@ "spam_score": "垃圾邮件分数", "syncjobs": "同步任务", "tls_policy": "TLS 策略", - "unlimited_quota": "无限邮箱容量配额" + "unlimited_quota": "无限邮箱容量配额", + "pw_reset": "允许重置mailcow使用者密码" }, "add": { "activate_filter_warn": "当“启用”选项被勾选后,其它所有的过滤器都会被禁用。", @@ -62,7 +63,7 @@ "exclude": "拒绝对象 (Regex)", "full_name": "全称", "gal": "全球地址簿", - "gal_info": "全球地址簿包含了域名下的所有对象,并且此行为不能被用户更改。如果关闭,用户的 \"空闲/繁忙\" 的状态将无法在 SOGo 中显示。 重启 SOGo 服务以应用更改。", + "gal_info": "全球地址簿包含了域名下的所有对象,并且此行为不能被用户更改。如果关闭,用户的 \"空闲/繁忙\" 的状态将无法在 SOGo 中显示。 重启 SOGo 服务以应用更改。", "generate": "生成", "goto_ham": "学习为非垃圾邮件", "goto_null": "静默丢弃邮件", @@ -145,7 +146,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 转换为纯文本内容", @@ -291,7 +292,7 @@ "rsettings_preset_2": "允许管理员接收垃圾邮件", "rsettings_preset_3": "只允许指定的发件人发信 (例如只允许内部邮箱发送)", "rsettings_preset_4": "禁用域名的 Rspamd 服务", - "rspamd_com_settings": "设置名称将会自动生成,请看参考下方的示例预设。查看Rspamd 文档以了解更多的细节。", + "rspamd_com_settings": "设置名称将会自动生成,请看参考下方的示例预设。查看Rspamd 文档以了解更多的细节", "rspamd_global_filters": "全局过滤规则", "rspamd_global_filters_agree": "我会小心谨慎的!", "rspamd_global_filters_info": "全局过滤规则包含了不同类型的全局黑名单和白名单。", @@ -338,7 +339,26 @@ "yes": "✓", "options": "选项", "f2b_max_ban_time": "最长封禁时间(秒)", - "copy_to_clipboard": "复制到粘贴板" + "copy_to_clipboard": "复制到粘贴板", + "reset_password_vars": "{{link}} 生成的密码重置链接
{{username}} 要求重置密码的使用者的邮箱名称
{{username2}} 复原邮箱名称
{{date}} 要求重置密码之日期
{{token_lifetime}} 令牌(token)过期时间(以分钟计)
{{hostname}} mailcow 主机名", + "logo_normal_label": "正常", + "f2b_manage_external_info": "Fail2Ban 还会持续维护封禁列表,但不会主动设置规则阻挡流量。请使用以下生成的封禁列表在外部阻止流量。", + "password_reset_info": "若无提供复原电子邮箱,此功能将无法使用。", + "f2b_ban_time_increment": "禁止时间会随着禁止次数增加", + "logo_dark_label": "为深色模式反色", + "cors_settings": "CORS 设定", + "f2b_manage_external": "外部管理Fail2Ban", + "ip_check_opt_in": "使用第三方服务 ipv4.mailcow.emailipv6.mailcow.email 以解析外部IP地址。", + "password_reset_settings": "密码复原设置", + "password_reset_tmpl_html": "HTML 模版", + "password_reset_tmpl_text": "文字模版", + "password_settings": "密码设定", + "restore_template": "留空以恢复预设模版。", + "ip_check": "IP 检查", + "ip_check_disabled": "IP 检查已禁用。你可透过以下路径启用
系统 > 配置 > 选项 > 页面自定义", + "queue_unban": "解除封禁", + "allowed_methods": "访问控制允许方式", + "allowed_origins": "访问控制允许原" }, "danger": { "access_denied": "访问被拒绝或者表单数据无效", @@ -459,7 +479,23 @@ "value_missing": "请填入所有值", "yotp_verification_failed": "Yubico OTP 认证失败: %s", "template_exists": "模板 %s 已存在", - "template_name_invalid": "模板名称无效" + "template_name_invalid": "模板名称无效", + "cors_invalid_method": "制定的允许模式无效", + "cors_invalid_origin": "指定的允许原无效", + "reset_token_limit_exceeded": "Reset token 数量已达上限。请稍后再尝试。", + "extended_sender_acl_denied": "缺少设置外部发送者地址的 ACL", + "img_dimensions_exceeded": "图片超出最大图片大小", + "img_size_exceeded": "图片超过最大文件大小", + "invalid_reset_token": "不合法的 reset token", + "password_reset_invalid_user": "未找到此信箱或未设置恢复邮箱", + "password_reset_na": "密码恢复目前无法使用。请联系您的管理员。", + "recovery_email_failed": "无法发送恢复邮件。请联系您的管理员。", + "template_id_invalid": "Template ID %s 无效", + "to_invalid": "收件人不能为空", + "webauthn_authenticator_failed": "找不到所选的 authenticator", + "webauthn_publickey_failed": "没有为选定的身份验证器保存公钥", + "webauthn_username_failed": "所选的 authenticator 属于另一个账户", + "demo_mode_enabled": "演示模式已开启" }, "debug": { "chart_this_server": "图表 (此服务器)", @@ -494,7 +530,13 @@ "error_show_ip": "无法解析公网IP地址", "show_ip": "显示公网IP", "update_available": "有可用更新", - "update_failed": "无法检查更新" + "update_failed": "无法检查更新", + "architecture": "结构", + "container_stopped": "已停止", + "current_time": "系统时间", + "timezone": "时区", + "no_update_available": "系统已经是最新版本", + "wip": "正在施工中" }, "diagnostics": { "cname_from_a": "来自 A/AAAA 记录的值。但只要记录指向正确的资源即可。", @@ -618,7 +660,31 @@ "title": "编辑对象", "unchanged_if_empty": "如果不更改则留空", "username": "用户名", - "validate_save": "验证并保存" + "validate_save": "验证并保存", + "domain_footer_info_vars": { + "from_name": "{= from_name =} - 信件中的 From name 字段,例如对于 \"Mailcow <moo@mailcow.tld>\" 来说它是 \"Mailcow\"", + "auth_user": "{= auth_user =} - MTA 指定的经过认证的用户名", + "from_user": "{= from_user =} - 信件中的 From user 字段,例如对于 \"moo@mailcow.tld\" 来说它是 \"moo\"", + "from_addr": "{= from_addr =} - 信件中的 From address 字段", + "from_domain": "{= from_domain =} - 信件中的 From domain 字段", + "custom": "{= foo =} - 如果信箱有一个自定义属性 \"foo\" 的值为 \"bar\", 它将返回 \"bar\"" + }, + "created_on": "创建于", + "custom_attributes": "自定义属性", + "mailbox_rename": "重命名信箱", + "mailbox_rename_agree": "我已经创建了一个备份。", + "mailbox_rename_warning": "重要! 重命名信箱之前请先创建备份。", + "mailbox_rename_alias": "自动创建别名", + "mailbox_rename_title": "新本地信箱的名字", + "password_recovery_email": "密码重置邮箱", + "domain_footer": "域页脚", + "domain_footer_html": "HTML footer", + "domain_footer_info": "Domain-wide footers 会被加入到该 domain 下的地址发出的所有邮件中。
下列变量可以在 footer 中使用:", + "domain_footer_plain": "纯文字 footer", + "domain_footer_skip_replies": "在回信中忽略 footer", + "footer_exclude": "从 footer 中排除", + "last_modified": "上次修改时间", + "pushover_sound": "声音" }, "fido2": { "confirm": "确认", @@ -653,13 +719,14 @@ "header": { "administration": "配置和管理", "apps": "应用", - "debug": "系统信息", + "debug": "信息", "email": "E-Mail", "mailcow_config": "配置", "quarantine": "隔离", "restart_netfilter": "重启 netfilter", "restart_sogo": "重启 SOGo", - "user_settings": "用户设置" + "user_settings": "用户设置", + "mailcow_system": "系统" }, "info": { "awaiting_tfa_confirmation": "等待 TFA 确认", @@ -673,7 +740,14 @@ "mobileconfig_info": "请使用邮箱用户登录以下载 Apple 连接描述文件。", "other_logins": "Key 登录", "password": "密码", - "username": "用户名" + "username": "用户名", + "forgot_password": "> 忘记密码?", + "back_to_mailcow": "返回到 mailcow", + "new_password": "新密码", + "new_password_confirm": "确认新密码", + "reset_password": "重置密码", + "request_reset_password": "请求重置密码", + "invalid_pass_reset_token": "密码重置 token 无效或已过期。
请重新获取新的密码重置链接。" }, "mailbox": { "action": "操作", @@ -777,9 +851,9 @@ "recipient_map": "收件人映射", "recipient_map_info": "收件人映射用于在邮件被发送前替换收件人的地址。", "recipient_map_new": "新收件人", - "recipient_map_new_info": "新收件人必须为合法的邮箱地址", + "recipient_map_new_info": "新收件人必须为合法的邮箱地址。", "recipient_map_old": "原收件人", - "recipient_map_old_info": "原收件人必须为合法的邮箱地址", + "recipient_map_old_info": "原收件人必须为合法的邮箱地址。", "recipient_maps": "收件人映射", "relay_all": "中继所有收件人", "remove": "删除", @@ -796,7 +870,7 @@ "sieve_preset_5": "自动回复 (休假)", "sieve_preset_6": "拒绝接收邮件并通知", "sieve_preset_7": "重定向邮件并保留或删除", - "sieve_preset_8": "删除发件人发送给自己别名地址的邮件", + "sieve_preset_8": "重定向来自特定发件人的邮件,标记为已读并放入子文件夹中", "sieve_preset_header": "请看下方的示例预设。 查看 Sieve Wikipedia 页面 (英文)以了解更多细节。", "sogo_visible": "SOGo 别名显示", "sogo_visible_n": "在 SOGo 中隐藏别名", @@ -838,10 +912,20 @@ "mailbox_templates": "邮箱模板", "gal": "全局地址列表", "max_aliases": "最大别名数", - "max_mailboxes": "最大可能的邮箱数" + "max_mailboxes": "最大可能的邮箱数", + "created_on": "建立于", + "force_pw_update": "强制在下一次登陆时更新密码", + "add_template": "新增模板", + "goto_ham": "学习为 非垃圾邮件 ", + "goto_spam": "学习为 垃圾邮件 ", + "last_modified": "上次修改时间", + "max_quota": "每个信箱的最大容量配额", + "relay_unknown": "转发未知信箱", + "templates": "模板", + "template": "模板" }, "oauth2": { - "access_denied": "请作为邮箱所有者登录以使用 OAuth2 授权", + "access_denied": "请作为邮箱所有者登录以使用 OAuth2 授权。", "authorize_app": "授权应用", "deny": "拒绝", "permit": "授权应用", @@ -904,7 +988,19 @@ }, "queue": { "queue_manager": "队列管理器", - "delete": "全部删除" + "delete": "全部删除", + "info": "邮件队列包含所有等待投递的邮件。如果邮件长时间停留在邮件队列中,系统会自动将其删除。
对应的邮件错误信息会提供有关邮件无法送达的原因。", + "flush": "刷新队列", + "legend": "邮件队列操作功能:", + "ays": "请确认您要删除当前队列中的所有项目。", + "deliver_mail": "投递", + "deliver_mail_legend": "尝试重新投递选中的邮件。", + "hold_mail": "保留", + "hold_mail_legend": "保持选中的邮件。(不继续投递)", + "show_message": "显示内容", + "unban": "队列解除限制", + "unhold_mail": "取消保持", + "unhold_mail_legend": "允许选中的邮件继续投递。(需要在保持状态)" }, "ratelimit": { "disabled": "禁用", @@ -972,7 +1068,7 @@ "nginx_reloaded": "Nginx 已重新启动", "object_modified": "已保存对象 %s 更改", "password_policy_saved": "已成功保存密码规则", - "pushover_settings_edited": "已成功设置 Pushover,请重新校验凭证", + "pushover_settings_edited": "Pushover 设置已保存,请重新校验凭证。", "qlearn_spam": "消息 ID %s 已被学习为垃圾邮件并被删除", "queue_command_success": "成功执行配额命令", "recipient_map_entry_deleted": "已删除接收人映射 ID %s", @@ -996,7 +1092,17 @@ "verified_fido2_login": "FIDO2 登录验证成功", "verified_totp_login": "TOTP 登录验证成功", "verified_webauthn_login": "WebAuthn 登录验证成功", - "verified_yotp_login": "Yubico OTP 登录验证成功" + "verified_yotp_login": "Yubico OTP 登录验证成功", + "domain_footer_modified": "对域 footer %s 的修改已保存", + "cors_headers_edited": "CORS 设置已保存", + "ip_check_opt_in_modified": "IP 检查已保存", + "f2b_banlist_refreshed": "黑名单 ID 已成功刷新。", + "mailbox_renamed": "信箱 %s 已被重命名为 %s", + "password_changed_success": "密码重置成功", + "recovery_email_sent": "重置邮件已发送至 %s", + "template_added": "新增了模板 %s", + "template_modified": "模板 %s 的修改已保存", + "template_removed": "模板 ID %s 已删除" }, "tfa": { "api_register": "%s 使用了 Yubico Cloud API,请在此为你的密钥获取 API 密钥", @@ -1023,7 +1129,8 @@ "webauthn": "WebAuthn 认证", "waiting_usb_auth": "等待 USB 设备中...

现在请触碰你的 WebAuthn USB 设备上的按钮。", "waiting_usb_register": "等待 USB 设备中...

请在上方输入你的密码并请触碰你的 WebAuthn USB 设备上的按钮以确认注册该 WebAuthn 设备。", - "yubi_otp": "Yubico OTP 认证" + "yubi_otp": "Yubico OTP 认证", + "authenticators": "验证器(Authenticators)" }, "user": { "action": "操作", @@ -1040,7 +1147,7 @@ "alias_valid_until": "有效至", "aliases_also_send_as": "同时允许发送为", "aliases_send_as_all": "已关闭发件人可访性检查的域名和域名别名", - "app_hint": "应用密码是你登录 IMAP 和 SMTP 时的可选替代密码,用户名仍然保持不变。
应用密码不适用于 SOGo (包括 ActiveSync) ", + "app_hint": "应用密码是你登录 IMAP 和 SMTP 时的可选替代密码,用户名仍然保持不变。
应用密码不适用于 SOGo (包括 ActiveSync)。", "allowed_protocols": "允许使用的协议", "app_name": "应用名称", "app_passwds": "应用密码", @@ -1184,7 +1291,12 @@ "weeks": "周", "with_app_password": "包含应用密码", "year": "年", - "years": "年" + "years": "年", + "pw_recovery_email": "密码重置邮箱", + "password_reset_info": "如果不提供密码重置邮箱,此功能将无法使用。", + "pushover_sound": "声音", + "value": "值", + "attribute": "属性" }, "warning": { "cannot_delete_self": "不能删除已登录的用户", @@ -1211,6 +1323,17 @@ "last": "最后一页", "previous": "上一页", "next": "下一页" - } + }, + "aria": { + "sortDescending": ": 激活以降序对列进行排序", + "sortAscending": ": 激活以升序对列进行排序" + }, + "decimal": ".", + "emptyTable": "表中没有可用的数据", + "infoFiltered": "(从 _MAX_ 个总条目中过滤)", + "thousands": ",", + "lengthMenu": "显示 _MENU_ 条目", + "loadingRecords": "加载中...", + "zeroRecords": "未找到符合条件的记录" } } diff --git a/data/web/lang/lang.zh-tw.json b/data/web/lang/lang.zh-tw.json index bf6ed4f7a..abb75a868 100644 --- a/data/web/lang/lang.zh-tw.json +++ b/data/web/lang/lang.zh-tw.json @@ -28,7 +28,8 @@ "spam_score": "垃圾郵件分數", "syncjobs": "同步任務", "tls_policy": "TLS 規則", - "unlimited_quota": "信箱容量配額無上限" + "unlimited_quota": "信箱容量配額無上限", + "pw_reset": "允許重置mailcow使用者密碼" }, "add": { "activate_filter_warn": "當\"啟用\"被勾選,所有其他過濾器都會被消除。", @@ -351,7 +352,14 @@ "ip_check_opt_in": "選擇使用第三方服務 ipv4.mailcow.emailipv6.mailcow.email 來解析外部 IP 位址。", "ip_check_disabled": "IP 檢查已停用。 您可以在
系統 > 配置 > 選項 > 自訂下啟用它", "options": "選項", - "queue_unban": "解除禁令" + "queue_unban": "解除禁令", + "reset_password_vars": "{{link}} 生成密碼重置連結
{{username}} 請求密碼重置的使用者信箱
{{username2}} 備援電子信箱名稱
{{date}} 密碼重置要求之日期
{{token_lifetime}} 權杖(token)壽命(以分鐘計)
{{hostname}} mailcow 主機名稱(hostname)", + "password_reset_info": "若無提供備援電子郵件,此功能將無法使用。", + "password_reset_settings": "密碼復原設定", + "password_reset_tmpl_html": "HTML 範本", + "password_reset_tmpl_text": "文字範本", + "password_settings": "密碼設定", + "restore_template": "留白以還原預設範本" }, "danger": { "access_denied": "存取拒絕或表單資料有誤", @@ -480,7 +488,14 @@ "extended_sender_acl_denied": "缺少設定外部寄件者地址的 ACL", "template_exists": "模板 %s 已存在", "template_id_invalid": "範本 ID %s 無效", - "template_name_invalid": "模板名稱無效" + "template_name_invalid": "模板名稱無效", + "password_reset_invalid_user": "查無此信箱或無設定備援郵件", + "password_reset_na": "密碼恢復目前無法使用。請聯繫您的管理員。", + "to_invalid": "收件者不能為空", + "img_size_exceeded": "圖片超出最大檔案大小", + "recovery_email_failed": "無法寄送還原電子郵件。請聯絡您的管理員。", + "reset_token_limit_exceeded": "重置權杖(token)已達上限。請稍後再試。", + "img_dimensions_exceeded": "圖片超出最大圖片大小" }, "debug": { "chart_this_server": "圖表 (此伺服器)", @@ -511,7 +526,7 @@ "architecture": "結構", "current_time": "系統時間", "container_running": "正在執行", - "memory": "記憶", + "memory": "記憶體", "container_disabled": "容器停止或停用", "container_stopped": "已停止", "cores": "核心", @@ -650,7 +665,19 @@ "validate_save": "驗證並儲存", "domain_footer_info": "網域範圍的頁尾將會新增至與該網域內的位址關聯的所有外發電子郵件。
以下變數可用於頁尾:", "custom_attributes": "自訂屬性", - "pushover_sound": "聲音" + "pushover_sound": "聲音", + "domain_footer": "全域頁腳", + "domain_footer_info_vars": { + "from_name": "{= from_name =} - 從信封中的名稱部分,例如:\"Mailcow <moo@mailcow.tld>\" 則回傳 \"Mailcow\"", + "auth_user": "{= auth_user =} - MTA所認證之指定使用者名稱", + "from_user": "{= from_user =} - 從信封中的使用者部分。例如:\"moo@mailcow.tld\" 則回傳\"moo\"", + "from_addr": "{= from_addr =} - 從信封中的地址部分", + "from_domain": "{= from_domain =} - 從信封中的域名(domain)部分" + }, + "domain_footer_skip_replies": "於回信中忽落頁腳", + "domain_footer_html": "HTML 頁腳", + "password_recovery_email": "密碼重置備援信箱", + "domain_footer_plain": "純文字頁腳" }, "fido2": { "confirm": "確認", @@ -706,7 +733,14 @@ "mobileconfig_info": "請使用信箱使用者登入以下載 Apple 連接描述檔案。", "other_logins": "金鑰登入", "password": "密碼", - "username": "使用者名稱" + "username": "使用者名稱", + "back_to_mailcow": "返回至mailcow", + "invalid_pass_reset_token": "密碼重置權杖(token)無效或已過期
請重新請求新的重置連結。", + "forgot_password": "> 忘記密碼?", + "new_password": "新密碼", + "new_password_confirm": "確認新密碼", + "reset_password": "重置密碼", + "request_reset_password": "要求重置密碼" }, "mailbox": { "action": "操作", @@ -1042,7 +1076,9 @@ "cors_headers_edited": "CORS 設定已儲存", "domain_footer_modified": "網域頁尾 %s 的變更已儲存", "f2b_banlist_refreshed": "禁止清單 ID 已成功刷新。", - "ip_check_opt_in_modified": "IP檢查已成功儲存" + "ip_check_opt_in_modified": "IP檢查已成功儲存", + "password_changed_success": "密碼重置成功", + "recovery_email_sent": "已寄送復原郵件至 %s" }, "tfa": { "api_register": "%s 使用 Yubico Cloud API,請在這裡為這把金鑰獲取 API 金鑰", @@ -1069,7 +1105,8 @@ "webauthn": "WebAuthn 認證", "waiting_usb_auth": "等待 USB 裝置...

請觸碰 USB 裝置上的按鈕。", "waiting_usb_register": "等待 USB 裝置...

請輸入密碼並觸碰 USB 裝置上的按鈕來確認註冊。", - "yubi_otp": "Yubico OTP 認證" + "yubi_otp": "Yubico OTP 認證", + "authenticators": "驗證器(Authenticators)" }, "user": { "action": "操作", @@ -1233,7 +1270,9 @@ "years": "年", "attribute": "屬性", "pushover_sound": "聲音", - "value": "數值" + "value": "數值", + "password_reset_info": "若無提供密碼備援電子信箱,此功能將無法使用。", + "pw_recovery_email": "密碼備援電子信箱" }, "warning": { "cannot_delete_self": "不能刪除已登入的使用者", diff --git a/data/web/templates/edit/mailbox.twig b/data/web/templates/edit/mailbox.twig index 91b0dfa91..2f93e6e5d 100644 --- a/data/web/templates/edit/mailbox.twig +++ b/data/web/templates/edit/mailbox.twig @@ -9,6 +9,7 @@ +
@@ -308,10 +309,10 @@
-
+
@@ -486,10 +487,10 @@ {% include 'mailbox/rl-frame.twig' %} - +
-
+

{{ lang.edit.mbox_rl_info }}

@@ -498,6 +499,58 @@
+
+
+
+ +
+
+
+
+

{{ lang.edit.mailbox_rename_warning }}

+
+
+
+ +
+
+
+
+
+ + + +
+
+ {{ lang.edit.mailbox_rename_title }} +
+
+
+ + @{{ result.domain }} +
+
+
+
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
{% else %} diff --git a/docker-compose.yml b/docker-compose.yml index 428e68b6f..6b5891131 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -80,7 +80,7 @@ services: - clamd rspamd-mailcow: - image: mailcow/rspamd:1.97 + image: mailcow/rspamd:1.98 stop_grace_period: 30s depends_on: - dovecot-mailcow @@ -112,7 +112,7 @@ services: - rspamd php-fpm-mailcow: - image: mailcow/phpfpm:1.90 + image: mailcow/phpfpm:1.91 command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: - redis-mailcow @@ -192,7 +192,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:nightly-20240905 + image: mailcow/sogo:nightly-20241112 environment: - DBNAME=${DBNAME} - DBUSER=${DBUSER} @@ -226,9 +226,9 @@ services: ofelia.job-exec.sogo_sessions.schedule: "@every 1m" ofelia.job-exec.sogo_sessions.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool -v expire-sessions $${SOGO_EXPIRE_SESSION} || exit 0\"" ofelia.job-exec.sogo_ealarms.schedule: "@every 1m" - ofelia.job-exec.sogo_ealarms.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-ealarms-notify -p /etc/sogo/sieve.creds || exit 0\"" + ofelia.job-exec.sogo_ealarms.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-ealarms-notify -p /etc/sogo/cron.creds || exit 0\"" ofelia.job-exec.sogo_eautoreply.schedule: "@every 5m" - ofelia.job-exec.sogo_eautoreply.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/sieve.creds || exit 0\"" + ofelia.job-exec.sogo_eautoreply.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/cron.creds || exit 0\"" ofelia.job-exec.sogo_backup.schedule: "@every 24h" ofelia.job-exec.sogo_backup.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool backup /sogo_backup ALL || exit 0\"" restart: always @@ -239,7 +239,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:nightly-20240819 + image: mailcow/dovecot:nightly-20241112 depends_on: - mysql-mailcow - netfilter-mailcow @@ -323,7 +323,7 @@ services: - dovecot postfix-mailcow: - image: mailcow/postfix:1.76 + image: mailcow/postfix:1.77 depends_on: mysql-mailcow: condition: service_started @@ -554,7 +554,7 @@ services: - watchdog dockerapi-mailcow: - image: mailcow/dockerapi:2.08 + image: mailcow/dockerapi:2.09 security_opt: - label=disable restart: always diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index 19b4c28c1..2df3ca161 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?.*)$ -NEXTCLOUD_VERSION=28.0.6 +NEXTCLOUD_VERSION=28.0.11 display_warning() { local message=("$@") diff --git a/update.sh b/update.sh index fe8aee72f..cf94eb71c 100755 --- a/update.sh +++ b/update.sh @@ -275,6 +275,34 @@ detect_bad_asn() { fi } +fix_broken_dnslist_conf() { + +# Fixing issue: #6143. To be removed in a later patch + + local file="${SCRIPT_DIR}/data/conf/postfix/dns_blocklists.cf" + # Check if the file exists + if [[ ! -f "$file" ]]; then + return 1 + fi + + # Check if the file contains the autogenerated comment + if grep -q "# Autogenerated by mailcow" "$file"; then + # Ask the user if custom changes were made + echo -e "\e[91mWARNING!!! \e[31mAn old version of dns_blocklists.cnf has been detected which may cause a broken postfix upon startup (see: https://github.com/mailcow/mailcow-dockerized/issues/6143)...\e[0m" + echo -e "\e[31mIf you have any custom settings in there you might copy it away and adapt the changes after the file is regenerated...\e[0m" + read -p "Do you want to delete the file now and let mailcow regenerate it properly? " response + if [[ "${response}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + rm "$file" + echo -e "\e[32mdns_blocklists.cf has been deleted and will be properly regenerated" + return 0 + else + echo -e "\e[35mOk, not deleting it! Please make sure you take a look at postfix upon start then..." + return 2 + fi + fi + +} + ############## End Function Section ############## # Check permissions @@ -437,6 +465,8 @@ source mailcow.conf detect_docker_compose_command +fix_broken_dnslist_conf + DOTS=${MAILCOW_HOSTNAME//[^.]}; if [ ${#DOTS} -lt 1 ]; then echo -e "\e[31mMAILCOW_HOSTNAME (${MAILCOW_HOSTNAME}) is not a FQDN!\e[0m"