You've already forked mailcow-dockerized
							
							
				mirror of
				https://github.com/mailcow/mailcow-dockerized.git
				synced 2025-10-30 23:57:54 +02:00 
			
		
		
		
	| @@ -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: | | ||||
|   | ||||
| @@ -22,7 +22,7 @@ jobs: | ||||
|           bash helper-scripts/update_postscreen_whitelist.sh | ||||
|  | ||||
|     - name: Create Pull Request | ||||
|       uses: peter-evans/create-pull-request@v6 | ||||
|       uses: peter-evans/create-pull-request@v7 | ||||
|       with: | ||||
|         token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }} | ||||
|         commit-message: update postscreen_access.cidr | ||||
|   | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -45,6 +45,7 @@ data/conf/rspamd/override.d/* | ||||
| data/conf/sogo/custom-theme.js | ||||
| data/conf/sogo/plist_ldap | ||||
| data/conf/sogo/sieve.creds | ||||
| data/conf/sogo/cron.creds | ||||
| data/conf/sogo/sogo-full.svg | ||||
| data/gitea/ | ||||
| data/gogs/ | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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"): | ||||
|   | ||||
| @@ -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 | ||||
| @@ -371,6 +371,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 | ||||
| cat <<EOF > /etc/dovecot/sogo-sso.conf | ||||
| # Autogenerated by mailcow | ||||
| passdb { | ||||
| @@ -405,6 +407,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 | ||||
|   | ||||
| @@ -1,17 +1,17 @@ | ||||
| FROM php:8.2-fpm-alpine3.18 | ||||
| FROM php:8.2-fpm-alpine3.20 | ||||
|  | ||||
| LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" | ||||
|  | ||||
| # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$ | ||||
| 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=(?<version>.*)$ | ||||
| ARG IMAGICK_PECL_VERSION=3.7.0 | ||||
| # renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$ | ||||
| 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(?<version>.*)$ | ||||
| ARG MEMCACHED_PECL_VERSION=3.2.0 | ||||
| # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$ | ||||
| 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=(?<version>.*)$ | ||||
| ARG COMPOSER_VERSION=2.6.6 | ||||
|  | ||||
|   | ||||
| @@ -10,16 +10,25 @@ done | ||||
|  | ||||
| # Do not attempt to write to slave | ||||
| if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then | ||||
|   REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT}" | ||||
|   REDIS_HOST=$REDIS_SLAVEOF_IP | ||||
|   REDIS_PORT=$REDIS_SLAVEOF_PORT | ||||
| else | ||||
|   REDIS_CMDLINE="redis-cli -h redis -p 6379" | ||||
|   REDIS_HOST="redis" | ||||
|   REDIS_PORT="6379" | ||||
| fi | ||||
| REDIS_CMDLINE="redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT}" | ||||
|  | ||||
| until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do | ||||
|   echo "Waiting for Redis..." | ||||
|   sleep 2 | ||||
| done | ||||
|  | ||||
| # Set redis session store | ||||
| echo -n ' | ||||
| session.save_handler = redis | ||||
| session.save_path = "tcp://'${REDIS_HOST}':'${REDIS_PORT}'" | ||||
| ' > /usr/local/etc/php/conf.d/session_store.ini | ||||
|  | ||||
| # Check mysql_upgrade (master and slave) | ||||
| CONTAINER_ID= | ||||
| until [[ ! -z "${CONTAINER_ID}" ]] && [[ "${CONTAINER_ID}" =~ ^[[:alnum:]]*$ ]]; do | ||||
|   | ||||
| @@ -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 "$@" | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| FROM debian:bookworm-slim | ||||
| LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" | ||||
| LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>" | ||||
|  | ||||
| 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 | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -10,6 +10,8 @@ if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then | ||||
|   cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf | ||||
| fi | ||||
|  | ||||
| echo "$TZ" > /etc/timezone | ||||
|  | ||||
| # Run hooks | ||||
| for file in /hooks/*; do | ||||
|   if [ -x "${file}" ]; then | ||||
|   | ||||
| @@ -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 # | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| # Whitelist generated by Postwhite v3.4 on Thu Aug  1 00:16:45 UTC 2024 | ||||
| # Whitelist generated by Postwhite v3.4 on Fri Nov  1 00:18:49 UTC 2024 | ||||
| # https://github.com/stevejenkins/postwhite/ | ||||
| # 1954 total rules | ||||
| # 2013 total rules | ||||
| 2a00:1450:4000::/36	permit | ||||
| 2a01:111:f400::/48	permit | ||||
| 2a01:111:f403:8000::/50	permit | ||||
| @@ -19,7 +19,8 @@ | ||||
| 8.20.114.31	permit | ||||
| 8.25.194.0/23	permit | ||||
| 8.25.196.0/23	permit | ||||
| 10.162.0.0/16	permit | ||||
| 8.39.54.0/23	permit | ||||
| 8.40.222.0/23	permit | ||||
| 12.130.86.238	permit | ||||
| 13.110.208.0/21	permit | ||||
| 13.110.209.0/24	permit | ||||
| @@ -30,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 | ||||
| @@ -113,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 | ||||
| @@ -179,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 | ||||
| @@ -200,17 +206,18 @@ | ||||
| 52.96.91.34	permit | ||||
| 52.96.111.82	permit | ||||
| 52.96.172.98	permit | ||||
| 52.96.214.50	permit | ||||
| 52.96.222.194	permit | ||||
| 52.96.222.226	permit | ||||
| 52.96.223.2	permit | ||||
| 52.96.228.130	permit | ||||
| 52.96.229.242	permit | ||||
| 52.100.0.0/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 | ||||
| @@ -222,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 | ||||
| @@ -239,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 | ||||
| @@ -270,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 | ||||
| @@ -324,6 +325,7 @@ | ||||
| 65.110.161.77	permit | ||||
| 65.123.29.213	permit | ||||
| 65.123.29.220	permit | ||||
| 65.154.166.0/24	permit | ||||
| 65.212.180.36	permit | ||||
| 66.102.0.0/20	permit | ||||
| 66.119.150.192/26	permit | ||||
| @@ -1283,6 +1285,9 @@ | ||||
| 117.120.16.0/21	permit | ||||
| 119.42.242.52/31	permit | ||||
| 119.42.242.156	permit | ||||
| 121.244.91.48	permit | ||||
| 121.244.91.52	permit | ||||
| 122.15.156.182	permit | ||||
| 123.126.78.64/29	permit | ||||
| 124.108.96.24/31	permit | ||||
| 124.108.96.28/31	permit | ||||
| @@ -1311,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 | ||||
| @@ -1322,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 | ||||
| @@ -1338,7 +1348,19 @@ | ||||
| 134.170.141.64/26	permit | ||||
| 134.170.143.0/24	permit | ||||
| 134.170.174.0/24	permit | ||||
| 135.84.80.0/24	permit | ||||
| 135.84.81.0/24	permit | ||||
| 135.84.82.0/24	permit | ||||
| 135.84.83.0/24	permit | ||||
| 135.84.216.0/22	permit | ||||
| 136.143.160.0/24	permit | ||||
| 136.143.161.0/24	permit | ||||
| 136.143.162.0/24	permit | ||||
| 136.143.178.49	permit | ||||
| 136.143.182.0/23	permit | ||||
| 136.143.184.0/24	permit | ||||
| 136.143.188.0/24	permit | ||||
| 136.143.190.0/23	permit | ||||
| 136.147.128.0/20	permit | ||||
| 136.147.135.0/24	permit | ||||
| 136.147.176.0/20	permit | ||||
| @@ -1353,7 +1375,9 @@ | ||||
| 139.138.46.219	permit | ||||
| 139.138.57.55	permit | ||||
| 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 | ||||
| @@ -1362,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 | ||||
| @@ -1375,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 | ||||
| @@ -1394,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 | ||||
| @@ -1452,7 +1484,9 @@ | ||||
| 163.114.132.120	permit | ||||
| 163.114.134.16	permit | ||||
| 163.114.135.16	permit | ||||
| 164.152.23.32	permit | ||||
| 164.177.132.168/30	permit | ||||
| 165.173.128.0/24	permit | ||||
| 166.78.68.0/22	permit | ||||
| 166.78.68.221	permit | ||||
| 166.78.69.169	permit | ||||
| @@ -1476,13 +1510,21 @@ | ||||
| 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 | ||||
| 168.245.127.231	permit | ||||
| 169.148.129.0/24	permit | ||||
| 169.148.131.0/24	permit | ||||
| 169.148.142.10	permit | ||||
| 169.148.144.0/25	permit | ||||
| 169.148.144.10	permit | ||||
| 170.10.68.0/22	permit | ||||
| 170.10.128.0/24	permit | ||||
| 170.10.129.0/24	permit | ||||
| 170.10.132.56/29	permit | ||||
| 170.10.132.64/29	permit | ||||
| 170.10.133.0/24	permit | ||||
| 172.217.0.0/19	permit | ||||
| 172.217.32.0/20	permit | ||||
| @@ -1491,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 | ||||
| @@ -1509,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 | ||||
| @@ -1582,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 | ||||
| @@ -1634,13 +1678,22 @@ | ||||
| 199.16.156.0/22	permit | ||||
| 199.33.145.1	permit | ||||
| 199.33.145.32	permit | ||||
| 199.34.22.36	permit | ||||
| 199.59.148.0/22	permit | ||||
| 199.67.80.2	permit | ||||
| 199.67.80.20	permit | ||||
| 199.67.82.2	permit | ||||
| 199.67.82.20	permit | ||||
| 199.67.84.0/24	permit | ||||
| 199.67.86.0/24	permit | ||||
| 199.67.88.0/24	permit | ||||
| 199.101.161.130	permit | ||||
| 199.101.162.0/25	permit | ||||
| 199.122.120.0/21	permit | ||||
| 199.122.123.0/24	permit | ||||
| 199.127.232.0/22	permit | ||||
| 199.255.192.0/22	permit | ||||
| 202.12.124.128/27	permit | ||||
| 202.129.242.0/23	permit | ||||
| 202.165.102.47	permit | ||||
| 202.177.148.100	permit | ||||
| @@ -1691,7 +1744,11 @@ | ||||
| 204.92.114.187	permit | ||||
| 204.92.114.203	permit | ||||
| 204.92.114.204/31	permit | ||||
| 204.220.160.0/20	permit | ||||
| 204.141.32.0/23	permit | ||||
| 204.141.42.0/23	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 | ||||
| @@ -1942,6 +1999,8 @@ | ||||
| 2603:1030:20e:3::23c	permit | ||||
| 2603:1030:b:3::152	permit | ||||
| 2603:1030:c02:8::14	permit | ||||
| 2607:13c0:0001:0000:0000:0000:0000:7000/116	permit | ||||
| 2607:13c0:0002:0000:0000:0000:0000:1000/116	permit | ||||
| 2607:f8b0:4000::/36	permit | ||||
| 2620:109:c003:104::/64	permit | ||||
| 2620:109:c003:104::215	permit | ||||
|   | ||||
| @@ -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, | ||||
| }; | ||||
| @@ -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. | ||||
|   | ||||
| @@ -939,10 +939,10 @@ function check_login($user, $pass, $app_passwd_data = false) { | ||||
|     $stmt->execute(array(':user' => $user)); | ||||
|     $rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC)); | ||||
|   } | ||||
|   foreach ($rows as $row) {  | ||||
|   foreach ($rows as $row) { | ||||
|     // verify password | ||||
|     if (verify_hash($row['password'], $pass) !== false) { | ||||
|       if (!array_key_exists("app_passwd_id", $row)){  | ||||
|       if (!array_key_exists("app_passwd_id", $row)){ | ||||
|         // password is not a app password | ||||
|         // check for tfa authenticators | ||||
|         $authenticators = get_tfa($user); | ||||
| @@ -953,11 +953,6 @@ function check_login($user, $pass, $app_passwd_data = false) { | ||||
|           $_SESSION['pending_mailcow_cc_role'] = "user"; | ||||
|           $_SESSION['pending_tfa_methods'] = $authenticators['additional']; | ||||
|           unset($_SESSION['ldelay']); | ||||
|           $_SESSION['return'][] =  array( | ||||
|             'type' => 'success', | ||||
|             'log' => array(__FUNCTION__, $user, '*'), | ||||
|             'msg' => array('logged_in_as', $user) | ||||
|           ); | ||||
|           return "pending"; | ||||
|         } else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) { | ||||
|           // no authenticators found, login successfull | ||||
| @@ -966,6 +961,11 @@ function check_login($user, $pass, $app_passwd_data = false) { | ||||
|           $stmt->execute(array(':user' => $user)); | ||||
|  | ||||
|           unset($_SESSION['ldelay']); | ||||
|           $_SESSION['return'][] =  array( | ||||
|             'type' => 'success', | ||||
|             'log' => array(__FUNCTION__, $user, '*'), | ||||
|             'msg' => array('logged_in_as', $user) | ||||
|           ); | ||||
|           return "user"; | ||||
|         } | ||||
|       } elseif ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) { | ||||
| @@ -1028,7 +1028,7 @@ function update_sogo_static_view($mailbox = null) { | ||||
|     // Check if the mailbox exists | ||||
|     $stmt = $pdo->prepare("SELECT username FROM mailbox WHERE username = :mailbox AND active = '1'"); | ||||
|     $stmt->execute(array(':mailbox' => $mailbox)); | ||||
|     $row = $stmt->fetch(PDO::FETCH_ASSOC);   | ||||
|     $row = $stmt->fetch(PDO::FETCH_ASSOC); | ||||
|     if ($row){ | ||||
|       $mailbox_exists = true; | ||||
|     } | ||||
| @@ -1056,7 +1056,7 @@ function update_sogo_static_view($mailbox = null) { | ||||
|               LEFT OUTER JOIN grouped_sender_acl_external external_acl ON external_acl.username = mailbox.username | ||||
|             WHERE | ||||
|               mailbox.active = '1'"; | ||||
|    | ||||
|  | ||||
|   if ($mailbox_exists) { | ||||
|     $query .= " AND mailbox.username = :mailbox"; | ||||
|     $stmt = $pdo->prepare($query); | ||||
| @@ -1065,9 +1065,9 @@ function update_sogo_static_view($mailbox = null) { | ||||
|     $query .= " GROUP BY mailbox.username"; | ||||
|     $stmt = $pdo->query($query); | ||||
|   } | ||||
|    | ||||
|  | ||||
|   $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');"); | ||||
|    | ||||
|  | ||||
|   flush_memcached(); | ||||
| } | ||||
| function edit_user_account($_data) { | ||||
| @@ -1100,7 +1100,7 @@ function edit_user_account($_data) { | ||||
|           AND `username` = :user"); | ||||
|     $stmt->execute(array(':user' => $username)); | ||||
|     $row = $stmt->fetch(PDO::FETCH_ASSOC); | ||||
|    | ||||
|  | ||||
|     if (!verify_hash($row['password'], $password_old)) { | ||||
|       $_SESSION['return'][] =  array( | ||||
|         'type' => 'danger', | ||||
| @@ -1109,7 +1109,7 @@ function edit_user_account($_data) { | ||||
|       ); | ||||
|       return false; | ||||
|     } | ||||
|    | ||||
|  | ||||
|     $password_new = $_data['user_new_pass']; | ||||
|     $password_new2  = $_data['user_new_pass2']; | ||||
|     if (password_check($password_new, $password_new2) !== true) { | ||||
| @@ -1124,7 +1124,7 @@ function edit_user_account($_data) { | ||||
|       ':password_hashed' => $password_hashed, | ||||
|       ':username' => $username | ||||
|     )); | ||||
|    | ||||
|  | ||||
|     update_sogo_static_view(); | ||||
|   } | ||||
|   // edit password recovery email | ||||
| @@ -1374,7 +1374,7 @@ function set_tfa($_data) { | ||||
|             $_data['registration']->certificate, | ||||
|             0 | ||||
|         )); | ||||
|      | ||||
|  | ||||
|         $_SESSION['return'][] =  array( | ||||
|             'type' => 'success', | ||||
|             'log' => array(__FUNCTION__, $_data_log), | ||||
| @@ -1544,7 +1544,7 @@ function unset_tfa_key($_data) { | ||||
|  | ||||
|   try { | ||||
|     if (!is_numeric($id)) $access_denied = true; | ||||
|      | ||||
|  | ||||
|     // set access_denied error | ||||
|     if ($access_denied){ | ||||
|       $_SESSION['return'][] = array( | ||||
| @@ -1553,7 +1553,7 @@ function unset_tfa_key($_data) { | ||||
|         'msg' => 'access_denied' | ||||
|       ); | ||||
|       return false; | ||||
|     }  | ||||
|     } | ||||
|  | ||||
|     // check if it's last key | ||||
|     $stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa` | ||||
| @@ -1602,7 +1602,7 @@ function get_tfa($username = null, $id = null) { | ||||
|         WHERE `username` = :username AND `active` = '1'"); | ||||
|     $stmt->execute(array(':username' => $username)); | ||||
|     $results = $stmt->fetchAll(PDO::FETCH_ASSOC); | ||||
|   | ||||
|  | ||||
|     // no tfa methods found | ||||
|     if (count($results) == 0) { | ||||
|         $data['name'] = 'none'; | ||||
| @@ -1810,8 +1810,8 @@ function verify_tfa_login($username, $_data) { | ||||
|                   'msg' => array('webauthn_authenticator_failed') | ||||
|               ); | ||||
|               return false; | ||||
|             }  | ||||
|              | ||||
|             } | ||||
|  | ||||
|             if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) { | ||||
|                 $_SESSION['return'][] =  array( | ||||
|                     'type' => 'danger', | ||||
| @@ -2173,7 +2173,7 @@ function cors($action, $data = null) { | ||||
|           'msg' => 'access_denied' | ||||
|         ); | ||||
|         return false; | ||||
|       }     | ||||
|       } | ||||
|  | ||||
|       $allowed_origins = isset($data['allowed_origins']) ? $data['allowed_origins'] : array($_SERVER['SERVER_NAME']); | ||||
|       $allowed_origins = !is_array($allowed_origins) ? array_filter(array_map('trim', explode("\n", $allowed_origins))) : $allowed_origins; | ||||
| @@ -2206,7 +2206,7 @@ function cors($action, $data = null) { | ||||
|         $redis->hMSet('CORS_SETTINGS', array( | ||||
|           'allowed_origins' => implode(', ', $allowed_origins), | ||||
|           'allowed_methods' => implode(', ', $allowed_methods) | ||||
|         ));    | ||||
|         )); | ||||
|       } catch (RedisException $e) { | ||||
|         $_SESSION['return'][] = array( | ||||
|           'type' => 'danger', | ||||
| @@ -2258,10 +2258,10 @@ function cors($action, $data = null) { | ||||
|       header('Access-Control-Allow-Headers: Accept, Content-Type, X-Api-Key, Origin'); | ||||
|  | ||||
|       // Access-Control settings requested, this is just a preflight request | ||||
|       if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS' &&  | ||||
|       if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && | ||||
|         isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) && | ||||
|         isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) { | ||||
|    | ||||
|  | ||||
|         $allowed_methods = explode(', ', $cors_settings["allowed_methods"]); | ||||
|         if (in_array($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'], $allowed_methods, true)) | ||||
|           // method allowed send 200 OK | ||||
| @@ -2315,7 +2315,7 @@ function reset_password($action, $data = null) { | ||||
|     break; | ||||
|     case 'issue': | ||||
|       $username = $data; | ||||
|        | ||||
|  | ||||
|       // perform cleanup | ||||
|       $stmt = $pdo->prepare("DELETE FROM `reset_password` WHERE created < DATE_SUB(NOW(), INTERVAL :lifetime MINUTE);"); | ||||
|       $stmt->execute(array(':lifetime' => $PW_RESET_TOKEN_LIFETIME)); | ||||
| @@ -2397,8 +2397,8 @@ function reset_password($action, $data = null) { | ||||
|       $request_date = new DateTime(); | ||||
|       $locale_date = locale_get_default(); | ||||
|       $date_formatter = new IntlDateFormatter( | ||||
|         $locale_date,  | ||||
|         IntlDateFormatter::FULL,  | ||||
|         $locale_date, | ||||
|         IntlDateFormatter::FULL, | ||||
|         IntlDateFormatter::FULL | ||||
|       ); | ||||
|       $formatted_request_date = $date_formatter->format($request_date); | ||||
| @@ -2514,7 +2514,7 @@ function reset_password($action, $data = null) { | ||||
|       $stmt->execute(array( | ||||
|         ':username' => $username | ||||
|       )); | ||||
|     | ||||
|  | ||||
|       $_SESSION['return'][] = array( | ||||
|         'type' => 'success', | ||||
|         'log' => array(__FUNCTION__, $action, $_data_log), | ||||
| @@ -2557,7 +2557,7 @@ function reset_password($action, $data = null) { | ||||
|       $text = $data['text']; | ||||
|       $html = $data['html']; | ||||
|       $subject = $data['subject']; | ||||
|      | ||||
|  | ||||
|       if (!filter_var($from, FILTER_VALIDATE_EMAIL)) { | ||||
|         $_SESSION['return'][] =  array( | ||||
|           'type' => 'danger', | ||||
| @@ -2590,7 +2590,7 @@ function reset_password($action, $data = null) { | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|      | ||||
|  | ||||
|       ini_set('max_execution_time', 0); | ||||
|       ini_set('max_input_time', 0); | ||||
|       $mail = new PHPMailer; | ||||
| @@ -2622,7 +2622,7 @@ function reset_password($action, $data = null) { | ||||
|         return false; | ||||
|       } | ||||
|       $mail->ClearAllRecipients(); | ||||
|      | ||||
|  | ||||
|       return true; | ||||
|     break; | ||||
|   } | ||||
|   | ||||
| @@ -1233,7 +1233,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; | ||||
| @@ -1265,14 +1265,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( | ||||
| @@ -1467,7 +1467,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { | ||||
|             ); | ||||
|             return false; | ||||
|           } | ||||
|            | ||||
|  | ||||
|           // check attributes | ||||
|           $attr = array(); | ||||
|           $attr['tags']                       = (isset($_data['tags'])) ? $_data['tags'] : array(); | ||||
| @@ -1557,7 +1557,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']); | ||||
| @@ -2109,7 +2109,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 | ||||
| @@ -2802,11 +2802,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(); | ||||
| @@ -2833,10 +2833,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), | ||||
| @@ -3192,7 +3192,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { | ||||
|                 ':tag_name' => $tag, | ||||
|               )); | ||||
|             } | ||||
|              | ||||
|  | ||||
|             $_SESSION['return'][] = array( | ||||
|               'type' => 'success', | ||||
|               'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), | ||||
| @@ -3203,6 +3203,197 @@ 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_templates': | ||||
|           if ($_SESSION['mailcow_cc_role'] != "admin") { | ||||
|             $_SESSION['return'][] = array( | ||||
| @@ -3235,11 +3426,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; | ||||
| @@ -3259,11 +3450,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']; | ||||
| @@ -3282,10 +3473,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]; | ||||
|               }         | ||||
|               } | ||||
|             } | ||||
|  | ||||
|  | ||||
| @@ -3297,7 +3488,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { | ||||
|               ":id" => $id , | ||||
|               ":template" => $_data["template"] , | ||||
|               ":attributes" => json_encode($attr) | ||||
|             ));  | ||||
|             )); | ||||
|           } | ||||
|  | ||||
|  | ||||
| @@ -3326,7 +3517,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( | ||||
| @@ -3353,15 +3544,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': | ||||
| @@ -3443,7 +3634,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']; | ||||
| @@ -3696,7 +3887,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.'%'); | ||||
|             } | ||||
| @@ -3705,7 +3896,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']; | ||||
|             } | ||||
|           } | ||||
| @@ -4260,7 +4451,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 { | ||||
| @@ -4420,19 +4611,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; | ||||
|           } | ||||
| @@ -4440,11 +4631,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); | ||||
|             } | ||||
| @@ -4546,6 +4737,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { | ||||
|             } | ||||
|             else if ($SaslLogs['service'] == 'pop3') { | ||||
|               $last_pop3_login = strtotime($SaslLogs['datetime']); | ||||
|             } | ||||
| 			else if ($SaslLogs['service'] == 'SSO') { | ||||
|               $last_sso_login = strtotime($SaslLogs['datetime']); | ||||
|             } | ||||
|           } | ||||
|           if (!isset($last_imap_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { | ||||
| @@ -4556,10 +4750,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { | ||||
|           } | ||||
|           if (!isset($last_pop3_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { | ||||
|             $last_pop3_login = 0; | ||||
|           } | ||||
| 		  if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { | ||||
|             $last_sso_login = 0; | ||||
|           } | ||||
|           $mailboxdata['last_imap_login'] = $last_imap_login; | ||||
|           $mailboxdata['last_smtp_login'] = $last_smtp_login; | ||||
|           $mailboxdata['last_pop3_login'] = $last_pop3_login; | ||||
|           $mailboxdata['last_sso_login'] = $last_sso_login; | ||||
|  | ||||
|           if (!isset($_extra) || $_extra != 'reduced') { | ||||
|             $rl = ratelimit('get', 'mailbox', $_data); | ||||
| @@ -4610,19 +4808,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; | ||||
|           } | ||||
| @@ -5064,7 +5262,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { | ||||
|             $ids = $_data['ids']; | ||||
|           } | ||||
|  | ||||
|            | ||||
|  | ||||
|           foreach ($ids as $id) { | ||||
|             // delete template | ||||
|             $stmt = $pdo->prepare("DELETE FROM `templates` | ||||
| @@ -5377,7 +5575,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { | ||||
|               ); | ||||
|               continue; | ||||
|             } | ||||
|              | ||||
|  | ||||
|             update_sogo_static_view($username); | ||||
|             $_SESSION['return'][] = array( | ||||
|               'type' => 'success', | ||||
| @@ -5404,7 +5602,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { | ||||
|             $ids = $_data['ids']; | ||||
|           } | ||||
|  | ||||
|            | ||||
|  | ||||
|           foreach ($ids as $id) { | ||||
|             // delete template | ||||
|             $stmt = $pdo->prepare("DELETE FROM `templates` | ||||
| @@ -5413,7 +5611,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { | ||||
|               ":id" => $id, | ||||
|               ":type" => "mailbox", | ||||
|               ":template" => "Default" | ||||
|             ));  | ||||
|             )); | ||||
|           } | ||||
|  | ||||
|           $_SESSION['return'][] = array( | ||||
| @@ -5487,7 +5685,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']; | ||||
| @@ -5500,7 +5698,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', | ||||
| @@ -5517,7 +5715,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { | ||||
|               ); | ||||
|               return false; | ||||
|             } | ||||
|              | ||||
|  | ||||
|             foreach($tags as $tag){ | ||||
|               // delete tag | ||||
|               $wasModified = true; | ||||
| @@ -5572,7 +5770,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, | ||||
|   | ||||
							
								
								
									
										178
									
								
								data/web/inc/lib/composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										178
									
								
								data/web/inc/lib/composer.lock
									
									
									
										generated
									
									
									
								
							| @@ -1039,6 +1039,73 @@ | ||||
|             }, | ||||
|             "time": "2017-04-19T22:01:50+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/deprecation-contracts", | ||||
|             "version": "v3.5.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/deprecation-contracts.git", | ||||
|                 "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", | ||||
|                 "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
|                 "php": ">=8.1" | ||||
|             }, | ||||
|             "type": "library", | ||||
|             "extra": { | ||||
|                 "branch-alias": { | ||||
|                     "dev-main": "3.5-dev" | ||||
|                 }, | ||||
|                 "thanks": { | ||||
|                     "name": "symfony/contracts", | ||||
|                     "url": "https://github.com/symfony/contracts" | ||||
|                 } | ||||
|             }, | ||||
|             "autoload": { | ||||
|                 "files": [ | ||||
|                     "function.php" | ||||
|                 ] | ||||
|             }, | ||||
|             "notification-url": "https://packagist.org/downloads/", | ||||
|             "license": [ | ||||
|                 "MIT" | ||||
|             ], | ||||
|             "authors": [ | ||||
|                 { | ||||
|                     "name": "Nicolas Grekas", | ||||
|                     "email": "p@tchwork.com" | ||||
|                 }, | ||||
|                 { | ||||
|                     "name": "Symfony Community", | ||||
|                     "homepage": "https://symfony.com/contributors" | ||||
|                 } | ||||
|             ], | ||||
|             "description": "A generic function and convention to trigger deprecation notices", | ||||
|             "homepage": "https://symfony.com", | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
|                     "url": "https://symfony.com/sponsor", | ||||
|                     "type": "custom" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2024-04-18T09:32:20+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-ctype", | ||||
|             "version": "v1.24.0", | ||||
| @@ -1287,6 +1354,82 @@ | ||||
|             ], | ||||
|             "time": "2021-09-13T13:58:33+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.0.5", | ||||
| @@ -1604,34 +1747,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/" | ||||
|                 } | ||||
| @@ -1664,7 +1810,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": [ | ||||
|                 { | ||||
| @@ -1676,7 +1822,7 @@ | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2022-09-28T08:42:51+00:00" | ||||
|             "time": "2024-09-09T17:55:12+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "yubico/u2flib-server", | ||||
| @@ -1728,5 +1874,5 @@ | ||||
|     "prefer-lowest": false, | ||||
|     "platform": [], | ||||
|     "platform-dev": [], | ||||
|     "plugin-api-version": "2.3.0" | ||||
|     "plugin-api-version": "2.6.0" | ||||
| } | ||||
|   | ||||
							
								
								
									
										17
									
								
								data/web/inc/lib/vendor/autoload.php
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								data/web/inc/lib/vendor/autoload.php
									
									
									
									
										vendored
									
									
								
							| @@ -3,8 +3,21 @@ | ||||
| // autoload.php @generated by Composer | ||||
|  | ||||
| if (PHP_VERSION_ID < 50600) { | ||||
|     echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; | ||||
|     exit(1); | ||||
|     if (!headers_sent()) { | ||||
|         header('HTTP/1.1 500 Internal Server Error'); | ||||
|     } | ||||
|     $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; | ||||
|     if (!ini_get('display_errors')) { | ||||
|         if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { | ||||
|             fwrite(STDERR, $err); | ||||
|         } elseif (!headers_sent()) { | ||||
|             echo $err; | ||||
|         } | ||||
|     } | ||||
|     trigger_error( | ||||
|         $err, | ||||
|         E_USER_ERROR | ||||
|     ); | ||||
| } | ||||
|  | ||||
| require_once __DIR__ . '/composer/autoload_real.php'; | ||||
|   | ||||
							
								
								
									
										137
									
								
								data/web/inc/lib/vendor/composer/ClassLoader.php
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										137
									
								
								data/web/inc/lib/vendor/composer/ClassLoader.php
									
									
									
									
										vendored
									
									
								
							| @@ -42,35 +42,37 @@ namespace Composer\Autoload; | ||||
|  */ | ||||
| class ClassLoader | ||||
| { | ||||
|     /** @var ?string */ | ||||
|     /** @var \Closure(string):void */ | ||||
|     private static $includeFile; | ||||
|  | ||||
|     /** @var string|null */ | ||||
|     private $vendorDir; | ||||
|  | ||||
|     // PSR-4 | ||||
|     /** | ||||
|      * @var array[] | ||||
|      * @psalm-var array<string, array<string, int>> | ||||
|      * @var array<string, array<string, int>> | ||||
|      */ | ||||
|     private $prefixLengthsPsr4 = array(); | ||||
|     /** | ||||
|      * @var array[] | ||||
|      * @psalm-var array<string, array<int, string>> | ||||
|      * @var array<string, list<string>> | ||||
|      */ | ||||
|     private $prefixDirsPsr4 = array(); | ||||
|     /** | ||||
|      * @var array[] | ||||
|      * @psalm-var array<string, string> | ||||
|      * @var list<string> | ||||
|      */ | ||||
|     private $fallbackDirsPsr4 = array(); | ||||
|  | ||||
|     // PSR-0 | ||||
|     /** | ||||
|      * @var array[] | ||||
|      * @psalm-var array<string, array<string, string[]>> | ||||
|      * List of PSR-0 prefixes | ||||
|      * | ||||
|      * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) | ||||
|      * | ||||
|      * @var array<string, array<string, list<string>>> | ||||
|      */ | ||||
|     private $prefixesPsr0 = array(); | ||||
|     /** | ||||
|      * @var array[] | ||||
|      * @psalm-var array<string, string> | ||||
|      * @var list<string> | ||||
|      */ | ||||
|     private $fallbackDirsPsr0 = array(); | ||||
|  | ||||
| @@ -78,8 +80,7 @@ class ClassLoader | ||||
|     private $useIncludePath = false; | ||||
|  | ||||
|     /** | ||||
|      * @var string[] | ||||
|      * @psalm-var array<string, string> | ||||
|      * @var array<string, string> | ||||
|      */ | ||||
|     private $classMap = array(); | ||||
|  | ||||
| @@ -87,29 +88,29 @@ class ClassLoader | ||||
|     private $classMapAuthoritative = false; | ||||
|  | ||||
|     /** | ||||
|      * @var bool[] | ||||
|      * @psalm-var array<string, bool> | ||||
|      * @var array<string, bool> | ||||
|      */ | ||||
|     private $missingClasses = array(); | ||||
|  | ||||
|     /** @var ?string */ | ||||
|     /** @var string|null */ | ||||
|     private $apcuPrefix; | ||||
|  | ||||
|     /** | ||||
|      * @var self[] | ||||
|      * @var array<string, self> | ||||
|      */ | ||||
|     private static $registeredLoaders = array(); | ||||
|  | ||||
|     /** | ||||
|      * @param ?string $vendorDir | ||||
|      * @param string|null $vendorDir | ||||
|      */ | ||||
|     public function __construct($vendorDir = null) | ||||
|     { | ||||
|         $this->vendorDir = $vendorDir; | ||||
|         self::initializeIncludeClosure(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return string[] | ||||
|      * @return array<string, list<string>> | ||||
|      */ | ||||
|     public function getPrefixes() | ||||
|     { | ||||
| @@ -121,8 +122,7 @@ class ClassLoader | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return array[] | ||||
|      * @psalm-return array<string, array<int, string>> | ||||
|      * @return array<string, list<string>> | ||||
|      */ | ||||
|     public function getPrefixesPsr4() | ||||
|     { | ||||
| @@ -130,8 +130,7 @@ class ClassLoader | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return array[] | ||||
|      * @psalm-return array<string, string> | ||||
|      * @return list<string> | ||||
|      */ | ||||
|     public function getFallbackDirs() | ||||
|     { | ||||
| @@ -139,8 +138,7 @@ class ClassLoader | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return array[] | ||||
|      * @psalm-return array<string, string> | ||||
|      * @return list<string> | ||||
|      */ | ||||
|     public function getFallbackDirsPsr4() | ||||
|     { | ||||
| @@ -148,8 +146,7 @@ class ClassLoader | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return string[] Array of classname => path | ||||
|      * @psalm-return array<string, string> | ||||
|      * @return array<string, string> Array of classname => path | ||||
|      */ | ||||
|     public function getClassMap() | ||||
|     { | ||||
| @@ -157,8 +154,7 @@ class ClassLoader | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param string[] $classMap Class to filename map | ||||
|      * @psalm-param array<string, string> $classMap | ||||
|      * @param array<string, string> $classMap Class to filename map | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
| @@ -175,24 +171,25 @@ class ClassLoader | ||||
|      * Registers a set of PSR-0 directories for a given prefix, either | ||||
|      * appending or prepending to the ones previously set for this prefix. | ||||
|      * | ||||
|      * @param string          $prefix  The prefix | ||||
|      * @param string[]|string $paths   The PSR-0 root directories | ||||
|      * @param bool            $prepend Whether to prepend the directories | ||||
|      * @param string              $prefix  The prefix | ||||
|      * @param list<string>|string $paths   The PSR-0 root directories | ||||
|      * @param bool                $prepend Whether to prepend the directories | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function add($prefix, $paths, $prepend = false) | ||||
|     { | ||||
|         $paths = (array) $paths; | ||||
|         if (!$prefix) { | ||||
|             if ($prepend) { | ||||
|                 $this->fallbackDirsPsr0 = array_merge( | ||||
|                     (array) $paths, | ||||
|                     $paths, | ||||
|                     $this->fallbackDirsPsr0 | ||||
|                 ); | ||||
|             } else { | ||||
|                 $this->fallbackDirsPsr0 = array_merge( | ||||
|                     $this->fallbackDirsPsr0, | ||||
|                     (array) $paths | ||||
|                     $paths | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
| @@ -201,19 +198,19 @@ class ClassLoader | ||||
|  | ||||
|         $first = $prefix[0]; | ||||
|         if (!isset($this->prefixesPsr0[$first][$prefix])) { | ||||
|             $this->prefixesPsr0[$first][$prefix] = (array) $paths; | ||||
|             $this->prefixesPsr0[$first][$prefix] = $paths; | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|         if ($prepend) { | ||||
|             $this->prefixesPsr0[$first][$prefix] = array_merge( | ||||
|                 (array) $paths, | ||||
|                 $paths, | ||||
|                 $this->prefixesPsr0[$first][$prefix] | ||||
|             ); | ||||
|         } else { | ||||
|             $this->prefixesPsr0[$first][$prefix] = array_merge( | ||||
|                 $this->prefixesPsr0[$first][$prefix], | ||||
|                 (array) $paths | ||||
|                 $paths | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| @@ -222,9 +219,9 @@ class ClassLoader | ||||
|      * Registers a set of PSR-4 directories for a given namespace, either | ||||
|      * appending or prepending to the ones previously set for this namespace. | ||||
|      * | ||||
|      * @param string          $prefix  The prefix/namespace, with trailing '\\' | ||||
|      * @param string[]|string $paths   The PSR-4 base directories | ||||
|      * @param bool            $prepend Whether to prepend the directories | ||||
|      * @param string              $prefix  The prefix/namespace, with trailing '\\' | ||||
|      * @param list<string>|string $paths   The PSR-4 base directories | ||||
|      * @param bool                $prepend Whether to prepend the directories | ||||
|      * | ||||
|      * @throws \InvalidArgumentException | ||||
|      * | ||||
| @@ -232,17 +229,18 @@ class ClassLoader | ||||
|      */ | ||||
|     public function addPsr4($prefix, $paths, $prepend = false) | ||||
|     { | ||||
|         $paths = (array) $paths; | ||||
|         if (!$prefix) { | ||||
|             // Register directories for the root namespace. | ||||
|             if ($prepend) { | ||||
|                 $this->fallbackDirsPsr4 = array_merge( | ||||
|                     (array) $paths, | ||||
|                     $paths, | ||||
|                     $this->fallbackDirsPsr4 | ||||
|                 ); | ||||
|             } else { | ||||
|                 $this->fallbackDirsPsr4 = array_merge( | ||||
|                     $this->fallbackDirsPsr4, | ||||
|                     (array) $paths | ||||
|                     $paths | ||||
|                 ); | ||||
|             } | ||||
|         } elseif (!isset($this->prefixDirsPsr4[$prefix])) { | ||||
| @@ -252,18 +250,18 @@ class ClassLoader | ||||
|                 throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); | ||||
|             } | ||||
|             $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; | ||||
|             $this->prefixDirsPsr4[$prefix] = (array) $paths; | ||||
|             $this->prefixDirsPsr4[$prefix] = $paths; | ||||
|         } elseif ($prepend) { | ||||
|             // Prepend directories for an already registered namespace. | ||||
|             $this->prefixDirsPsr4[$prefix] = array_merge( | ||||
|                 (array) $paths, | ||||
|                 $paths, | ||||
|                 $this->prefixDirsPsr4[$prefix] | ||||
|             ); | ||||
|         } else { | ||||
|             // Append directories for an already registered namespace. | ||||
|             $this->prefixDirsPsr4[$prefix] = array_merge( | ||||
|                 $this->prefixDirsPsr4[$prefix], | ||||
|                 (array) $paths | ||||
|                 $paths | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| @@ -272,8 +270,8 @@ class ClassLoader | ||||
|      * Registers a set of PSR-0 directories for a given prefix, | ||||
|      * replacing any others previously set for this prefix. | ||||
|      * | ||||
|      * @param string          $prefix The prefix | ||||
|      * @param string[]|string $paths  The PSR-0 base directories | ||||
|      * @param string              $prefix The prefix | ||||
|      * @param list<string>|string $paths  The PSR-0 base directories | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
| @@ -290,8 +288,8 @@ class ClassLoader | ||||
|      * Registers a set of PSR-4 directories for a given namespace, | ||||
|      * replacing any others previously set for this namespace. | ||||
|      * | ||||
|      * @param string          $prefix The prefix/namespace, with trailing '\\' | ||||
|      * @param string[]|string $paths  The PSR-4 base directories | ||||
|      * @param string              $prefix The prefix/namespace, with trailing '\\' | ||||
|      * @param list<string>|string $paths  The PSR-4 base directories | ||||
|      * | ||||
|      * @throws \InvalidArgumentException | ||||
|      * | ||||
| @@ -425,7 +423,8 @@ class ClassLoader | ||||
|     public function loadClass($class) | ||||
|     { | ||||
|         if ($file = $this->findFile($class)) { | ||||
|             includeFile($file); | ||||
|             $includeFile = self::$includeFile; | ||||
|             $includeFile($file); | ||||
|  | ||||
|             return true; | ||||
|         } | ||||
| @@ -476,9 +475,9 @@ class ClassLoader | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the currently registered loaders indexed by their corresponding vendor directories. | ||||
|      * Returns the currently registered loaders keyed by their corresponding vendor directories. | ||||
|      * | ||||
|      * @return self[] | ||||
|      * @return array<string, self> | ||||
|      */ | ||||
|     public static function getRegisteredLoaders() | ||||
|     { | ||||
| @@ -555,18 +554,26 @@ class ClassLoader | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Scope isolated include. | ||||
|  * | ||||
|  * Prevents access to $this/self from included files. | ||||
|  * | ||||
|  * @param  string $file | ||||
|  * @return void | ||||
|  * @private | ||||
|  */ | ||||
| function includeFile($file) | ||||
| { | ||||
|     include $file; | ||||
|     /** | ||||
|      * @return void | ||||
|      */ | ||||
|     private static function initializeIncludeClosure() | ||||
|     { | ||||
|         if (self::$includeFile !== null) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Scope isolated include. | ||||
|          * | ||||
|          * Prevents access to $this/self from included files. | ||||
|          * | ||||
|          * @param  string $file | ||||
|          * @return void | ||||
|          */ | ||||
|         self::$includeFile = \Closure::bind(static function($file) { | ||||
|             include $file; | ||||
|         }, null, null); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -98,7 +98,7 @@ class InstalledVersions | ||||
|     { | ||||
|         foreach (self::getInstalled() as $installed) { | ||||
|             if (isset($installed['versions'][$packageName])) { | ||||
|                 return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); | ||||
|                 return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -119,7 +119,7 @@ class InstalledVersions | ||||
|      */ | ||||
|     public static function satisfies(VersionParser $parser, $packageName, $constraint) | ||||
|     { | ||||
|         $constraint = $parser->parseConstraints($constraint); | ||||
|         $constraint = $parser->parseConstraints((string) $constraint); | ||||
|         $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); | ||||
|  | ||||
|         return $provided->matches($constraint); | ||||
| @@ -328,7 +328,9 @@ class InstalledVersions | ||||
|                 if (isset(self::$installedByVendor[$vendorDir])) { | ||||
|                     $installed[] = self::$installedByVendor[$vendorDir]; | ||||
|                 } elseif (is_file($vendorDir.'/composer/installed.php')) { | ||||
|                     $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; | ||||
|                     /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ | ||||
|                     $required = require $vendorDir.'/composer/installed.php'; | ||||
|                     $installed[] = self::$installedByVendor[$vendorDir] = $required; | ||||
|                     if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { | ||||
|                         self::$installed = $installed[count($installed) - 1]; | ||||
|                     } | ||||
| @@ -340,12 +342,17 @@ class InstalledVersions | ||||
|             // only require the installed.php file if this file is loaded from its dumped location, | ||||
|             // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 | ||||
|             if (substr(__DIR__, -8, 1) !== 'C') { | ||||
|                 self::$installed = require __DIR__ . '/installed.php'; | ||||
|                 /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ | ||||
|                 $required = require __DIR__ . '/installed.php'; | ||||
|                 self::$installed = $required; | ||||
|             } else { | ||||
|                 self::$installed = array(); | ||||
|             } | ||||
|         } | ||||
|         $installed[] = self::$installed; | ||||
|  | ||||
|         if (self::$installed !== array()) { | ||||
|             $installed[] = self::$installed; | ||||
|         } | ||||
|  | ||||
|         return $installed; | ||||
|     } | ||||
|   | ||||
| @@ -7,7 +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', | ||||
|     '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', | ||||
|   | ||||
| @@ -10,8 +10,14 @@ return array( | ||||
|     'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', | ||||
|     'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php', | ||||
|     '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', | ||||
|     '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', | ||||
|     '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', | ||||
| ); | ||||
|   | ||||
| @@ -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'), | ||||
|   | ||||
| @@ -33,25 +33,18 @@ class ComposerAutoloaderInit873464e4bd965a3168f133248b1b218b | ||||
|  | ||||
|         $loader->register(true); | ||||
|  | ||||
|         $includeFiles = \Composer\Autoload\ComposerStaticInit873464e4bd965a3168f133248b1b218b::$files; | ||||
|         foreach ($includeFiles as $fileIdentifier => $file) { | ||||
|             composerRequire873464e4bd965a3168f133248b1b218b($fileIdentifier, $file); | ||||
|         $filesToLoad = \Composer\Autoload\ComposerStaticInit873464e4bd965a3168f133248b1b218b::$files; | ||||
|         $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { | ||||
|             if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { | ||||
|                 $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; | ||||
|  | ||||
|                 require $file; | ||||
|             } | ||||
|         }, null, null); | ||||
|         foreach ($filesToLoad as $fileIdentifier => $file) { | ||||
|             $requireFile($fileIdentifier, $file); | ||||
|         } | ||||
|  | ||||
|         return $loader; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param string $fileIdentifier | ||||
|  * @param string $file | ||||
|  * @return void | ||||
|  */ | ||||
| function composerRequire873464e4bd965a3168f133248b1b218b($fileIdentifier, $file) | ||||
| { | ||||
|     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { | ||||
|         $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; | ||||
|  | ||||
|         require $file; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -11,10 +11,16 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b | ||||
|         'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', | ||||
|         'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php', | ||||
|         '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', | ||||
|         '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', | ||||
|         '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 ( | ||||
| @@ -25,6 +31,7 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b | ||||
|         ), | ||||
|         'S' =>  | ||||
|         array ( | ||||
|             'Symfony\\Polyfill\\Php81\\' => 23, | ||||
|             'Symfony\\Polyfill\\Php80\\' => 23, | ||||
|             'Symfony\\Polyfill\\Mbstring\\' => 26, | ||||
|             'Symfony\\Polyfill\\Ctype\\' => 23, | ||||
| @@ -80,6 +87,10 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b | ||||
|         array ( | ||||
|             0 => __DIR__ . '/..' . '/tightenco/collect/src/Collect', | ||||
|         ), | ||||
|         'Symfony\\Polyfill\\Php81\\' =>  | ||||
|         array ( | ||||
|             0 => __DIR__ . '/..' . '/symfony/polyfill-php81', | ||||
|         ), | ||||
|         'Symfony\\Polyfill\\Php80\\' =>  | ||||
|         array ( | ||||
|             0 => __DIR__ . '/..' . '/symfony/polyfill-php80', | ||||
| @@ -170,7 +181,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', | ||||
|         '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', | ||||
|   | ||||
							
								
								
									
										184
									
								
								data/web/inc/lib/vendor/composer/installed.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										184
									
								
								data/web/inc/lib/vendor/composer/installed.json
									
									
									
									
										vendored
									
									
								
							| @@ -1068,6 +1068,76 @@ | ||||
|             ], | ||||
|             "install-path": "../soundasleep/html2text" | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/deprecation-contracts", | ||||
|             "version": "v3.5.0", | ||||
|             "version_normalized": "3.5.0.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/deprecation-contracts.git", | ||||
|                 "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", | ||||
|                 "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
|                 "php": ">=8.1" | ||||
|             }, | ||||
|             "time": "2024-04-18T09:32:20+00:00", | ||||
|             "type": "library", | ||||
|             "extra": { | ||||
|                 "branch-alias": { | ||||
|                     "dev-main": "3.5-dev" | ||||
|                 }, | ||||
|                 "thanks": { | ||||
|                     "name": "symfony/contracts", | ||||
|                     "url": "https://github.com/symfony/contracts" | ||||
|                 } | ||||
|             }, | ||||
|             "installation-source": "dist", | ||||
|             "autoload": { | ||||
|                 "files": [ | ||||
|                     "function.php" | ||||
|                 ] | ||||
|             }, | ||||
|             "notification-url": "https://packagist.org/downloads/", | ||||
|             "license": [ | ||||
|                 "MIT" | ||||
|             ], | ||||
|             "authors": [ | ||||
|                 { | ||||
|                     "name": "Nicolas Grekas", | ||||
|                     "email": "p@tchwork.com" | ||||
|                 }, | ||||
|                 { | ||||
|                     "name": "Symfony Community", | ||||
|                     "homepage": "https://symfony.com/contributors" | ||||
|                 } | ||||
|             ], | ||||
|             "description": "A generic function and convention to trigger deprecation notices", | ||||
|             "homepage": "https://symfony.com", | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
|                     "url": "https://symfony.com/sponsor", | ||||
|                     "type": "custom" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "install-path": "../symfony/deprecation-contracts" | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-ctype", | ||||
|             "version": "v1.24.0", | ||||
| @@ -1325,6 +1395,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.0.5", | ||||
| @@ -1654,37 +1803,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/" | ||||
|                 } | ||||
| @@ -1717,7 +1869,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": [ | ||||
|                 { | ||||
|   | ||||
							
								
								
									
										28
									
								
								data/web/inc/lib/vendor/composer/installed.php
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								data/web/inc/lib/vendor/composer/installed.php
									
									
									
									
										vendored
									
									
								
							| @@ -3,7 +3,7 @@ | ||||
|         'name' => '__root__', | ||||
|         'pretty_version' => 'dev-master', | ||||
|         'version' => 'dev-master', | ||||
|         'reference' => '8e0b1d8aee4af02311692cb031695cc2ac3850fd', | ||||
|         'reference' => '220fdbb168792c07493db330d898b345cc902055', | ||||
|         'type' => 'library', | ||||
|         'install_path' => __DIR__ . '/../../', | ||||
|         'aliases' => array(), | ||||
| @@ -13,7 +13,7 @@ | ||||
|         '__root__' => array( | ||||
|             'pretty_version' => 'dev-master', | ||||
|             'version' => 'dev-master', | ||||
|             'reference' => '8e0b1d8aee4af02311692cb031695cc2ac3850fd', | ||||
|             'reference' => '220fdbb168792c07493db330d898b345cc902055', | ||||
|             'type' => 'library', | ||||
|             'install_path' => __DIR__ . '/../../', | ||||
|             'aliases' => array(), | ||||
| @@ -175,6 +175,15 @@ | ||||
|             'aliases' => array(), | ||||
|             'dev_requirement' => false, | ||||
|         ), | ||||
|         'symfony/deprecation-contracts' => array( | ||||
|             'pretty_version' => 'v3.5.0', | ||||
|             'version' => '3.5.0.0', | ||||
|             'reference' => '0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1', | ||||
|             'type' => 'library', | ||||
|             'install_path' => __DIR__ . '/../symfony/deprecation-contracts', | ||||
|             'aliases' => array(), | ||||
|             'dev_requirement' => false, | ||||
|         ), | ||||
|         'symfony/polyfill-ctype' => array( | ||||
|             'pretty_version' => 'v1.24.0', | ||||
|             'version' => '1.24.0.0', | ||||
| @@ -202,6 +211,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.0.5', | ||||
|             'version' => '6.0.5.0', | ||||
| @@ -245,9 +263,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(), | ||||
|   | ||||
| @@ -4,8 +4,8 @@ | ||||
|  | ||||
| $issues = array(); | ||||
|  | ||||
| if (!(PHP_VERSION_ID >= 80002)) { | ||||
|     $issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.2". You are running ' . PHP_VERSION . '.'; | ||||
| if (!(PHP_VERSION_ID >= 80100)) { | ||||
|     $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; | ||||
| } | ||||
|  | ||||
| if ($issues) { | ||||
|   | ||||
							
								
								
									
										5
									
								
								data/web/inc/lib/vendor/symfony/deprecation-contracts/CHANGELOG.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								data/web/inc/lib/vendor/symfony/deprecation-contracts/CHANGELOG.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| CHANGELOG | ||||
| ========= | ||||
|  | ||||
| The changelog is maintained for all Symfony contracts at the following URL: | ||||
| https://github.com/symfony/contracts/blob/main/CHANGELOG.md | ||||
							
								
								
									
										19
									
								
								data/web/inc/lib/vendor/symfony/deprecation-contracts/LICENSE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								data/web/inc/lib/vendor/symfony/deprecation-contracts/LICENSE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| Copyright (c) 2020-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. | ||||
							
								
								
									
										26
									
								
								data/web/inc/lib/vendor/symfony/deprecation-contracts/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								data/web/inc/lib/vendor/symfony/deprecation-contracts/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| Symfony Deprecation Contracts | ||||
| ============================= | ||||
|  | ||||
| A generic function and convention to trigger deprecation notices. | ||||
|  | ||||
| This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. | ||||
|  | ||||
| By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, | ||||
| the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. | ||||
|  | ||||
| The function requires at least 3 arguments: | ||||
|  - the name of the Composer package that is triggering the deprecation | ||||
|  - the version of the package that introduced the deprecation | ||||
|  - the message of the deprecation | ||||
|  - more arguments can be provided: they will be inserted in the message using `printf()` formatting | ||||
|  | ||||
| Example: | ||||
| ```php | ||||
| trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); | ||||
| ``` | ||||
|  | ||||
| This will generate the following message: | ||||
| `Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` | ||||
|  | ||||
| While not recommended, the deprecation notices can be completely ignored by declaring an empty | ||||
| `function trigger_deprecation() {}` in your application. | ||||
							
								
								
									
										35
									
								
								data/web/inc/lib/vendor/symfony/deprecation-contracts/composer.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								data/web/inc/lib/vendor/symfony/deprecation-contracts/composer.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| { | ||||
|     "name": "symfony/deprecation-contracts", | ||||
|     "type": "library", | ||||
|     "description": "A generic function and convention to trigger deprecation notices", | ||||
|     "homepage": "https://symfony.com", | ||||
|     "license": "MIT", | ||||
|     "authors": [ | ||||
|         { | ||||
|             "name": "Nicolas Grekas", | ||||
|             "email": "p@tchwork.com" | ||||
|         }, | ||||
|         { | ||||
|             "name": "Symfony Community", | ||||
|             "homepage": "https://symfony.com/contributors" | ||||
|         } | ||||
|     ], | ||||
|     "require": { | ||||
|         "php": ">=8.1" | ||||
|     }, | ||||
|     "autoload": { | ||||
|         "files": [ | ||||
|             "function.php" | ||||
|         ] | ||||
|     }, | ||||
|     "minimum-stability": "dev", | ||||
|     "extra": { | ||||
|         "branch-alias": { | ||||
|             "dev-main": "3.5-dev" | ||||
|         }, | ||||
|         "thanks": { | ||||
|             "name": "symfony/contracts", | ||||
|             "url": "https://github.com/symfony/contracts" | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										27
									
								
								data/web/inc/lib/vendor/symfony/deprecation-contracts/function.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								data/web/inc/lib/vendor/symfony/deprecation-contracts/function.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| if (!function_exists('trigger_deprecation')) { | ||||
|     /** | ||||
|      * Triggers a silenced deprecation notice. | ||||
|      * | ||||
|      * @param string $package The name of the Composer package that is triggering the deprecation | ||||
|      * @param string $version The version of the package that introduced the deprecation | ||||
|      * @param string $message The message of the deprecation | ||||
|      * @param mixed  ...$args Values to insert in the message using printf() formatting | ||||
|      * | ||||
|      * @author Nicolas Grekas <p@tchwork.com> | ||||
|      */ | ||||
|     function trigger_deprecation(string $package, string $version, string $message, mixed ...$args): void | ||||
|     { | ||||
|         @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								data/web/inc/lib/vendor/symfony/polyfill-php81/LICENSE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								data/web/inc/lib/vendor/symfony/polyfill-php81/LICENSE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										37
									
								
								data/web/inc/lib/vendor/symfony/polyfill-php81/Php81.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								data/web/inc/lib/vendor/symfony/polyfill-php81/Php81.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * 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 <p@tchwork.com> | ||||
|  * | ||||
|  * @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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										18
									
								
								data/web/inc/lib/vendor/symfony/polyfill-php81/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								data/web/inc/lib/vendor/symfony/polyfill-php81/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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). | ||||
							
								
								
									
										51
									
								
								data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * 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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										20
									
								
								data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * 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() | ||||
|         { | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										28
									
								
								data/web/inc/lib/vendor/symfony/polyfill-php81/bootstrap.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								data/web/inc/lib/vendor/symfony/polyfill-php81/bootstrap.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of the Symfony package. | ||||
|  * | ||||
|  * (c) Fabien Potencier <fabien@symfony.com> | ||||
|  * | ||||
|  * 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; } | ||||
| } | ||||
							
								
								
									
										33
									
								
								data/web/inc/lib/vendor/symfony/polyfill-php81/composer.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								data/web/inc/lib/vendor/symfony/polyfill-php81/composer.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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" | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										18
									
								
								data/web/inc/lib/vendor/twig/twig/.editorconfig
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								data/web/inc/lib/vendor/twig/twig/.editorconfig
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
| @@ -1,4 +0,0 @@ | ||||
| /doc/ export-ignore | ||||
| /extra/ export-ignore | ||||
| /tests/ export-ignore | ||||
| /phpunit.xml.dist export-ignore | ||||
| @@ -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" | ||||
| @@ -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/' | ||||
							
								
								
									
										6
									
								
								data/web/inc/lib/vendor/twig/twig/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								data/web/inc/lib/vendor/twig/twig/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +0,0 @@ | ||||
| /doc/_build/vendor | ||||
| /doc/_build/output | ||||
| /composer.lock | ||||
| /phpunit.xml | ||||
| /vendor | ||||
| .phpunit.result.cache | ||||
| @@ -1,20 +0,0 @@ | ||||
| <?php | ||||
|  | ||||
| return (new PhpCsFixer\Config()) | ||||
|     ->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__)) | ||||
| ; | ||||
							
								
								
									
										177
									
								
								data/web/inc/lib/vendor/twig/twig/CHANGELOG
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										177
									
								
								data/web/inc/lib/vendor/twig/twig/CHANGELOG
									
									
									
									
										vendored
									
									
								
							| @@ -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() | ||||
|   | ||||
							
								
								
									
										2
									
								
								data/web/inc/lib/vendor/twig/twig/LICENSE
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								data/web/inc/lib/vendor/twig/twig/LICENSE
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| Copyright (c) 2009-2022 by the Twig Team. | ||||
| Copyright (c) 2009-present by the Twig Team. | ||||
|  | ||||
| All rights reserved. | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								data/web/inc/lib/vendor/twig/twig/README.rst
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								data/web/inc/lib/vendor/twig/twig/README.rst
									
									
									
									
										vendored
									
									
								
							| @@ -11,7 +11,7 @@ Sponsors | ||||
|  | ||||
| .. raw:: html | ||||
|  | ||||
|     <a href="https://blackfire.io/docs/introduction?utm_source=twig&utm_medium=github_readme&utm_campaign=logo"> | ||||
|     <a href="https://docs.blackfire.io/introduction?utm_source=twig&utm_medium=github_readme&utm_campaign=logo"> | ||||
|         <img src="https://static.blackfire.io/assets/intemporals/logo/png/blackfire-io_secondary_horizontal_transparent.png?1" width="255px" alt="Blackfire.io"> | ||||
|     </a> | ||||
|  | ||||
|   | ||||
							
								
								
									
										21
									
								
								data/web/inc/lib/vendor/twig/twig/composer.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								data/web/inc/lib/vendor/twig/twig/composer.json
									
									
									
									
										vendored
									
									
								
							| @@ -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" | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										136
									
								
								data/web/inc/lib/vendor/twig/twig/src/AbstractTwigCallable.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								data/web/inc/lib/vendor/twig/twig/src/AbstractTwigCallable.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of Twig. | ||||
|  * | ||||
|  * (c) Fabien Potencier | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Twig; | ||||
|  | ||||
| /** | ||||
|  * @author Fabien Potencier <fabien@symfony.com> | ||||
|  */ | ||||
| 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); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										20
									
								
								data/web/inc/lib/vendor/twig/twig/src/Attribute/FirstClassTwigCallableReady.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								data/web/inc/lib/vendor/twig/twig/src/Attribute/FirstClassTwigCallableReady.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of Twig. | ||||
|  * | ||||
|  * (c) Fabien Potencier | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Twig\Attribute; | ||||
|  | ||||
| /** | ||||
|  * Marks nodes that are ready to accept a TwigCallable instead of its name. | ||||
|  */ | ||||
| #[\Attribute(\Attribute::TARGET_METHOD)] | ||||
| final class FirstClassTwigCallableReady | ||||
| { | ||||
| } | ||||
							
								
								
									
										20
									
								
								data/web/inc/lib/vendor/twig/twig/src/Attribute/YieldReady.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								data/web/inc/lib/vendor/twig/twig/src/Attribute/YieldReady.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of Twig. | ||||
|  * | ||||
|  * (c) Fabien Potencier | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Twig\Attribute; | ||||
|  | ||||
| /** | ||||
|  * Marks nodes that are ready for using "yield" instead of "echo" or "print()" for rendering. | ||||
|  */ | ||||
| #[\Attribute(\Attribute::TARGET_CLASS)] | ||||
| final class YieldReady | ||||
| { | ||||
| } | ||||
							
								
								
									
										79
									
								
								data/web/inc/lib/vendor/twig/twig/src/Cache/ChainCache.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								data/web/inc/lib/vendor/twig/twig/src/Cache/ChainCache.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of Twig. | ||||
|  * | ||||
|  * (c) Fabien Potencier | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Twig\Cache; | ||||
|  | ||||
| /** | ||||
|  * Chains several caches together. | ||||
|  * | ||||
|  * Cached items are fetched from the first cache having them in its data store. | ||||
|  * They are saved and deleted in all adapters at once. | ||||
|  * | ||||
|  * @author Quentin Devos <quentin@devos.pm> | ||||
|  */ | ||||
| final class ChainCache implements CacheInterface | ||||
| { | ||||
|     /** | ||||
|      * @param iterable<CacheInterface> $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)); | ||||
|     } | ||||
| } | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										25
									
								
								data/web/inc/lib/vendor/twig/twig/src/Cache/ReadOnlyFilesystemCache.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								data/web/inc/lib/vendor/twig/twig/src/Cache/ReadOnlyFilesystemCache.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of Twig. | ||||
|  * | ||||
|  * (c) Fabien Potencier | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Twig\Cache; | ||||
|  | ||||
| /** | ||||
|  * Implements a cache on the filesystem that can only be read, not written to. | ||||
|  * | ||||
|  * @author Quentin Devos <quentin@devos.pm> | ||||
|  */ | ||||
| class ReadOnlyFilesystemCache extends FilesystemCache | ||||
| { | ||||
|     public function write(string $key, string $content): void | ||||
|     { | ||||
|         // Do nothing with the content, it's a read-only filesystem. | ||||
|     } | ||||
| } | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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<string, mixed> */ | ||||
|     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<string|TemplateWrapper> $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<string, mixed> | ||||
|      */ | ||||
|     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<string, array{precedence: int, class: class-string<AbstractUnary>}> | ||||
|      */ | ||||
|     public function getUnaryOperators(): array | ||||
|     { | ||||
| @@ -812,6 +858,8 @@ class Environment | ||||
|  | ||||
|     /** | ||||
|      * @internal | ||||
|      * | ||||
|      * @return array<string, array{precedence: int, class: class-string<AbstractBinary>, 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', | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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']); | ||||
|   | ||||
| @@ -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)))); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 <fabien@symfony.com> | ||||
|  * | ||||
|  * @internal | ||||
|  */ | ||||
| class ExpressionParser | ||||
| { | ||||
|     public const OPERATOR_LEFT = 1; | ||||
|     public const OPERATOR_RIGHT = 2; | ||||
|  | ||||
|     private $parser; | ||||
|     private $env; | ||||
|     /** @var array<string, array{precedence: int, class: class-string<AbstractUnary>}> */ | ||||
|     private $unaryOperators; | ||||
|     /** @var array<string, array{precedence: int, class: class-string<AbstractBinary>, 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 | ||||
|   | ||||
| @@ -40,6 +40,6 @@ abstract class AbstractExtension implements ExtensionInterface | ||||
|  | ||||
|     public function getOperators() | ||||
|     { | ||||
|         return []; | ||||
|         return [[], []]; | ||||
|     } | ||||
| } | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -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(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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<string, callable(Environment, string, string): string> 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']; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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<array> First array of unary operators, second array of binary operators | ||||
|      * | ||||
|      * @psalm-return array{ | ||||
|      *     array<string, array{precedence: int, class: class-string<AbstractExpression>}>, | ||||
|      *     array<string, array{precedence: int, class?: class-string<AbstractExpression>, associativity: ExpressionParser::OPERATOR_*}> | ||||
|      * } | ||||
|      */ | ||||
|     public function getOperators(); | ||||
| } | ||||
|   | ||||
| @@ -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 <fabien@symfony.com> | ||||
|  */ | ||||
| interface GlobalsInterface | ||||
| { | ||||
|     /** | ||||
|      * @return array<string, mixed> | ||||
|      */ | ||||
|     public function getGlobals(): array; | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										30
									
								
								data/web/inc/lib/vendor/twig/twig/src/Extension/YieldNotReadyExtension.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								data/web/inc/lib/vendor/twig/twig/src/Extension/YieldNotReadyExtension.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of Twig. | ||||
|  * | ||||
|  * (c) Fabien Potencier | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Twig\Extension; | ||||
|  | ||||
| use Twig\NodeVisitor\YieldNotReadyNodeVisitor; | ||||
|  | ||||
| /** | ||||
|  * @internal to be removed in Twig 4 | ||||
|  */ | ||||
| final class YieldNotReadyExtension extends AbstractExtension | ||||
| { | ||||
|     public function __construct( | ||||
|         private bool $useYield, | ||||
|     ) { | ||||
|     } | ||||
|  | ||||
|     public function getNodeVisitors(): array | ||||
|     { | ||||
|         return [new YieldNotReadyNodeVisitor($this->useYield)]; | ||||
|     } | ||||
| } | ||||
| @@ -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<string, TwigFilter> */ | ||||
|     private $filters; | ||||
|     /** @var array<string, TwigFilter> */ | ||||
|     private $dynamicFilters; | ||||
|     /** @var array<string, TwigTest> */ | ||||
|     private $tests; | ||||
|     /** @var array<string, TwigTest> */ | ||||
|     private $dynamicTests; | ||||
|     /** @var array<string, TwigFunction> */ | ||||
|     private $functions; | ||||
|     /** @var array<string, TwigFunction> */ | ||||
|     private $dynamicFunctions; | ||||
|     /** @var array<string, array{precedence: int, class: class-string<AbstractExpression>}> */ | ||||
|     private $unaryOperators; | ||||
|     /** @var array<string, array{precedence: int, class?: class-string<AbstractExpression>, associativity: ExpressionParser::OPERATOR_*}> */ | ||||
|     private $binaryOperators; | ||||
|     /** @var array<string, mixed> */ | ||||
|     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<string, mixed> | ||||
|      */ | ||||
|     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<string, array{precedence: int, class: class-string<AbstractExpression>}> | ||||
|      */ | ||||
|     public function getUnaryOperators(): array | ||||
|     { | ||||
|         if (!$this->initialized) { | ||||
| @@ -388,6 +398,9 @@ final class ExtensionSet | ||||
|         return $this->unaryOperators; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return array<string, array{precedence: int, class?: class-string<AbstractExpression>, 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]); | ||||
|   | ||||
| @@ -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); | ||||
|         } | ||||
|  | ||||
|   | ||||
							
								
								
									
										147
									
								
								data/web/inc/lib/vendor/twig/twig/src/Lexer.php
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										147
									
								
								data/web/inc/lib/vendor/twig/twig/src/Lexer.php
									
									
									
									
										vendored
									
									
								
							| @@ -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; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -21,22 +21,28 @@ use Twig\Source; | ||||
|  */ | ||||
| final class ChainLoader implements LoaderInterface | ||||
| { | ||||
|     /** | ||||
|      * @var array<string, bool> | ||||
|      */ | ||||
|     private $hasSourceCache = []; | ||||
|     private $loaders = []; | ||||
|  | ||||
|     /** | ||||
|      * @param LoaderInterface[] $loaders | ||||
|      * @param iterable<LoaderInterface> $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).')' : '')); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -16,10 +16,10 @@ namespace Twig; | ||||
|  * | ||||
|  * @author Fabien Potencier <fabien@symfony.com> | ||||
|  */ | ||||
| class Markup implements \Countable, \JsonSerializable | ||||
| class Markup implements \Countable, \JsonSerializable, \Stringable | ||||
| { | ||||
|     private $content; | ||||
|     private $charset; | ||||
|     private ?string $charset; | ||||
|  | ||||
|     public function __construct($content, $charset) | ||||
|     { | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
|  | ||||
| namespace Twig\Node; | ||||
|  | ||||
| use Twig\Attribute\YieldReady; | ||||
| use Twig\Compiler; | ||||
|  | ||||
| /** | ||||
| @@ -24,11 +25,12 @@ use Twig\Compiler; | ||||
|  * | ||||
|  * @author Fabien Potencier <fabien@symfony.com> | ||||
|  */ | ||||
| #[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 | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
|  | ||||
| namespace Twig\Node; | ||||
|  | ||||
| use Twig\Attribute\YieldReady; | ||||
| use Twig\Compiler; | ||||
|  | ||||
| /** | ||||
| @@ -19,24 +20,29 @@ use Twig\Compiler; | ||||
|  * | ||||
|  * @author Fabien Potencier <fabien@symfony.com> | ||||
|  */ | ||||
| #[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<null|scalar|\Stringable>\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") | ||||
|         ; | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
|  | ||||
| namespace Twig\Node; | ||||
|  | ||||
| use Twig\Attribute\YieldReady; | ||||
| use Twig\Compiler; | ||||
|  | ||||
| /** | ||||
| @@ -19,18 +20,19 @@ use Twig\Compiler; | ||||
|  * | ||||
|  * @author Fabien Potencier <fabien@symfony.com> | ||||
|  */ | ||||
| #[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'))) | ||||
|         ; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -11,11 +11,14 @@ | ||||
|  | ||||
| namespace Twig\Node; | ||||
|  | ||||
| use Twig\Attribute\YieldReady; | ||||
|  | ||||
| /** | ||||
|  * Represents a body node. | ||||
|  * | ||||
|  * @author Fabien Potencier <fabien@symfony.com> | ||||
|  */ | ||||
| #[YieldReady] | ||||
| class BodyNode extends Node | ||||
| { | ||||
| } | ||||
|   | ||||
							
								
								
									
										57
									
								
								data/web/inc/lib/vendor/twig/twig/src/Node/CaptureNode.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								data/web/inc/lib/vendor/twig/twig/src/Node/CaptureNode.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| <?php | ||||
|  | ||||
| /* | ||||
|  * This file is part of Twig. | ||||
|  * | ||||
|  * (c) Fabien Potencier | ||||
|  * | ||||
|  * For the full copyright and license information, please view the LICENSE | ||||
|  * file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Twig\Node; | ||||
|  | ||||
| use Twig\Attribute\YieldReady; | ||||
| use Twig\Compiler; | ||||
|  | ||||
| /** | ||||
|  * Represents a node for which we need to capture the output. | ||||
|  * | ||||
|  * @author Fabien Potencier <fabien@symfony.com> | ||||
|  */ | ||||
| #[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(';'); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -11,17 +11,19 @@ | ||||
|  | ||||
| namespace Twig\Node; | ||||
|  | ||||
| use Twig\Attribute\YieldReady; | ||||
| use Twig\Compiler; | ||||
|  | ||||
| /** | ||||
|  * @author Fabien Potencier <fabien@symfony.com> | ||||
|  */ | ||||
| #[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") | ||||
|         ; | ||||
|     } | ||||
|   | ||||
| @@ -11,17 +11,24 @@ | ||||
|  | ||||
| namespace Twig\Node; | ||||
|  | ||||
| use Twig\Attribute\YieldReady; | ||||
| use Twig\Compiler; | ||||
|  | ||||
| /** | ||||
|  * @author Fabien Potencier <fabien@symfony.com> | ||||
|  */ | ||||
| #[YieldReady] | ||||
| class CheckSecurityNode extends Node | ||||
| { | ||||
|     private $usedFilters; | ||||
|     private $usedTags; | ||||
|     private $usedFunctions; | ||||
|  | ||||
|     /** | ||||
|      * @param array<string, int> $usedFilters | ||||
|      * @param array<string, int> $usedTags | ||||
|      * @param array<string, int> $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() | ||||
|   | ||||
| @@ -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 <fabien@symfony.com> | ||||
|  */ | ||||
| #[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 | ||||
|   | ||||
| @@ -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 <yonelceruto@gmail.com> | ||||
|  */ | ||||
| #[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") | ||||
|         ; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 <fabien@symfony.com> | ||||
|  */ | ||||
| #[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 | ||||
|   | ||||
| @@ -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 <fabien@symfony.com> | ||||
|  */ | ||||
| #[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); | ||||
|   | ||||
| @@ -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'); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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)) | ||||
|         ; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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')) | ||||
|   | ||||
| @@ -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')) | ||||
|   | ||||
| @@ -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')) | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user