diff -x images -crNEB release-4.0.0-a1/.htaccess release-4.0.0-a2-dev/.htaccess
*** release-4.0.0-a1/.htaccess	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/.htaccess	Wed Apr  1 17:47:36 2026
***************
*** 20,32 ****
  
  #
  # The following 3 lines will rewrite URLs passed through the front controller
! # to not require app.php in the actual URL. In other words, a controller is
! # by default accessed at /app.php/my/controller, but can also be accessed at
  # /my/controller
  #
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
! RewriteRule ^(.*)$ app.php [QSA,L]
  
  #
  # If symbolic links are not already being followed,
--- 20,32 ----
  
  #
  # The following 3 lines will rewrite URLs passed through the front controller
! # to not require index.php in the actual URL. In other words, a controller is
! # by default accessed at /index.php/my/controller, but can also be accessed at
  # /my/controller
  #
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
! RewriteRule ^(.*)$ index.php [QSA,L]
  
  #
  # If symbolic links are not already being followed,
***************
*** 60,65 ****
--- 60,73 ----
  			Order Allow,Deny
  			Deny from All
  		</Files>
+ 		<Files "composer.json">
+ 			Order Allow,Deny
+ 			Deny from All
+ 		</Files>
+ 		<Files "composer.lock">
+ 			Order Allow,Deny
+ 			Deny from All
+ 		</Files>
  	</IfVersion>
  	<IfVersion >= 2.4>
  		<Files "config.php">
***************
*** 68,73 ****
--- 76,87 ----
  		<Files "common.php">
  			Require all denied
  		</Files>
+ 		<Files "composer.json">
+ 			Require all denied
+ 		</Files>
+ 		<Files "composer.lock">
+ 			Require all denied
+ 		</Files>
  	</IfVersion>
  </IfModule>
  <IfModule !mod_version.c>
***************
*** 80,85 ****
--- 94,107 ----
  			Order Allow,Deny
  			Deny from All
  		</Files>
+ 		<Files "composer.json">
+ 			Order Allow,Deny
+ 			Deny from All
+ 		</Files>
+ 		<Files "composer.lock">
+ 			Order Allow,Deny
+ 			Deny from All
+ 		</Files>
  	</IfModule>
  	<IfModule mod_authz_core.c>
  		<Files "config.php">
***************
*** 88,92 ****
--- 110,120 ----
  		<Files "common.php">
  			Require all denied
  		</Files>
+ 		<Files "composer.json">
+ 			Require all denied
+ 		</Files>
+ 		<Files "composer.lock">
+ 			Require all denied
+ 		</Files>
  	</IfModule>
  </IfModule>
diff -x images -crNEB release-4.0.0-a1/adm/style/acp_avatar_options_upload.html release-4.0.0-a2-dev/adm/style/acp_avatar_options_upload.html
*** release-4.0.0-a1/adm/style/acp_avatar_options_upload.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/adm/style/acp_avatar_options_upload.html	Wed Apr  1 17:47:36 2026
***************
*** 6,11 ****
--- 6,12 ----
  {% INCLUDECSS T_ASSETS_PATH ~ '/css/cropper.min.css' %}
  {% INCLUDEJS T_ASSETS_PATH ~ '/javascript/cropper.min.js' %}
  {% INCLUDEJS T_ASSETS_PATH ~ '/javascript/jquery-cropper.js' %}
+ {% INCLUDEJS T_ASSETS_PATH ~ '/javascript/hermite.js' %}
  {% INCLUDEJS T_ASSETS_PATH ~ '/javascript/phpbb-avatars.js' %}
  
  <input type="hidden" id="avatar-cropper-data" name="avatar_cropper_data" value=""
diff -x images -crNEB release-4.0.0-a1/adm/style/acp_ext_list.html release-4.0.0-a2-dev/adm/style/acp_ext_list.html
*** release-4.0.0-a1/adm/style/acp_ext_list.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/adm/style/acp_ext_list.html	Wed Apr  1 17:47:36 2026
***************
*** 99,107 ****
  			<tr>
  				<td class="row3">
  					<ol>
! 						{% for step in lang_raw('EXTENSION_INSTALLING_EXPLAIN') %}
! 							<li>{{ step }}</li>
! 						{% endfor %}
  					</ol>
  				</td>
  			</tr>
--- 99,107 ----
  			<tr>
  				<td class="row3">
  					<ol>
! 						<li>{{ lang('EXTENSION_INSTALLING_EXPLAIN_STEP1') }}</li>
! 						<li>{{ lang('EXTENSION_INSTALLING_EXPLAIN_STEP2') }}</li>
! 						<li>{{ lang('EXTENSION_INSTALLING_EXPLAIN_STEP3') }}</li>
  					</ol>
  				</td>
  			</tr>
***************
*** 111,119 ****
  			<tr>
  				<td class="row3">
  					<ol>
! 						{% for step in lang_raw('EXTENSION_UPDATING_EXPLAIN') %}
! 							<li>{{ step }}</li>
! 						{% endfor %}
  					</ol>
  				</td>
  			</tr>
--- 111,120 ----
  			<tr>
  				<td class="row3">
  					<ol>
! 						<li>{{ lang('EXTENSION_UPDATING_EXPLAIN_STEP1') }}</li>
! 						<li>{{ lang('EXTENSION_UPDATING_EXPLAIN_STEP2') }}</li>
! 						<li>{{ lang('EXTENSION_UPDATING_EXPLAIN_STEP3') }}</li>
! 						<li>{{ lang('EXTENSION_UPDATING_EXPLAIN_STEP4') }}</li>
  					</ol>
  				</td>
  			</tr>
***************
*** 123,131 ****
  			<tr>
  				<td class="row3">
  					<ol>
! 						{% for step in lang_raw('EXTENSION_REMOVING_EXPLAIN') %}
! 							<li>{{ step }}</li>
! 						{% endfor %}
  					</ol>
  				</td>
  			</tr>
--- 124,132 ----
  			<tr>
  				<td class="row3">
  					<ol>
! 						<li>{{ lang('EXTENSION_REMOVING_EXPLAIN_STEP1') }}</li>
! 						<li>{{ lang('EXTENSION_REMOVING_EXPLAIN_STEP2') }}</li>
! 						<li>{{ lang('EXTENSION_REMOVING_EXPLAIN_STEP3') }}</li>
  					</ol>
  				</td>
  			</tr>
diff -x images -crNEB release-4.0.0-a1/adm/style/acp_forums.html release-4.0.0-a2-dev/adm/style/acp_forums.html
*** release-4.0.0-a1/adm/style/acp_forums.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/adm/style/acp_forums.html	Wed Apr  1 17:47:36 2026
***************
*** 196,206 ****
--- 196,208 ----
  	<div id="forum_cat_options">
  		<fieldset>
  			<legend>{L_GENERAL_FORUM_SETTINGS}</legend>
+ 		{% EVENT acp_forums_cat_options_prepend %}
  		<dl>
  			<dt><label for="display_active">{L_DISPLAY_ACTIVE_TOPICS}{L_COLON}</label><br /><span>{L_DISPLAY_ACTIVE_TOPICS_EXPLAIN}</span></dt>
  			<dd><label><input type="radio" class="radio" name="display_active" value="1"<!-- IF S_ENABLE_ACTIVE_TOPICS --> id="display_active" checked="checked"<!-- ENDIF --> /> {L_YES}</label>
  				<label><input type="radio" class="radio" name="display_active" value="0"<!-- IF not S_ENABLE_ACTIVE_TOPICS --> id="display_active" checked="checked"<!-- ENDIF --> /> {L_NO}</label></dd>
  		</dl>
+ 		{% EVENT acp_forums_cat_options_append %}
  		</fieldset>
  	</div>
  
***************
*** 314,319 ****
--- 316,322 ----
  	<div id="forum_link_options">
  		<fieldset>
  			<legend>{L_GENERAL_FORUM_SETTINGS}</legend>
+ 		{% EVENT acp_forums_link_options_prepend %}
  		<dl>
  			<dt><label for="link_display_on_index">{L_LIST_INDEX}{L_COLON}</label><br /><span>{L_LIST_INDEX_EXPLAIN}</span></dt>
  			<dd><label><input type="radio" class="radio" name="link_display_on_index" value="1"<!-- IF S_DISPLAY_ON_INDEX --> id="link_display_on_index" checked="checked"<!-- ENDIF --> /> {L_YES}</label>
***************
*** 328,333 ****
--- 331,337 ----
  			<dd><label><input type="radio" class="radio" name="forum_link_track" value="1"<!-- IF S_FORUM_LINK_TRACK --> id="forum_link_track" checked="checked"<!-- ENDIF --> /> {L_YES}</label>
  				<label><input type="radio" class="radio" name="forum_link_track" value="0"<!-- IF not S_FORUM_LINK_TRACK --> id="forum_link_track" checked="checked"<!-- ENDIF --> /> {L_NO}</label></dd>
  		</dl>
+ 		{% EVENT acp_forums_link_options_append %}
  		</fieldset>
  	</div>
  
diff -x images -crNEB release-4.0.0-a1/adm/style/acp_groups.html release-4.0.0-a2-dev/adm/style/acp_groups.html
*** release-4.0.0-a1/adm/style/acp_groups.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/adm/style/acp_groups.html	Wed Apr  1 17:47:36 2026
***************
*** 113,119 ****
  		<legend>{L_GROUP_AVATAR}</legend>
  	<dl>
  		<dt><label>{L_CURRENT_IMAGE}{L_COLON}</label><br /><span>{L_AVATAR_EXPLAIN}</span></dt>
! 		<dd class="c-avatar-box">{% if AVATAR_HTML %}{{ AVATAR_HTML }}{% else %}<img src="{{ ADMIN_ROOT_PATH ~ 'images/no_avatar.gif' }}" alt="">{% endif %}</dd>
  		<dd><label for="avatar_delete"><input type="checkbox" name="avatar_delete" id="avatar_delete" /> {L_DELETE_AVATAR}</label></dd>
  	</dl>
  	<dl>
--- 113,119 ----
  		<legend>{L_GROUP_AVATAR}</legend>
  	<dl>
  		<dt><label>{L_CURRENT_IMAGE}{L_COLON}</label><br /><span>{L_AVATAR_EXPLAIN}</span></dt>
! 		<dd class="c-avatar-box">{% include 'avatar.html' with {avatar_data: AVATAR} only %}</dd>
  		<dd><label for="avatar_delete"><input type="checkbox" name="avatar_delete" id="avatar_delete" /> {L_DELETE_AVATAR}</label></dd>
  	</dl>
  	<dl>
diff -x images -crNEB release-4.0.0-a1/adm/style/acp_users_avatar.html release-4.0.0-a2-dev/adm/style/acp_users_avatar.html
*** release-4.0.0-a1/adm/style/acp_users_avatar.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/adm/style/acp_users_avatar.html	Wed Apr  1 17:47:36 2026
***************
*** 5,11 ****
  	<!-- IF ERROR --><p class="error">{ERROR}</p><!-- ENDIF -->
  		<dl>
  			<dt><label>{L_CURRENT_IMAGE}{L_COLON}</label><br /><span>{L_AVATAR_EXPLAIN}</span></dt>
! 			<dd class="c-avatar-box">{% if AVATAR_HTML %}{{ AVATAR_HTML }}{% else %}<img src="{{ ADMIN_ROOT_PATH ~ 'images/no_avatar.gif' }}" alt="">{% endif %}</dd>
  			<dd><label for="avatar_delete"><input type="checkbox" name="avatar_delete" id="avatar_delete" /> {L_DELETE_AVATAR}</label></dd>
  		</dl>
  	</fieldset>
--- 5,11 ----
  	<!-- IF ERROR --><p class="error">{ERROR}</p><!-- ENDIF -->
  		<dl>
  			<dt><label>{L_CURRENT_IMAGE}{L_COLON}</label><br /><span>{L_AVATAR_EXPLAIN}</span></dt>
! 			<dd class="c-avatar-box">{% include 'avatar.html' with {avatar_data: AVATAR} only %}</dd>
  			<dd><label for="avatar_delete"><input type="checkbox" name="avatar_delete" id="avatar_delete" /> {L_DELETE_AVATAR}</label></dd>
  		</dl>
  	</fieldset>
diff -x images -crNEB release-4.0.0-a1/adm/style/admin.css release-4.0.0-a2-dev/adm/style/admin.css
*** release-4.0.0-a1/adm/style/admin.css	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/adm/style/admin.css	Wed Apr  1 17:47:36 2026
***************
*** 2708,2713 ****
--- 2708,2745 ----
  	padding: 0;
  }
  
+ /* Placeholder avatars
+ ---------------------------------------- */
+ .avatar-color-default { background-color: hsl(207, 15%, 80%); }
+ .avatar-color-0 { background-color: #d32f2f; }
+ .avatar-color-1 { background-color: #c2185b; }
+ .avatar-color-2 { background-color: #7b1fa2; }
+ .avatar-color-3 { background-color: #512da8; }
+ .avatar-color-4 { background-color: #303f9f; }
+ 
+ .avatar-initials {
+ 	font-size: 36px;
+ 	font-weight: bold;
+ 	line-height: 1;
+ 	color: #ffffff;
+ }
+ 
+ .avatar-placeholder {
+ 	border-radius: 50%;
+ 	display: flex;
+ 	justify-content: center;
+ 	align-items: center;
+ 	width: 80px;
+ 	height: 80px;
+ }
+ 
+ .avatar-initials .o-icon {
+ 	font-size: 36px;
+ 	vertical-align: baseline !important;
+ 	width: auto;
+ 	height: auto;
+ }
+ 
  /* Dropdown menu
  ---------------------------------------- */
  .dropdown {
diff -x images -crNEB release-4.0.0-a1/adm/style/avatar.html release-4.0.0-a2-dev/adm/style/avatar.html
*** release-4.0.0-a1/adm/style/avatar.html	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/adm/style/avatar.html	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,17 ----
+ {% if avatar_data.src %}
+ 	{% if avatar_link %}
+ 		<a href="{{ avatar_link }}" class="avatar">{{ avatar(avatar_data, lang('USER_AVATAR')) }}</a>
+ 	{% else %}
+ 		<span class="avatar">{{ avatar(avatar_data, lang('USER_AVATAR')) }}</span>
+ 	{% endif %}
+ {% elseif avatar_data.id %}
+ 	{% set color_classes = ['avatar-color-0', 'avatar-color-1', 'avatar-color-2', 'avatar-color-3', 'avatar-color-4'] %}
+ 	{% set color_class = not(avatar_data.id starts with 'g') ? color_classes[avatar_data.id % 5] : 'avatar-color-default' %}
+ 	{% if avatar_link %}<a href="{{ avatar_link }}" class="avatar">{% endif %}
+ 	<div class="avatar-placeholder {{ color_class }}">
+ 		{% set username = username ?: avatar_data.username %}
+ 		{% set initials = username|striptags|trim(' -+_[]', 'left')|slice(0, 1)|upper %}
+ 		<div class="avatar-initials">{% if initials %}{{ initials }}{% else %}{{ Icon('font', 'user', '', true) }}{% endif %}</div>
+ 	</div>
+ 	{% if avatar_link %}</a>{% endif %}
+ {% endif %}
diff -x images -crNEB release-4.0.0-a1/adm/style/installer_install.html release-4.0.0-a2-dev/adm/style/installer_install.html
*** release-4.0.0-a1/adm/style/installer_install.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/adm/style/installer_install.html	Wed Apr  1 17:47:36 2026
***************
*** 6,11 ****
--- 6,14 ----
  	<fieldset class="submit-buttons">
  		<legend>{L_SUBMIT}</legend>
  		<input class="button1" name="install" type="submit" value="{L_INSTALL}" />
+ 		{% if SHOW_RESTART_BUTTON %}
+ 		<input class="button2" name="install_restart" type="submit" value="{{ lang('INSTALL_RESTART') }}" />
+ 		{% endif %}
  	</fieldset>
  </form>
  <!-- ENDIF -->
diff -x images -crNEB release-4.0.0-a1/app.php release-4.0.0-a2-dev/app.php
*** release-4.0.0-a1/app.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/app.php	Wed Apr  1 17:47:36 2026
***************
*** 12,27 ****
  */
  
  /**
- */
- 
- /**
  * @ignore
  */
  define('IN_PHPBB', true);
  $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './';
  $phpEx = substr(strrchr(__FILE__, '.'), 1);
  include($phpbb_root_path . 'common.' . $phpEx);
  
  // Start session management
  $user->session_begin();
  $auth->acl($user->data);
--- 12,41 ----
  */
  
  /**
  * @ignore
  */
+ 
+ use Symfony\Component\HttpFoundation\RedirectResponse;
+ 
  define('IN_PHPBB', true);
  $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './';
  $phpEx = substr(strrchr(__FILE__, '.'), 1);
  include($phpbb_root_path . 'common.' . $phpEx);
  
+ if (basename($symfony_request->getScriptName()) === 'app.php')
+ {
+ 	$base_path = $symfony_request->getBasePath();
+ 	$path_info = $symfony_request->getPathInfo();
+ 	$query_string = $symfony_request->getQueryString();
+ 
+ 	$url = $base_path . '/index.php' . $path_info . ($query_string ? '?' . $query_string : '');
+ 
+ 	$response = new RedirectResponse($url, 301);
+ 	$response->send();
+ 
+ 	exit;
+ }
+ 
  // Start session management
  $user->session_begin();
  $auth->acl($user->data);
diff -x images -crNEB release-4.0.0-a1/assets/javascript/installer.js release-4.0.0-a2-dev/assets/javascript/installer.js
*** release-4.0.0-a1/assets/javascript/installer.js	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/assets/javascript/installer.js	Wed Apr  1 17:47:36 2026
***************
*** 375,381 ****
  	 */
  	function queryInstallerStatus() {
  		var url = $(location).attr('pathname');
! 		var lookUp = 'install/app.php';
  		var position = url.indexOf(lookUp);
  
  		if (position === -1) {
--- 375,381 ----
  	 */
  	function queryInstallerStatus() {
  		var url = $(location).attr('pathname');
! 		var lookUp = 'install/index.php';
  		var position = url.indexOf(lookUp);
  
  		if (position === -1) {
diff -x images -crNEB release-4.0.0-a1/assets/javascript/mentions.js release-4.0.0-a2-dev/assets/javascript/mentions.js
*** release-4.0.0-a1/assets/javascript/mentions.js	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/assets/javascript/mentions.js	Wed Apr  1 17:47:36 2026
***************
*** 290,295 ****
--- 290,298 ----
  				selectClass: 'is-active',
  				itemClass: 'mention-item',
  				menuItemTemplate,
+ 				noMatchTemplate() {
+ 					return (typeof mention_no_match_found === 'undefined') ? 'No match found' : mention_no_match_found;
+ 				},
  				selectTemplate(item) {
  					return '[mention ' + (item.type === 'g' ? 'group_id=' : 'user_id=') + item.id + ']' + item.name + '[/mention]';
  				},
diff -x images -crNEB release-4.0.0-a1/assets/javascript/phpbb-avatars.js release-4.0.0-a2-dev/assets/javascript/phpbb-avatars.js
*** release-4.0.0-a1/assets/javascript/phpbb-avatars.js	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/assets/javascript/phpbb-avatars.js	Wed Apr  1 17:47:36 2026
***************
*** 11,16 ****
--- 11,19 ----
  		cropper: null,
  		image: null,
  
+ 		/** @type {jQuery|null} */
+ 		$originalAvatar: null,
+ 
  		/** @type {jQuery} */
  		$form: null,
  
***************
*** 32,61 ****
  		/** @type {string} */
  		driverUpload: 'avatar_driver_upload',
  
  		/**
  		 * Initialise avatar cropping.
  		 */
  		init() {
  			// If the cropper library is not available
! 			if (!$.isFunction($.fn.cropper)) {
  				return;
  			}
  
  			// Correctly position the cropper buttons
  			this.$buttons.appendTo(this.$box);
  
! 			// Ensure we have an img for the cropping
! 			if (this.$box.children('img').length === 0) {
! 				const $avatarImg = $('<img src="" alt="">');
! 				$avatarImg.setAttribute('width', phpbb.avatars.$data.data().maxWidth);
! 				$avatarImg.setAttribute('height', phpbb.avatars.$data.data().maxHeight);
! 				$avatarImg.addClass('avatar');
! 				this.image = $avatarImg;
! 				this.$box.prepend($avatarImg);
! 			} else {
! 				this.image = this.$box.children('img');
  			}
  
  			this.bindInput();
  			this.bindSelect();
  			this.bindSubmit();
--- 35,78 ----
  		/** @type {string} */
  		driverUpload: 'avatar_driver_upload',
  
+ 		/** @type {{width: {min: number, max: number}, height: {min: number, max: number}}} Allowed avatar sizes */
+ 		allowedSizes: {
+ 			width: { min: -1, max: -1 },
+ 			height: { min: -1, max: -1 },
+ 		},
+ 
  		/**
  		 * Initialise avatar cropping.
  		 */
  		init() {
  			// If the cropper library is not available
! 			if (!$.fn.hasOwnProperty('cropper') || typeof $.fn.cropper !== 'function') {
  				return;
  			}
  
+ 			// Set allowed sizes from data attributes
+ 			const data = this.$data.data();
+ 			this.allowedSizes.width.min = data.minWidth;
+ 			this.allowedSizes.width.max = data.maxWidth;
+ 			this.allowedSizes.height.min = data.minHeight;
+ 			this.allowedSizes.height.max = data.maxHeight;
+ 
  			// Correctly position the cropper buttons
  			this.$buttons.appendTo(this.$box);
  
! 			// Add image for cropping but track original avatar if it exists
! 			const $existingImg = this.$box.find('img');
! 			if ($existingImg.length) {
! 				this.$originalAvatar = $existingImg;
  			}
  
+ 			const $avatarImg = $('<img src="" alt="">');
+ 			$avatarImg.attr('width', this.allowedSizes.width.max);
+ 			$avatarImg.attr('height', this.allowedSizes.height.max);
+ 			$avatarImg.addClass('avatar hidden');
+ 			this.image = $avatarImg;
+ 			this.$box.prepend($avatarImg);
+ 
  			this.bindInput();
  			this.bindSelect();
  			this.bindSubmit();
***************
*** 67,75 ****
--- 84,94 ----
  		destroy() {
  			this.$buttons.find('[data-cropper-action]').off('click.phpbb.avatars');
  			this.image.off('crop.phpbb.avatars');
+ 			this.setAvatarVisible(true);
  			this.$form.off('submit');
  
  			this.$data.val('');
+ 			this.$input.val(null);
  			this.$buttons.hide();
  			this.$box.removeClass('c-cropper-avatar-box');
  
***************
*** 87,96 ****
  		bindSelect() {
  			this.$driver.on('change', function() {
  				if ($(this).val() === phpbb.avatars.driverUpload) {
! 					if (phpbb.avatars.$input.val() !== '') {
! 						phpbb.avatars.$input.trigger('change');
! 					}
  				} else {
  					phpbb.avatars.destroy();
  				}
  			});
--- 106,119 ----
  		bindSelect() {
  			this.$driver.on('change', function() {
  				if ($(this).val() === phpbb.avatars.driverUpload) {
! 					// Rebind submit after switching back to upload driver
! 					phpbb.avatars.bindSubmit();
  				} else {
+ 					// Show placeholder avatar if it exists and was hidden
+ 					if (phpbb.avatars.$box.children('.avatar-placeholder').length) {
+ 						phpbb.avatars.$box.children('.avatar-placeholder').show();
+ 					}
+ 
  					phpbb.avatars.destroy();
  				}
  			});
***************
*** 112,117 ****
--- 135,141 ----
  					fileReader.addEventListener('load', function() {
  						phpbb.avatars.image.cropper('destroy').attr('src', this.result).addClass('avatar');
  						phpbb.avatars.$box.addClass('c-cropper-avatar-box');
+ 						phpbb.avatars.setAvatarVisible(false);
  						phpbb.avatars.initCropper();
  						phpbb.avatars.initButtons();
  					});
***************
*** 122,134 ****
  		},
  
  		/**
  		 * Bind submit button to be handled by ajax submit
  		 */
  		bindSubmit() {
  			const $this = this;
  			$this.$form = this.$input.closest('form');
  			$this.$form.on('submit', () => {
! 				const data = phpbb.avatars.$data.data();
  
  				const avatarCanvas = phpbb.avatars.cropper.getCroppedCanvas({
  					maxWidth: 4096, // High values for max quality cropping
--- 146,177 ----
  		},
  
  		/**
+ 		 * Show or hide the original avatar image.
+ 		 * @param {boolean} visible
+ 		 * @return {void}
+ 		 */
+ 		setAvatarVisible(visible) {
+ 			if (this.$originalAvatar !== null) {
+ 				if (visible) {
+ 					phpbb.avatars.$originalAvatar.removeClass('hidden');
+ 				} else {
+ 					phpbb.avatars.$originalAvatar.addClass('hidden');
+ 				}
+ 			}
+ 		},
+ 
+ 		/**
  		 * Bind submit button to be handled by ajax submit
  		 */
  		bindSubmit() {
  			const $this = this;
  			$this.$form = this.$input.closest('form');
  			$this.$form.on('submit', () => {
! 				if ($this.$form.find('#avatar_delete').is(':checked')) {
! 					return;
! 				}
! 
! 				const $submitButton = this.$form.find('fieldset > input[type=submit]').first();
  
  				const avatarCanvas = phpbb.avatars.cropper.getCroppedCanvas({
  					maxWidth: 4096, // High values for max quality cropping
***************
*** 137,148 ****
  
  				// eslint-disable-next-line no-undef
  				const hermiteResize = new Hermite_class();
! 				hermiteResize.resample_single(avatarCanvas, data.maxWidth, data.maxHeight, true);
  
  				avatarCanvas.toBlob(blob => {
  					const formData = new FormData($this.$form[0]);
  					formData.set('avatar_upload_file', blob, $this.getUploadFileName());
! 					formData.set('submit', '1');
  
  					const canvasDataUrl = avatarCanvas.toDataURL('image/png');
  
--- 180,191 ----
  
  				// eslint-disable-next-line no-undef
  				const hermiteResize = new Hermite_class();
! 				hermiteResize.resample_single(avatarCanvas, phpbb.avatars.allowedSizes.width.max, phpbb.avatars.allowedSizes.height.max, true);
  
  				avatarCanvas.toBlob(blob => {
  					const formData = new FormData($this.$form[0]);
  					formData.set('avatar_upload_file', blob, $this.getUploadFileName());
! 					formData.set($submitButton.attr('name'), $submitButton.val());
  
  					const canvasDataUrl = avatarCanvas.toDataURL('image/png');
  
***************
*** 181,187 ****
  
  		/**
  		 * Handle response from avatar submission
! 		 * @param {Object} response AJAX response object
  		 * @param {string} canvasDataUrl Uploaded canvas element as data URL
  		 */
  		uploadDone(response, canvasDataUrl) {
--- 224,232 ----
  
  		/**
  		 * Handle response from avatar submission
! 		 * @param {{MESSAGE_TITLE: string, MESSAGE_TEXT: string,
! 		 * 			REFRESH_DATA: {time: int, url: string},
! 		 * 			error: {title: string, messages: string[]}}} response AJAX response object
  		 * @param {string} canvasDataUrl Uploaded canvas element as data URL
  		 */
  		uploadDone(response, canvasDataUrl) {
***************
*** 191,196 ****
--- 236,244 ----
  
  			// Handle errors while deleting file
  			if (typeof response.error === 'undefined') {
+ 				// Ensure image is visible after upload
+ 				phpbb.avatars.image.removeClass('hidden');
+ 
  				const alert = phpbb.alert(response.MESSAGE_TITLE, response.MESSAGE_TEXT);
  
  				setTimeout(() => {
***************
*** 198,204 ****
  					alert.hide();
  				}, response.REFRESH_DATA.time * 1000);
  
! 				phpbb.avatars.image.attr('src', canvasDataUrl);
  				phpbb.avatars.destroy();
  			} else {
  				phpbb.alert(response.error.title, response.error.messages.join('<br>'));
--- 246,259 ----
  					alert.hide();
  				}, response.REFRESH_DATA.time * 1000);
  
! 				// Update original avatar image if it exists or use added image
! 				if (phpbb.avatars.$originalAvatar !== null) {
! 					phpbb.avatars.$originalAvatar.attr('src', canvasDataUrl);
! 					phpbb.avatars.image.addClass('hidden');
! 				} else {
! 					phpbb.avatars.image.attr('src', canvasDataUrl);
! 				}
! 
  				phpbb.avatars.destroy();
  			} else {
  				phpbb.alert(response.error.title, response.error.messages.join('<br>'));
***************
*** 239,247 ****
--- 294,307 ----
  		 * and registers a callback function for the 'crop' event.
  		 */
  		initCropper() {
+ 			// Hide placeholder avatar
+ 			this.$box.children('.avatar-placeholder').hide();
+ 
  			this.cropper = this.image.cropper({
  				aspectRatio: 1,
  				autoCropArea: 1,
+ 				minContainerHeight: this.allowedSizes.height.max * 2, // Double max size for better usability
+ 				minContainerWidth: this.allowedSizes.width.max * 2, // Double max size for better usability
  			}).data('cropper');
  
  			this.image.off('crop.phpbb.avatars').on('crop.phpbb.avatars', phpbb.avatars.onCrop);
***************
*** 261,272 ****
  		 * @param {object} event
  		 */
  		onCrop(event) {
- 			const data = phpbb.avatars.$data.data();
  			let { width, height } = event.detail;
  
! 			if (width < data.minWidth || height < data.minHeight) {
! 				width = Math.max(data.minWidth, Math.min(data.maxWidth, width));
! 				height = Math.max(data.minHeight, Math.min(data.maxHeight, height));
  				phpbb.avatars.cropper.setData({
  					width,
  					height,
--- 321,332 ----
  		 * @param {object} event
  		 */
  		onCrop(event) {
  			let { width, height } = event.detail;
+ 			const allowedSizes = phpbb.avatars.allowedSizes;
  
! 			if (width < allowedSizes.width.min || height < allowedSizes.height.min) {
! 				width = Math.max(allowedSizes.width.min, Math.min(allowedSizes.width.max, width));
! 				height = Math.max(allowedSizes.height.min, Math.min(allowedSizes.height.max, height));
  				phpbb.avatars.cropper.setData({
  					width,
  					height,
diff -x images -crNEB release-4.0.0-a1/assets/javascript/webpush.js release-4.0.0-a2-dev/assets/javascript/webpush.js
*** release-4.0.0-a1/assets/javascript/webpush.js	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/assets/javascript/webpush.js	Wed Apr  1 17:47:36 2026
***************
*** 165,212 ****
  	async function subscribeButtonHandler(event) {
  		event.preventDefault();
  
! 		subscribeButton.addEventListener('click', subscribeButtonHandler);
  
! 		// Prevent the user from clicking the subscribe button multiple times.
! 		const result = await Notification.requestPermission();
! 		if (result === 'denied') {
! 			phpbb.alert(subscribeButton.getAttribute('data-l-err'), subscribeButton.getAttribute('data-l-msg'));
! 			return;
! 		}
  
! 		const registration = await navigator.serviceWorker.getRegistration(serviceWorkerUrl);
  
! 		// We might already have a subscription that is unknown to this instance of phpBB.
! 		// Unsubscribe before trying to subscribe again.
! 		if (typeof registration !== 'undefined') {
! 			const subscribed = await registration.pushManager.getSubscription();
! 			if (subscribed) {
! 				await subscribed.unsubscribe();
  			}
- 		}
  
! 		const newSubscription = await registration.pushManager.subscribe({
! 			userVisibleOnly: true,
! 			applicationServerKey: urlB64ToUint8Array(vapidPublicKey),
! 		});
! 
! 		const loadingIndicator = phpbb.loadingIndicator();
! 		fetch(subscribeUrl, {
! 			method: 'POST',
! 			headers: {
! 				'X-Requested-With': 'XMLHttpRequest',
! 			},
! 			body: getFormData(newSubscription),
! 		})
! 			.then(response => {
! 				loadingIndicator.fadeOut(phpbb.alertTime);
! 				return response.json();
! 			})
! 			.then(handleSubscribe)
! 			.catch(error => {
! 				loadingIndicator.fadeOut(phpbb.alertTime);
! 				phpbb.alert(ajaxErrorTitle, error);
  			});
  	}
  
  	/**
--- 165,219 ----
  	async function subscribeButtonHandler(event) {
  		event.preventDefault();
  
! 		subscribeButton.removeEventListener('click', subscribeButtonHandler);
  
! 		try {
! 			// Prevent the user from clicking the subscribe button multiple times.
! 			const result = await Notification.requestPermission();
! 			if (result === 'denied') {
! 				phpbb.alert(subscribeButton.getAttribute('data-l-err'), subscribeButton.getAttribute('data-l-msg'));
! 				return;
! 			}
  
! 			const registration = await navigator.serviceWorker.getRegistration(serviceWorkerUrl);
  
! 			// We might already have a subscription that is unknown to this instance of phpBB.
! 			// Unsubscribe before trying to subscribe again.
! 			if (typeof registration !== 'undefined') {
! 				const subscribed = await registration.pushManager.getSubscription();
! 				if (subscribed) {
! 					await subscribed.unsubscribe();
! 				}
  			}
  
! 			const newSubscription = await registration.pushManager.subscribe({
! 				userVisibleOnly: true,
! 				applicationServerKey: urlB64ToUint8Array(vapidPublicKey),
  			});
+ 
+ 			const loadingIndicator = phpbb.loadingIndicator();
+ 			fetch(subscribeUrl, {
+ 				method: 'POST',
+ 				headers: {
+ 					'X-Requested-With': 'XMLHttpRequest',
+ 				},
+ 				body: getFormData(newSubscription),
+ 			})
+ 				.then(response => {
+ 					loadingIndicator.fadeOut(phpbb.alertTime);
+ 					return response.json();
+ 				})
+ 				.then(handleSubscribe)
+ 				.catch(error => {
+ 					loadingIndicator.fadeOut(phpbb.alertTime);
+ 					phpbb.alert(ajaxErrorTitle, error);
+ 				});
+ 		} catch (error) {
+ 			console.info(error);
+ 			phpbb.alert(subscribeButton.getAttribute('data-l-err'), error.message || subscribeButton.getAttribute('data-disabled-msg'));
+ 		} finally {
+ 			subscribeButton.addEventListener('click', subscribeButtonHandler);
+ 		}
  	}
  
  	/**
diff -x images -crNEB release-4.0.0-a1/composer.json release-4.0.0-a2-dev/composer.json
*** release-4.0.0-a1/composer.json	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/composer.json	Wed Apr  1 17:47:36 2026
***************
*** 26,32 ****
  		"phpbb/phpbb-core": "self.version"
  	},
  	"require": {
! 		"php": "^8.1",
  		"ext-pdo": "*",
  		"ext-zip": "*",
  		"ext-zlib": "*",
--- 26,32 ----
  		"phpbb/phpbb-core": "self.version"
  	},
  	"require": {
! 		"php": "^8.2",
  		"ext-pdo": "*",
  		"ext-zip": "*",
  		"ext-zlib": "*",
***************
*** 42,77 ****
  		"marc1706/fast-image-size": "^1.1",
  		"minishlink/web-push": "^8.0",
  		"s9e/text-formatter": "^2.0",
! 		"symfony/config": "^6.3",
! 		"symfony/console": "^6.3",
! 		"symfony/dependency-injection": "^6.3",
! 		"symfony/error-handler": "^6.3",
! 		"symfony/event-dispatcher": "^6.3",
! 		"symfony/filesystem": "^6.3",
! 		"symfony/finder": "^6.3",
! 		"symfony/http-foundation": "^6.3",
! 		"symfony/http-kernel": "^6.3",
  		"symfony/polyfill-mbstring": "^1.23",
! 		"symfony/mailer": "^6.3",
! 		"symfony/mime": "^6.3",
! 		"symfony/process": "^6.3",
! 		"symfony/proxy-manager-bridge": "^6.3",
! 		"symfony/routing": "^6.3",
! 		"symfony/twig-bridge": "^6.3",
! 		"symfony/yaml": "^6.3",
  		"twig/twig": "^3.14"
  	},
  	"require-dev": {
- 		"laravel/homestead": "~14.4",
  		"misantron/dbunit": "~5.0",
  		"phing/phing": "~2.4",
  		"phpunit/phpunit": "^10.0",
! 		"squizlabs/php_codesniffer": "~3.4",
! 		"symfony/browser-kit": "^6.3",
! 		"symfony/css-selector": "^6.3",
! 		"symfony/dom-crawler": "^6.3",
! 		"symfony/http-client": "^6.3",
! 		"vimeo/psalm": "^5.18.0",
  		"psalm/plugin-symfony": "^v5.1.0"
  	},
  	"extra": {
--- 42,75 ----
  		"marc1706/fast-image-size": "^1.1",
  		"minishlink/web-push": "^8.0",
  		"s9e/text-formatter": "^2.0",
! 		"symfony/config": "^7.4",
! 		"symfony/console": "^7.4",
! 		"symfony/dependency-injection": "^7.4",
! 		"symfony/error-handler": "^7.4",
! 		"symfony/event-dispatcher": "^7.4",
! 		"symfony/filesystem": "^7.4",
! 		"symfony/finder": "^7.4",
! 		"symfony/http-foundation": "^7.4",
! 		"symfony/http-kernel": "^7.4",
  		"symfony/polyfill-mbstring": "^1.23",
! 		"symfony/mailer": "^7.4",
! 		"symfony/mime": "^7.4",
! 		"symfony/process": "^7.4",
! 		"symfony/routing": "^7.4",
! 		"symfony/twig-bridge": "^7.4",
! 		"symfony/yaml": "^7.4",
  		"twig/twig": "^3.14"
  	},
  	"require-dev": {
  		"misantron/dbunit": "~5.0",
  		"phing/phing": "~2.4",
  		"phpunit/phpunit": "^10.0",
! 		"squizlabs/php_codesniffer": "^4.0",
! 		"symfony/browser-kit": "^7.4",
! 		"symfony/css-selector": "^7.4",
! 		"symfony/dom-crawler": "^7.4",
! 		"symfony/http-client": "^7.4",
! 		"vimeo/psalm": "^6.13.1",
  		"psalm/plugin-symfony": "^v5.1.0"
  	},
  	"extra": {
***************
*** 81,87 ****
  	},
  	"config": {
  		"platform": {
! 			"php": "8.1.0"
  		},
  		"allow-plugins": {
  			"composer/installers": true
--- 79,85 ----
  	},
  	"config": {
  		"platform": {
! 			"php": "8.2.27"
  		},
  		"allow-plugins": {
  			"composer/installers": true
diff -x images -crNEB release-4.0.0-a1/composer.lock release-4.0.0-a2-dev/composer.lock
*** release-4.0.0-a1/composer.lock	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/composer.lock	Wed Apr  1 17:47:36 2026
***************
*** 4,10 ****
          "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
          "This file is @generated automatically"
      ],
!     "content-hash": "43fe1ef1a33684f75d832b93f7ac764c",
      "packages": [
          {
              "name": "bantu/ini-get-wrapper",
--- 4,10 ----
          "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
          "This file is @generated automatically"
      ],
!     "content-hash": "96906149986423b8cf69fded01e56c6d",
      "packages": [
          {
              "name": "bantu/ini-get-wrapper",
***************
*** 173,188 ****
          },
          {
              "name": "composer/ca-bundle",
!             "version": "1.5.8",
              "source": {
                  "type": "git",
                  "url": "https://github.com/composer/ca-bundle.git",
!                 "reference": "719026bb30813accb68271fee7e39552a58e9f65"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/composer/ca-bundle/zipball/719026bb30813accb68271fee7e39552a58e9f65",
!                 "reference": "719026bb30813accb68271fee7e39552a58e9f65",
                  "shasum": ""
              },
              "require": {
--- 173,188 ----
          },
          {
              "name": "composer/ca-bundle",
!             "version": "1.5.10",
              "source": {
                  "type": "git",
                  "url": "https://github.com/composer/ca-bundle.git",
!                 "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/composer/ca-bundle/zipball/961a5e4056dd2e4a2eedcac7576075947c28bf63",
!                 "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63",
                  "shasum": ""
              },
              "require": {
***************
*** 229,235 ****
              "support": {
                  "irc": "irc://irc.freenode.org/composer",
                  "issues": "https://github.com/composer/ca-bundle/issues",
!                 "source": "https://github.com/composer/ca-bundle/tree/1.5.8"
              },
              "funding": [
                  {
--- 229,235 ----
              "support": {
                  "irc": "irc://irc.freenode.org/composer",
                  "issues": "https://github.com/composer/ca-bundle/issues",
!                 "source": "https://github.com/composer/ca-bundle/tree/1.5.10"
              },
              "funding": [
                  {
***************
*** 241,266 ****
                      "type": "github"
                  }
              ],
!             "time": "2025-08-20T18:49:47+00:00"
          },
          {
              "name": "composer/class-map-generator",
!             "version": "1.6.2",
              "source": {
                  "type": "git",
                  "url": "https://github.com/composer/class-map-generator.git",
!                 "reference": "ba9f089655d4cdd64e762a6044f411ccdaec0076"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/composer/class-map-generator/zipball/ba9f089655d4cdd64e762a6044f411ccdaec0076",
!                 "reference": "ba9f089655d4cdd64e762a6044f411ccdaec0076",
                  "shasum": ""
              },
              "require": {
                  "composer/pcre": "^2.1 || ^3.1",
                  "php": "^7.2 || ^8.0",
!                 "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7"
              },
              "require-dev": {
                  "phpstan/phpstan": "^1.12 || ^2",
--- 241,266 ----
                      "type": "github"
                  }
              ],
!             "time": "2025-12-08T15:06:51+00:00"
          },
          {
              "name": "composer/class-map-generator",
!             "version": "1.7.1",
              "source": {
                  "type": "git",
                  "url": "https://github.com/composer/class-map-generator.git",
!                 "reference": "8f5fa3cc214230e71f54924bd0197a3bcc705eb1"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/composer/class-map-generator/zipball/8f5fa3cc214230e71f54924bd0197a3bcc705eb1",
!                 "reference": "8f5fa3cc214230e71f54924bd0197a3bcc705eb1",
                  "shasum": ""
              },
              "require": {
                  "composer/pcre": "^2.1 || ^3.1",
                  "php": "^7.2 || ^8.0",
!                 "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8"
              },
              "require-dev": {
                  "phpstan/phpstan": "^1.12 || ^2",
***************
*** 268,274 ****
                  "phpstan/phpstan-phpunit": "^1 || ^2",
                  "phpstan/phpstan-strict-rules": "^1.1 || ^2",
                  "phpunit/phpunit": "^8",
!                 "symfony/filesystem": "^5.4 || ^6"
              },
              "type": "library",
              "extra": {
--- 268,274 ----
                  "phpstan/phpstan-phpunit": "^1 || ^2",
                  "phpstan/phpstan-strict-rules": "^1.1 || ^2",
                  "phpunit/phpunit": "^8",
!                 "symfony/filesystem": "^5.4 || ^6 || ^7 || ^8"
              },
              "type": "library",
              "extra": {
***************
*** 298,304 ****
              ],
              "support": {
                  "issues": "https://github.com/composer/class-map-generator/issues",
!                 "source": "https://github.com/composer/class-map-generator/tree/1.6.2"
              },
              "funding": [
                  {
--- 298,304 ----
              ],
              "support": {
                  "issues": "https://github.com/composer/class-map-generator/issues",
!                 "source": "https://github.com/composer/class-map-generator/tree/1.7.1"
              },
              "funding": [
                  {
***************
*** 310,353 ****
                      "type": "github"
                  }
              ],
!             "time": "2025-08-20T18:52:43+00:00"
          },
          {
              "name": "composer/composer",
!             "version": "2.8.11",
              "source": {
                  "type": "git",
                  "url": "https://github.com/composer/composer.git",
!                 "reference": "00e1a3396eea67033775c4a49c772376f45acd73"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/composer/composer/zipball/00e1a3396eea67033775c4a49c772376f45acd73",
!                 "reference": "00e1a3396eea67033775c4a49c772376f45acd73",
                  "shasum": ""
              },
              "require": {
                  "composer/ca-bundle": "^1.5",
                  "composer/class-map-generator": "^1.4.0",
                  "composer/metadata-minifier": "^1.0",
!                 "composer/pcre": "^2.2 || ^3.2",
                  "composer/semver": "^3.3",
                  "composer/spdx-licenses": "^1.5.7",
                  "composer/xdebug-handler": "^2.0.2 || ^3.0.3",
!                 "justinrainbow/json-schema": "^6.3.1",
                  "php": "^7.2.5 || ^8.0",
                  "psr/log": "^1.0 || ^2.0 || ^3.0",
!                 "react/promise": "^2.11 || ^3.3",
                  "seld/jsonlint": "^1.4",
                  "seld/phar-utils": "^1.2",
                  "seld/signal-handler": "^2.0",
!                 "symfony/console": "^5.4.35 || ^6.3.12 || ^7.0.3",
!                 "symfony/filesystem": "^5.4.35 || ^6.3.12 || ^7.0.3",
!                 "symfony/finder": "^5.4.35 || ^6.3.12 || ^7.0.3",
                  "symfony/polyfill-php73": "^1.24",
                  "symfony/polyfill-php80": "^1.24",
                  "symfony/polyfill-php81": "^1.24",
!                 "symfony/process": "^5.4.35 || ^6.3.12 || ^7.0.3"
              },
              "require-dev": {
                  "phpstan/phpstan": "^1.11.8",
--- 310,355 ----
                      "type": "github"
                  }
              ],
!             "time": "2025-12-29T13:15:25+00:00"
          },
          {
              "name": "composer/composer",
!             "version": "2.9.5",
              "source": {
                  "type": "git",
                  "url": "https://github.com/composer/composer.git",
!                 "reference": "72a8f8e653710e18d83e5dd531eb5a71fc3223e6"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/composer/composer/zipball/72a8f8e653710e18d83e5dd531eb5a71fc3223e6",
!                 "reference": "72a8f8e653710e18d83e5dd531eb5a71fc3223e6",
                  "shasum": ""
              },
              "require": {
                  "composer/ca-bundle": "^1.5",
                  "composer/class-map-generator": "^1.4.0",
                  "composer/metadata-minifier": "^1.0",
!                 "composer/pcre": "^2.3 || ^3.3",
                  "composer/semver": "^3.3",
                  "composer/spdx-licenses": "^1.5.7",
                  "composer/xdebug-handler": "^2.0.2 || ^3.0.3",
!                 "ext-json": "*",
!                 "justinrainbow/json-schema": "^6.5.1",
                  "php": "^7.2.5 || ^8.0",
                  "psr/log": "^1.0 || ^2.0 || ^3.0",
!                 "react/promise": "^3.3",
                  "seld/jsonlint": "^1.4",
                  "seld/phar-utils": "^1.2",
                  "seld/signal-handler": "^2.0",
!                 "symfony/console": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0",
!                 "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0",
!                 "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0",
                  "symfony/polyfill-php73": "^1.24",
                  "symfony/polyfill-php80": "^1.24",
                  "symfony/polyfill-php81": "^1.24",
!                 "symfony/polyfill-php84": "^1.30",
!                 "symfony/process": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0"
              },
              "require-dev": {
                  "phpstan/phpstan": "^1.11.8",
***************
*** 355,366 ****
                  "phpstan/phpstan-phpunit": "^1.4.0",
                  "phpstan/phpstan-strict-rules": "^1.6.0",
                  "phpstan/phpstan-symfony": "^1.4.0",
!                 "symfony/phpunit-bridge": "^6.4.3 || ^7.0.1"
              },
              "suggest": {
!                 "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages",
!                 "ext-zip": "Enabling the zip extension allows you to unzip archives",
!                 "ext-zlib": "Allow gzip compression of HTTP requests"
              },
              "bin": [
                  "bin/composer"
--- 357,369 ----
                  "phpstan/phpstan-phpunit": "^1.4.0",
                  "phpstan/phpstan-strict-rules": "^1.6.0",
                  "phpstan/phpstan-symfony": "^1.4.0",
!                 "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3 || ^8.0"
              },
              "suggest": {
!                 "ext-curl": "Provides HTTP support (will fallback to PHP streams if missing)",
!                 "ext-openssl": "Enables access to repositories and packages over HTTPS",
!                 "ext-zip": "Allows direct extraction of ZIP archives (unzip/7z binaries will be used instead if available)",
!                 "ext-zlib": "Enables gzip for HTTP requests"
              },
              "bin": [
                  "bin/composer"
***************
*** 373,379 ****
                      ]
                  },
                  "branch-alias": {
!                     "dev-main": "2.8-dev"
                  }
              },
              "autoload": {
--- 376,382 ----
                      ]
                  },
                  "branch-alias": {
!                     "dev-main": "2.9-dev"
                  }
              },
              "autoload": {
***************
*** 408,414 ****
                  "irc": "ircs://irc.libera.chat:6697/composer",
                  "issues": "https://github.com/composer/composer/issues",
                  "security": "https://github.com/composer/composer/security/policy",
!                 "source": "https://github.com/composer/composer/tree/2.8.11"
              },
              "funding": [
                  {
--- 411,417 ----
                  "irc": "ircs://irc.libera.chat:6697/composer",
                  "issues": "https://github.com/composer/composer/issues",
                  "security": "https://github.com/composer/composer/security/policy",
!                 "source": "https://github.com/composer/composer/tree/2.9.5"
              },
              "funding": [
                  {
***************
*** 420,426 ****
                      "type": "github"
                  }
              ],
!             "time": "2025-08-21T09:29:39+00:00"
          },
          {
              "name": "composer/installers",
--- 423,429 ----
                      "type": "github"
                  }
              ],
!             "time": "2026-01-29T10:40:53+00:00"
          },
          {
              "name": "composer/installers",
***************
*** 1014,1029 ****
          },
          {
              "name": "doctrine/dbal",
!             "version": "3.10.2",
              "source": {
                  "type": "git",
                  "url": "https://github.com/doctrine/dbal.git",
!                 "reference": "c6c16cf787eaba3112203dfcd715fa2059c62282"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/doctrine/dbal/zipball/c6c16cf787eaba3112203dfcd715fa2059c62282",
!                 "reference": "c6c16cf787eaba3112203dfcd715fa2059c62282",
                  "shasum": ""
              },
              "require": {
--- 1017,1032 ----
          },
          {
              "name": "doctrine/dbal",
!             "version": "3.10.5",
              "source": {
                  "type": "git",
                  "url": "https://github.com/doctrine/dbal.git",
!                 "reference": "95d84866bf3c04b2ddca1df7c049714660959aef"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/doctrine/dbal/zipball/95d84866bf3c04b2ddca1df7c049714660959aef",
!                 "reference": "95d84866bf3c04b2ddca1df7c049714660959aef",
                  "shasum": ""
              },
              "require": {
***************
*** 1039,1054 ****
              },
              "require-dev": {
                  "doctrine/cache": "^1.11|^2.0",
!                 "doctrine/coding-standard": "13.0.1",
                  "fig/log-test": "^1",
                  "jetbrains/phpstorm-stubs": "2023.1",
!                 "phpstan/phpstan": "2.1.22",
                  "phpstan/phpstan-strict-rules": "^2",
!                 "phpunit/phpunit": "9.6.23",
!                 "slevomat/coding-standard": "8.16.2",
!                 "squizlabs/php_codesniffer": "3.13.1",
!                 "symfony/cache": "^5.4|^6.0|^7.0",
!                 "symfony/console": "^4.4|^5.4|^6.0|^7.0"
              },
              "suggest": {
                  "symfony/console": "For helpful console commands such as SQL execution and import of files."
--- 1042,1057 ----
              },
              "require-dev": {
                  "doctrine/cache": "^1.11|^2.0",
!                 "doctrine/coding-standard": "14.0.0",
                  "fig/log-test": "^1",
                  "jetbrains/phpstorm-stubs": "2023.1",
!                 "phpstan/phpstan": "2.1.30",
                  "phpstan/phpstan-strict-rules": "^2",
!                 "phpunit/phpunit": "9.6.34",
!                 "slevomat/coding-standard": "8.27.1",
!                 "squizlabs/php_codesniffer": "4.0.1",
!                 "symfony/cache": "^5.4|^6.0|^7.0|^8.0",
!                 "symfony/console": "^4.4|^5.4|^6.0|^7.0|^8.0"
              },
              "suggest": {
                  "symfony/console": "For helpful console commands such as SQL execution and import of files."
***************
*** 1108,1114 ****
              ],
              "support": {
                  "issues": "https://github.com/doctrine/dbal/issues",
!                 "source": "https://github.com/doctrine/dbal/tree/3.10.2"
              },
              "funding": [
                  {
--- 1111,1117 ----
              ],
              "support": {
                  "issues": "https://github.com/doctrine/dbal/issues",
!                 "source": "https://github.com/doctrine/dbal/tree/3.10.5"
              },
              "funding": [
                  {
***************
*** 1124,1156 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-09-04T23:51:27+00:00"
          },
          {
              "name": "doctrine/deprecations",
!             "version": "1.1.5",
              "source": {
                  "type": "git",
                  "url": "https://github.com/doctrine/deprecations.git",
!                 "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38",
!                 "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38",
                  "shasum": ""
              },
              "require": {
                  "php": "^7.1 || ^8.0"
              },
              "conflict": {
!                 "phpunit/phpunit": "<=7.5 || >=13"
              },
              "require-dev": {
!                 "doctrine/coding-standard": "^9 || ^12 || ^13",
!                 "phpstan/phpstan": "1.4.10 || 2.1.11",
                  "phpstan/phpstan-phpunit": "^1.0 || ^2",
!                 "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12",
                  "psr/log": "^1 || ^2 || ^3"
              },
              "suggest": {
--- 1127,1159 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-02-24T08:03:57+00:00"
          },
          {
              "name": "doctrine/deprecations",
!             "version": "1.1.6",
              "source": {
                  "type": "git",
                  "url": "https://github.com/doctrine/deprecations.git",
!                 "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca",
!                 "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca",
                  "shasum": ""
              },
              "require": {
                  "php": "^7.1 || ^8.0"
              },
              "conflict": {
!                 "phpunit/phpunit": "<=7.5 || >=14"
              },
              "require-dev": {
!                 "doctrine/coding-standard": "^9 || ^12 || ^14",
!                 "phpstan/phpstan": "1.4.10 || 2.1.30",
                  "phpstan/phpstan-phpunit": "^1.0 || ^2",
!                 "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0",
                  "psr/log": "^1 || ^2 || ^3"
              },
              "suggest": {
***************
*** 1170,1191 ****
              "homepage": "https://www.doctrine-project.org/",
              "support": {
                  "issues": "https://github.com/doctrine/deprecations/issues",
!                 "source": "https://github.com/doctrine/deprecations/tree/1.1.5"
              },
!             "time": "2025-04-07T20:06:18+00:00"
          },
          {
              "name": "doctrine/event-manager",
!             "version": "2.0.1",
              "source": {
                  "type": "git",
                  "url": "https://github.com/doctrine/event-manager.git",
!                 "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e",
!                 "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e",
                  "shasum": ""
              },
              "require": {
--- 1173,1194 ----
              "homepage": "https://www.doctrine-project.org/",
              "support": {
                  "issues": "https://github.com/doctrine/deprecations/issues",
!                 "source": "https://github.com/doctrine/deprecations/tree/1.1.6"
              },
!             "time": "2026-02-07T07:09:04+00:00"
          },
          {
              "name": "doctrine/event-manager",
!             "version": "2.1.1",
              "source": {
                  "type": "git",
                  "url": "https://github.com/doctrine/event-manager.git",
!                 "reference": "dda33921b198841ca8dbad2eaa5d4d34769d18cf"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/doctrine/event-manager/zipball/dda33921b198841ca8dbad2eaa5d4d34769d18cf",
!                 "reference": "dda33921b198841ca8dbad2eaa5d4d34769d18cf",
                  "shasum": ""
              },
              "require": {
***************
*** 1195,1204 ****
                  "doctrine/common": "<2.9"
              },
              "require-dev": {
!                 "doctrine/coding-standard": "^12",
!                 "phpstan/phpstan": "^1.8.8",
!                 "phpunit/phpunit": "^10.5",
!                 "vimeo/psalm": "^5.24"
              },
              "type": "library",
              "autoload": {
--- 1198,1207 ----
                  "doctrine/common": "<2.9"
              },
              "require-dev": {
!                 "doctrine/coding-standard": "^14",
!                 "phpdocumentor/guides-cli": "^1.4",
!                 "phpstan/phpstan": "^2.1.32",
!                 "phpunit/phpunit": "^10.5.58"
              },
              "type": "library",
              "autoload": {
***************
*** 1247,1253 ****
              ],
              "support": {
                  "issues": "https://github.com/doctrine/event-manager/issues",
!                 "source": "https://github.com/doctrine/event-manager/tree/2.0.1"
              },
              "funding": [
                  {
--- 1250,1256 ----
              ],
              "support": {
                  "issues": "https://github.com/doctrine/event-manager/issues",
!                 "source": "https://github.com/doctrine/event-manager/tree/2.1.1"
              },
              "funding": [
                  {
***************
*** 1263,1269 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2024-05-22T20:47:39+00:00"
          },
          {
              "name": "doctrine/lexer",
--- 1266,1272 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-01-29T07:11:08+00:00"
          },
          {
              "name": "doctrine/lexer",
***************
*** 1410,1497 ****
              "time": "2025-03-06T22:45:56+00:00"
          },
          {
-             "name": "friendsofphp/proxy-manager-lts",
-             "version": "v1.0.18",
-             "source": {
-                 "type": "git",
-                 "url": "https://github.com/FriendsOfPHP/proxy-manager-lts.git",
-                 "reference": "2c8a6cffc3220e99352ad958fe7cf06bf6f7690f"
-             },
-             "dist": {
-                 "type": "zip",
-                 "url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/2c8a6cffc3220e99352ad958fe7cf06bf6f7690f",
-                 "reference": "2c8a6cffc3220e99352ad958fe7cf06bf6f7690f",
-                 "shasum": ""
-             },
-             "require": {
-                 "laminas/laminas-code": "~3.4.1|^4.0",
-                 "php": ">=7.1",
-                 "symfony/filesystem": "^4.4.17|^5.0|^6.0|^7.0"
-             },
-             "conflict": {
-                 "laminas/laminas-stdlib": "<3.2.1",
-                 "zendframework/zend-stdlib": "<3.2.1"
-             },
-             "replace": {
-                 "ocramius/proxy-manager": "^2.1"
-             },
-             "require-dev": {
-                 "ext-phar": "*",
-                 "symfony/phpunit-bridge": "^5.4|^6.0|^7.0"
-             },
-             "type": "library",
-             "extra": {
-                 "thanks": {
-                     "url": "https://github.com/Ocramius/ProxyManager",
-                     "name": "ocramius/proxy-manager"
-                 }
-             },
-             "autoload": {
-                 "psr-4": {
-                     "ProxyManager\\": "src/ProxyManager"
-                 }
-             },
-             "notification-url": "https://packagist.org/downloads/",
-             "license": [
-                 "MIT"
-             ],
-             "authors": [
-                 {
-                     "name": "Marco Pivetta",
-                     "email": "ocramius@gmail.com",
-                     "homepage": "https://ocramius.github.io/"
-                 },
-                 {
-                     "name": "Nicolas Grekas",
-                     "email": "p@tchwork.com"
-                 }
-             ],
-             "description": "Adding support for a wider range of PHP versions to ocramius/proxy-manager",
-             "homepage": "https://github.com/FriendsOfPHP/proxy-manager-lts",
-             "keywords": [
-                 "aop",
-                 "lazy loading",
-                 "proxy",
-                 "proxy pattern",
-                 "service proxies"
-             ],
-             "support": {
-                 "issues": "https://github.com/FriendsOfPHP/proxy-manager-lts/issues",
-                 "source": "https://github.com/FriendsOfPHP/proxy-manager-lts/tree/v1.0.18"
-             },
-             "funding": [
-                 {
-                     "url": "https://github.com/Ocramius",
-                     "type": "github"
-                 },
-                 {
-                     "url": "https://tidelift.com/funding/github/packagist/ocramius/proxy-manager",
-                     "type": "tidelift"
-                 }
-             ],
-             "time": "2024-03-20T12:50:41+00:00"
-         },
-         {
              "name": "google/recaptcha",
              "version": "1.3.1",
              "source": {
--- 1413,1418 ----
***************
*** 1754,1769 ****
          },
          {
              "name": "guzzlehttp/psr7",
!             "version": "2.8.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/guzzle/psr7.git",
!                 "reference": "21dc724a0583619cd1652f673303492272778051"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051",
!                 "reference": "21dc724a0583619cd1652f673303492272778051",
                  "shasum": ""
              },
              "require": {
--- 1675,1690 ----
          },
          {
              "name": "guzzlehttp/psr7",
!             "version": "2.9.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/guzzle/psr7.git",
!                 "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884",
!                 "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884",
                  "shasum": ""
              },
              "require": {
***************
*** 1779,1784 ****
--- 1700,1706 ----
              "require-dev": {
                  "bamarni/composer-bin-plugin": "^1.8.2",
                  "http-interop/http-factory-tests": "0.9.0",
+                 "jshttp/mime-db": "1.54.0.1",
                  "phpunit/phpunit": "^8.5.44 || ^9.6.25"
              },
              "suggest": {
***************
*** 1850,1856 ****
              ],
              "support": {
                  "issues": "https://github.com/guzzle/psr7/issues",
!                 "source": "https://github.com/guzzle/psr7/tree/2.8.0"
              },
              "funding": [
                  {
--- 1772,1778 ----
              ],
              "support": {
                  "issues": "https://github.com/guzzle/psr7/issues",
!                 "source": "https://github.com/guzzle/psr7/tree/2.9.0"
              },
              "funding": [
                  {
***************
*** 1866,1890 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-23T21:21:41+00:00"
          },
          {
              "name": "justinrainbow/json-schema",
!             "version": "6.5.2",
              "source": {
                  "type": "git",
                  "url": "https://github.com/jsonrainbow/json-schema.git",
!                 "reference": "ac0d369c09653cf7af561f6d91a705bc617a87b8"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/ac0d369c09653cf7af561f6d91a705bc617a87b8",
!                 "reference": "ac0d369c09653cf7af561f6d91a705bc617a87b8",
                  "shasum": ""
              },
              "require": {
                  "ext-json": "*",
!                 "marc-mabe/php-enum": "^4.0",
                  "php": "^7.2 || ^8.0"
              },
              "require-dev": {
--- 1788,1812 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-03-10T16:41:02+00:00"
          },
          {
              "name": "justinrainbow/json-schema",
!             "version": "v6.7.2",
              "source": {
                  "type": "git",
                  "url": "https://github.com/jsonrainbow/json-schema.git",
!                 "reference": "6fea66c7204683af437864e7c4e7abf383d14bc0"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/6fea66c7204683af437864e7c4e7abf383d14bc0",
!                 "reference": "6fea66c7204683af437864e7c4e7abf383d14bc0",
                  "shasum": ""
              },
              "require": {
                  "ext-json": "*",
!                 "marc-mabe/php-enum": "^4.4",
                  "php": "^7.2 || ^8.0"
              },
              "require-dev": {
***************
*** 1939,2010 ****
              ],
              "support": {
                  "issues": "https://github.com/jsonrainbow/json-schema/issues",
!                 "source": "https://github.com/jsonrainbow/json-schema/tree/6.5.2"
!             },
!             "time": "2025-09-09T09:42:27+00:00"
!         },
!         {
!             "name": "laminas/laminas-code",
!             "version": "4.16.0",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/laminas/laminas-code.git",
!                 "reference": "1793e78dad4108b594084d05d1fb818b85b110af"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/laminas/laminas-code/zipball/1793e78dad4108b594084d05d1fb818b85b110af",
!                 "reference": "1793e78dad4108b594084d05d1fb818b85b110af",
!                 "shasum": ""
              },
!             "require": {
!                 "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
!             },
!             "require-dev": {
!                 "doctrine/annotations": "^2.0.1",
!                 "ext-phar": "*",
!                 "laminas/laminas-coding-standard": "^3.0.0",
!                 "laminas/laminas-stdlib": "^3.18.0",
!                 "phpunit/phpunit": "^10.5.37",
!                 "psalm/plugin-phpunit": "^0.19.0",
!                 "vimeo/psalm": "^5.15.0"
!             },
!             "suggest": {
!                 "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features",
!                 "laminas/laminas-stdlib": "Laminas\\Stdlib component"
!             },
!             "type": "library",
!             "autoload": {
!                 "psr-4": {
!                     "Laminas\\Code\\": "src/"
!                 }
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "BSD-3-Clause"
!             ],
!             "description": "Extensions to the PHP Reflection API, static code scanning, and code generation",
!             "homepage": "https://laminas.dev",
!             "keywords": [
!                 "code",
!                 "laminas",
!                 "laminasframework"
!             ],
!             "support": {
!                 "chat": "https://laminas.dev/chat",
!                 "docs": "https://docs.laminas.dev/laminas-code/",
!                 "forum": "https://discourse.laminas.dev",
!                 "issues": "https://github.com/laminas/laminas-code/issues",
!                 "rss": "https://github.com/laminas/laminas-code/releases.atom",
!                 "source": "https://github.com/laminas/laminas-code"
!             },
!             "funding": [
!                 {
!                     "url": "https://funding.communitybridge.org/projects/laminas-project",
!                     "type": "community_bridge"
!                 }
!             ],
!             "time": "2024-11-20T13:15:13+00:00"
          },
          {
              "name": "marc-mabe/php-enum",
--- 1861,1869 ----
              ],
              "support": {
                  "issues": "https://github.com/jsonrainbow/json-schema/issues",
!                 "source": "https://github.com/jsonrainbow/json-schema/tree/v6.7.2"
              },
!             "time": "2026-02-15T15:06:22+00:00"
          },
          {
              "name": "marc-mabe/php-enum",
***************
*** 2081,2109 ****
          },
          {
              "name": "marc1706/fast-image-size",
!             "version": "v1.1.7",
              "source": {
                  "type": "git",
                  "url": "https://github.com/marc1706/fast-image-size.git",
!                 "reference": "aea6934a273182208bced600953f19a3d2eaeca0"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/marc1706/fast-image-size/zipball/aea6934a273182208bced600953f19a3d2eaeca0",
!                 "reference": "aea6934a273182208bced600953f19a3d2eaeca0",
                  "shasum": ""
              },
              "require": {
                  "ext-mbstring": "*",
!                 "php": ">=5.3.0"
              },
              "require-dev": {
!                 "phpunit/phpunit": "^4.8"
              },
              "type": "library",
              "extra": {
                  "branch-alias": {
!                     "dev-master": "1.1.x-dev"
                  }
              },
              "autoload": {
--- 1940,1968 ----
          },
          {
              "name": "marc1706/fast-image-size",
!             "version": "v1.3.1",
              "source": {
                  "type": "git",
                  "url": "https://github.com/marc1706/fast-image-size.git",
!                 "reference": "35c193f56d3a3abaf441918ea4eda7cd5d89d443"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/marc1706/fast-image-size/zipball/35c193f56d3a3abaf441918ea4eda7cd5d89d443",
!                 "reference": "35c193f56d3a3abaf441918ea4eda7cd5d89d443",
                  "shasum": ""
              },
              "require": {
                  "ext-mbstring": "*",
!                 "php": ">=7.2"
              },
              "require-dev": {
!                 "phpunit/phpunit": "^8.5"
              },
              "type": "library",
              "extra": {
                  "branch-alias": {
!                     "dev-master": "1.3.x-dev"
                  }
              },
              "autoload": {
***************
*** 2135,2143 ****
              ],
              "support": {
                  "issues": "https://github.com/marc1706/fast-image-size/issues",
!                 "source": "https://github.com/marc1706/fast-image-size/tree/v1.1.7"
              },
!             "time": "2022-01-12T16:29:39+00:00"
          },
          {
              "name": "minishlink/web-push",
--- 1994,2002 ----
              ],
              "support": {
                  "issues": "https://github.com/marc1706/fast-image-size/issues",
!                 "source": "https://github.com/marc1706/fast-image-size/tree/v1.3.1"
              },
!             "time": "2026-03-28T15:26:03+00:00"
          },
          {
              "name": "minishlink/web-push",
***************
*** 2208,2231 ****
          },
          {
              "name": "paragonie/constant_time_encoding",
!             "version": "v3.0.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/paragonie/constant_time_encoding.git",
!                 "reference": "df1e7fde177501eee2037dd159cf04f5f301a512"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512",
!                 "reference": "df1e7fde177501eee2037dd159cf04f5f301a512",
                  "shasum": ""
              },
              "require": {
                  "php": "^8"
              },
              "require-dev": {
!                 "phpunit/phpunit": "^9",
!                 "vimeo/psalm": "^4|^5"
              },
              "type": "library",
              "autoload": {
--- 2067,2092 ----
          },
          {
              "name": "paragonie/constant_time_encoding",
!             "version": "v3.1.3",
              "source": {
                  "type": "git",
                  "url": "https://github.com/paragonie/constant_time_encoding.git",
!                 "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
!                 "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
                  "shasum": ""
              },
              "require": {
                  "php": "^8"
              },
              "require-dev": {
!                 "infection/infection": "^0",
!                 "nikic/php-fuzzer": "^0",
!                 "phpunit/phpunit": "^9|^10|^11",
!                 "vimeo/psalm": "^4|^5|^6"
              },
              "type": "library",
              "autoload": {
***************
*** 2271,2290 ****
                  "issues": "https://github.com/paragonie/constant_time_encoding/issues",
                  "source": "https://github.com/paragonie/constant_time_encoding"
              },
!             "time": "2024-05-08T12:36:18+00:00"
          },
          {
              "name": "paragonie/sodium_compat",
!             "version": "v2.1.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/paragonie/sodium_compat.git",
!                 "reference": "a673d5f310477027cead2e2f2b6db5d8368157cb"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/a673d5f310477027cead2e2f2b6db5d8368157cb",
!                 "reference": "a673d5f310477027cead2e2f2b6db5d8368157cb",
                  "shasum": ""
              },
              "require": {
--- 2132,2151 ----
                  "issues": "https://github.com/paragonie/constant_time_encoding/issues",
                  "source": "https://github.com/paragonie/constant_time_encoding"
              },
!             "time": "2025-09-24T15:06:41+00:00"
          },
          {
              "name": "paragonie/sodium_compat",
!             "version": "v2.5.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/paragonie/sodium_compat.git",
!                 "reference": "4714da6efdc782c06690bc72ce34fae7941c2d9f"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/4714da6efdc782c06690bc72ce34fae7941c2d9f",
!                 "reference": "4714da6efdc782c06690bc72ce34fae7941c2d9f",
                  "shasum": ""
              },
              "require": {
***************
*** 2292,2299 ****
                  "php-64bit": "*"
              },
              "require-dev": {
!                 "phpunit/phpunit": "^7|^8|^9",
!                 "vimeo/psalm": "^4|^5"
              },
              "suggest": {
                  "ext-sodium": "Better performance, password hashing (Argon2i), secure memory management (memzero), and better security."
--- 2153,2162 ----
                  "php-64bit": "*"
              },
              "require-dev": {
!                 "infection/infection": "^0",
!                 "nikic/php-fuzzer": "^0",
!                 "phpunit/phpunit": "^7|^8|^9|^10|^11",
!                 "vimeo/psalm": "^4|^5|^6"
              },
              "suggest": {
                  "ext-sodium": "Better performance, password hashing (Argon2i), secure memory management (memzero), and better security."
***************
*** 2307,2313 ****
              "autoload": {
                  "files": [
                      "autoload.php"
!                 ]
              },
              "notification-url": "https://packagist.org/downloads/",
              "license": [
--- 2170,2179 ----
              "autoload": {
                  "files": [
                      "autoload.php"
!                 ],
!                 "psr-4": {
!                     "ParagonIE\\Sodium\\": "namespaced/"
!                 }
              },
              "notification-url": "https://packagist.org/downloads/",
              "license": [
***************
*** 2360,2368 ****
              ],
              "support": {
                  "issues": "https://github.com/paragonie/sodium_compat/issues",
!                 "source": "https://github.com/paragonie/sodium_compat/tree/v2.1.0"
              },
!             "time": "2024-09-04T12:51:01+00:00"
          },
          {
              "name": "psr/cache",
--- 2226,2234 ----
              ],
              "support": {
                  "issues": "https://github.com/paragonie/sodium_compat/issues",
!                 "source": "https://github.com/paragonie/sodium_compat/tree/v2.5.0"
              },
!             "time": "2025-12-30T16:12:18+00:00"
          },
          {
              "name": "psr/cache",
***************
*** 2981,2996 ****
          },
          {
              "name": "s9e/text-formatter",
!             "version": "2.19.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/s9e/TextFormatter.git",
!                 "reference": "d65a4f61cbe494937afb3150dc73b6e757d400d3"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/d65a4f61cbe494937afb3150dc73b6e757d400d3",
!                 "reference": "d65a4f61cbe494937afb3150dc73b6e757d400d3",
                  "shasum": ""
              },
              "require": {
--- 2847,2862 ----
          },
          {
              "name": "s9e/text-formatter",
!             "version": "2.19.3",
              "source": {
                  "type": "git",
                  "url": "https://github.com/s9e/TextFormatter.git",
!                 "reference": "aee579c12d05ca3053f9b9abdb8c479c0f2fbe69"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/aee579c12d05ca3053f9b9abdb8c479c0f2fbe69",
!                 "reference": "aee579c12d05ca3053f9b9abdb8c479c0f2fbe69",
                  "shasum": ""
              },
              "require": {
***************
*** 3018,3024 ****
              },
              "type": "library",
              "extra": {
!                 "version": "2.19.0"
              },
              "autoload": {
                  "psr-4": {
--- 2884,2890 ----
              },
              "type": "library",
              "extra": {
!                 "version": "2.19.3"
              },
              "autoload": {
                  "psr-4": {
***************
*** 3050,3058 ****
              ],
              "support": {
                  "issues": "https://github.com/s9e/TextFormatter/issues",
!                 "source": "https://github.com/s9e/TextFormatter/tree/2.19.0"
              },
!             "time": "2025-04-26T09:27:34+00:00"
          },
          {
              "name": "seld/jsonlint",
--- 2916,2924 ----
              ],
              "support": {
                  "issues": "https://github.com/s9e/TextFormatter/issues",
!                 "source": "https://github.com/s9e/TextFormatter/tree/2.19.3"
              },
!             "time": "2025-11-14T21:26:59+00:00"
          },
          {
              "name": "seld/jsonlint",
***************
*** 3294,3333 ****
          },
          {
              "name": "spomky-labs/pki-framework",
!             "version": "1.3.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/Spomky-Labs/pki-framework.git",
!                 "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/eced5b5ce70518b983ff2be486e902bbd15135ae",
!                 "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae",
                  "shasum": ""
              },
              "require": {
!                 "brick/math": "^0.10|^0.11|^0.12|^0.13",
                  "ext-mbstring": "*",
!                 "php": ">=8.1"
              },
              "require-dev": {
                  "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0",
                  "ext-gmp": "*",
                  "ext-openssl": "*",
!                 "infection/infection": "^0.28|^0.29",
                  "php-parallel-lint/php-parallel-lint": "^1.3",
                  "phpstan/extension-installer": "^1.3|^2.0",
                  "phpstan/phpstan": "^1.8|^2.0",
                  "phpstan/phpstan-deprecation-rules": "^1.0|^2.0",
                  "phpstan/phpstan-phpunit": "^1.1|^2.0",
                  "phpstan/phpstan-strict-rules": "^1.3|^2.0",
!                 "phpunit/phpunit": "^10.1|^11.0|^12.0",
                  "rector/rector": "^1.0|^2.0",
                  "roave/security-advisories": "dev-latest",
!                 "symfony/string": "^6.4|^7.0",
!                 "symfony/var-dumper": "^6.4|^7.0",
!                 "symplify/easy-coding-standard": "^12.0"
              },
              "suggest": {
                  "ext-bcmath": "For better performance (or GMP)",
--- 3160,3200 ----
          },
          {
              "name": "spomky-labs/pki-framework",
!             "version": "1.4.2",
              "source": {
                  "type": "git",
                  "url": "https://github.com/Spomky-Labs/pki-framework.git",
!                 "reference": "aa576cbd07128075bef97ac2f8af9854e67513d8"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/aa576cbd07128075bef97ac2f8af9854e67513d8",
!                 "reference": "aa576cbd07128075bef97ac2f8af9854e67513d8",
                  "shasum": ""
              },
              "require": {
!                 "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14|^0.15|^0.16|^0.17",
                  "ext-mbstring": "*",
!                 "php": ">=8.1",
!                 "psr/clock": "^1.0"
              },
              "require-dev": {
                  "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0",
                  "ext-gmp": "*",
                  "ext-openssl": "*",
!                 "infection/infection": "^0.28|^0.29|^0.31|^0.32",
                  "php-parallel-lint/php-parallel-lint": "^1.3",
                  "phpstan/extension-installer": "^1.3|^2.0",
                  "phpstan/phpstan": "^1.8|^2.0",
                  "phpstan/phpstan-deprecation-rules": "^1.0|^2.0",
                  "phpstan/phpstan-phpunit": "^1.1|^2.0",
                  "phpstan/phpstan-strict-rules": "^1.3|^2.0",
!                 "phpunit/phpunit": "^10.1|^11.0|^12.0|^13.0",
                  "rector/rector": "^1.0|^2.0",
                  "roave/security-advisories": "dev-latest",
!                 "symfony/string": "^6.4|^7.0|^8.0",
!                 "symfony/var-dumper": "^6.4|^7.0|^8.0",
!                 "symplify/easy-coding-standard": "^12.0|^13.0"
              },
              "suggest": {
                  "ext-bcmath": "For better performance (or GMP)",
***************
*** 3387,3393 ****
              ],
              "support": {
                  "issues": "https://github.com/Spomky-Labs/pki-framework/issues",
!                 "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.3.0"
              },
              "funding": [
                  {
--- 3254,3260 ----
              ],
              "support": {
                  "issues": "https://github.com/Spomky-Labs/pki-framework/issues",
!                 "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.2"
              },
              "funding": [
                  {
***************
*** 3399,3436 ****
                      "type": "patreon"
                  }
              ],
!             "time": "2025-06-13T08:35:04+00:00"
          },
          {
              "name": "symfony/config",
!             "version": "v6.4.24",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/config.git",
!                 "reference": "80e2cf005cf17138c97193be0434cdcfd1b2212e"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/config/zipball/80e2cf005cf17138c97193be0434cdcfd1b2212e",
!                 "reference": "80e2cf005cf17138c97193be0434cdcfd1b2212e",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "symfony/deprecation-contracts": "^2.5|^3",
!                 "symfony/filesystem": "^5.4|^6.0|^7.0",
                  "symfony/polyfill-ctype": "~1.8"
              },
              "conflict": {
!                 "symfony/finder": "<5.4",
                  "symfony/service-contracts": "<2.5"
              },
              "require-dev": {
!                 "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
!                 "symfony/finder": "^5.4|^6.0|^7.0",
!                 "symfony/messenger": "^5.4|^6.0|^7.0",
                  "symfony/service-contracts": "^2.5|^3",
!                 "symfony/yaml": "^5.4|^6.0|^7.0"
              },
              "type": "library",
              "autoload": {
--- 3266,3303 ----
                      "type": "patreon"
                  }
              ],
!             "time": "2026-03-23T22:56:56+00:00"
          },
          {
              "name": "symfony/config",
!             "version": "v7.4.7",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/config.git",
!                 "reference": "6c17162555bfb58957a55bb0e43e00035b6ae3d5"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/config/zipball/6c17162555bfb58957a55bb0e43e00035b6ae3d5",
!                 "reference": "6c17162555bfb58957a55bb0e43e00035b6ae3d5",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "symfony/deprecation-contracts": "^2.5|^3",
!                 "symfony/filesystem": "^7.1|^8.0",
                  "symfony/polyfill-ctype": "~1.8"
              },
              "conflict": {
!                 "symfony/finder": "<6.4",
                  "symfony/service-contracts": "<2.5"
              },
              "require-dev": {
!                 "symfony/event-dispatcher": "^6.4|^7.0|^8.0",
!                 "symfony/finder": "^6.4|^7.0|^8.0",
!                 "symfony/messenger": "^6.4|^7.0|^8.0",
                  "symfony/service-contracts": "^2.5|^3",
!                 "symfony/yaml": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 3458,3464 ****
              "description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/config/tree/v6.4.24"
              },
              "funding": [
                  {
--- 3325,3331 ----
              "description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/config/tree/v7.4.7"
              },
              "funding": [
                  {
***************
*** 3478,3528 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-07-26T13:50:30+00:00"
          },
          {
              "name": "symfony/console",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/console.git",
!                 "reference": "273fd29ff30ba0a88ca5fb83f7cf1ab69306adae"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/console/zipball/273fd29ff30ba0a88ca5fb83f7cf1ab69306adae",
!                 "reference": "273fd29ff30ba0a88ca5fb83f7cf1ab69306adae",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "symfony/deprecation-contracts": "^2.5|^3",
                  "symfony/polyfill-mbstring": "~1.0",
                  "symfony/service-contracts": "^2.5|^3",
!                 "symfony/string": "^5.4|^6.0|^7.0"
              },
              "conflict": {
!                 "symfony/dependency-injection": "<5.4",
!                 "symfony/dotenv": "<5.4",
!                 "symfony/event-dispatcher": "<5.4",
!                 "symfony/lock": "<5.4",
!                 "symfony/process": "<5.4"
              },
              "provide": {
                  "psr/log-implementation": "1.0|2.0|3.0"
              },
              "require-dev": {
                  "psr/log": "^1|^2|^3",
!                 "symfony/config": "^5.4|^6.0|^7.0",
!                 "symfony/dependency-injection": "^5.4|^6.0|^7.0",
!                 "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
!                 "symfony/http-foundation": "^6.4|^7.0",
!                 "symfony/http-kernel": "^6.4|^7.0",
!                 "symfony/lock": "^5.4|^6.0|^7.0",
!                 "symfony/messenger": "^5.4|^6.0|^7.0",
!                 "symfony/process": "^5.4|^6.0|^7.0",
!                 "symfony/stopwatch": "^5.4|^6.0|^7.0",
!                 "symfony/var-dumper": "^5.4|^6.0|^7.0"
              },
              "type": "library",
              "autoload": {
--- 3345,3395 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-03-06T10:41:14+00:00"
          },
          {
              "name": "symfony/console",
!             "version": "v7.4.7",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/console.git",
!                 "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d",
!                 "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "symfony/deprecation-contracts": "^2.5|^3",
                  "symfony/polyfill-mbstring": "~1.0",
                  "symfony/service-contracts": "^2.5|^3",
!                 "symfony/string": "^7.2|^8.0"
              },
              "conflict": {
!                 "symfony/dependency-injection": "<6.4",
!                 "symfony/dotenv": "<6.4",
!                 "symfony/event-dispatcher": "<6.4",
!                 "symfony/lock": "<6.4",
!                 "symfony/process": "<6.4"
              },
              "provide": {
                  "psr/log-implementation": "1.0|2.0|3.0"
              },
              "require-dev": {
                  "psr/log": "^1|^2|^3",
!                 "symfony/config": "^6.4|^7.0|^8.0",
!                 "symfony/dependency-injection": "^6.4|^7.0|^8.0",
!                 "symfony/event-dispatcher": "^6.4|^7.0|^8.0",
!                 "symfony/http-foundation": "^6.4|^7.0|^8.0",
!                 "symfony/http-kernel": "^6.4|^7.0|^8.0",
!                 "symfony/lock": "^6.4|^7.0|^8.0",
!                 "symfony/messenger": "^6.4|^7.0|^8.0",
!                 "symfony/process": "^6.4|^7.0|^8.0",
!                 "symfony/stopwatch": "^6.4|^7.0|^8.0",
!                 "symfony/var-dumper": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 3556,3562 ****
                  "terminal"
              ],
              "support": {
!                 "source": "https://github.com/symfony/console/tree/v6.4.25"
              },
              "funding": [
                  {
--- 3423,3429 ----
                  "terminal"
              ],
              "support": {
!                 "source": "https://github.com/symfony/console/tree/v7.4.7"
              },
              "funding": [
                  {
***************
*** 3576,3619 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-22T10:21:53+00:00"
          },
          {
              "name": "symfony/dependency-injection",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/dependency-injection.git",
!                 "reference": "900da8a42eceeb4a13a0ec34caa7db49328daff3"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/900da8a42eceeb4a13a0ec34caa7db49328daff3",
!                 "reference": "900da8a42eceeb4a13a0ec34caa7db49328daff3",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "psr/container": "^1.1|^2.0",
                  "symfony/deprecation-contracts": "^2.5|^3",
!                 "symfony/service-contracts": "^2.5|^3.0",
!                 "symfony/var-exporter": "^6.4.20|^7.2.5"
              },
              "conflict": {
                  "ext-psr": "<1.1|>=2",
!                 "symfony/config": "<6.1",
!                 "symfony/finder": "<5.4",
!                 "symfony/proxy-manager-bridge": "<6.3",
!                 "symfony/yaml": "<5.4"
              },
              "provide": {
                  "psr/container-implementation": "1.1|2.0",
                  "symfony/service-implementation": "1.1|2.0|3.0"
              },
              "require-dev": {
!                 "symfony/config": "^6.1|^7.0",
!                 "symfony/expression-language": "^5.4|^6.0|^7.0",
!                 "symfony/yaml": "^5.4|^6.0|^7.0"
              },
              "type": "library",
              "autoload": {
--- 3443,3485 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-03-06T14:06:20+00:00"
          },
          {
              "name": "symfony/dependency-injection",
!             "version": "v7.4.7",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/dependency-injection.git",
!                 "reference": "0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db",
!                 "reference": "0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "psr/container": "^1.1|^2.0",
                  "symfony/deprecation-contracts": "^2.5|^3",
!                 "symfony/service-contracts": "^3.6",
!                 "symfony/var-exporter": "^6.4.20|^7.2.5|^8.0"
              },
              "conflict": {
                  "ext-psr": "<1.1|>=2",
!                 "symfony/config": "<6.4",
!                 "symfony/finder": "<6.4",
!                 "symfony/yaml": "<6.4"
              },
              "provide": {
                  "psr/container-implementation": "1.1|2.0",
                  "symfony/service-implementation": "1.1|2.0|3.0"
              },
              "require-dev": {
!                 "symfony/config": "^6.4|^7.0|^8.0",
!                 "symfony/expression-language": "^6.4|^7.0|^8.0",
!                 "symfony/yaml": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 3641,3647 ****
              "description": "Allows you to standardize and centralize the way objects are constructed in your application",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/dependency-injection/tree/v6.4.25"
              },
              "funding": [
                  {
--- 3507,3513 ----
              "description": "Allows you to standardize and centralize the way objects are constructed in your application",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/dependency-injection/tree/v7.4.7"
              },
              "funding": [
                  {
***************
*** 3661,3667 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-13T09:41:44+00:00"
          },
          {
              "name": "symfony/deprecation-contracts",
--- 3527,3533 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-03-03T07:48:48+00:00"
          },
          {
              "name": "symfony/deprecation-contracts",
***************
*** 3732,3762 ****
          },
          {
              "name": "symfony/error-handler",
!             "version": "v6.4.24",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/error-handler.git",
!                 "reference": "30fd0b3cf0e972e82636038ce4db0e4fe777112c"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/error-handler/zipball/30fd0b3cf0e972e82636038ce4db0e4fe777112c",
!                 "reference": "30fd0b3cf0e972e82636038ce4db0e4fe777112c",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "psr/log": "^1|^2|^3",
!                 "symfony/var-dumper": "^5.4|^6.0|^7.0"
              },
              "conflict": {
                  "symfony/deprecation-contracts": "<2.5",
                  "symfony/http-kernel": "<6.4"
              },
              "require-dev": {
                  "symfony/deprecation-contracts": "^2.5|^3",
!                 "symfony/http-kernel": "^6.4|^7.0",
!                 "symfony/serializer": "^5.4|^6.0|^7.0"
              },
              "bin": [
                  "Resources/bin/patch-type-declarations"
--- 3598,3631 ----
          },
          {
              "name": "symfony/error-handler",
!             "version": "v7.4.4",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/error-handler.git",
!                 "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/error-handler/zipball/8da531f364ddfee53e36092a7eebbbd0b775f6b8",
!                 "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "psr/log": "^1|^2|^3",
!                 "symfony/polyfill-php85": "^1.32",
!                 "symfony/var-dumper": "^6.4|^7.0|^8.0"
              },
              "conflict": {
                  "symfony/deprecation-contracts": "<2.5",
                  "symfony/http-kernel": "<6.4"
              },
              "require-dev": {
+                 "symfony/console": "^6.4|^7.0|^8.0",
                  "symfony/deprecation-contracts": "^2.5|^3",
!                 "symfony/http-kernel": "^6.4|^7.0|^8.0",
!                 "symfony/serializer": "^6.4|^7.0|^8.0",
!                 "symfony/webpack-encore-bundle": "^1.0|^2.0"
              },
              "bin": [
                  "Resources/bin/patch-type-declarations"
***************
*** 3787,3793 ****
              "description": "Provides tools to manage errors and ease debugging PHP code",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/error-handler/tree/v6.4.24"
              },
              "funding": [
                  {
--- 3656,3662 ----
              "description": "Provides tools to manage errors and ease debugging PHP code",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/error-handler/tree/v7.4.4"
              },
              "funding": [
                  {
***************
*** 3807,3834 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-07-24T08:25:04+00:00"
          },
          {
              "name": "symfony/event-dispatcher",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/event-dispatcher.git",
!                 "reference": "b0cf3162020603587363f0551cd3be43958611ff"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b0cf3162020603587363f0551cd3be43958611ff",
!                 "reference": "b0cf3162020603587363f0551cd3be43958611ff",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "symfony/event-dispatcher-contracts": "^2.5|^3"
              },
              "conflict": {
!                 "symfony/dependency-injection": "<5.4",
                  "symfony/service-contracts": "<2.5"
              },
              "provide": {
--- 3676,3703 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-01-20T16:42:42+00:00"
          },
          {
              "name": "symfony/event-dispatcher",
!             "version": "v7.4.4",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/event-dispatcher.git",
!                 "reference": "dc2c0eba1af673e736bb851d747d266108aea746"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746",
!                 "reference": "dc2c0eba1af673e736bb851d747d266108aea746",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "symfony/event-dispatcher-contracts": "^2.5|^3"
              },
              "conflict": {
!                 "symfony/dependency-injection": "<6.4",
                  "symfony/service-contracts": "<2.5"
              },
              "provide": {
***************
*** 3837,3849 ****
              },
              "require-dev": {
                  "psr/log": "^1|^2|^3",
!                 "symfony/config": "^5.4|^6.0|^7.0",
!                 "symfony/dependency-injection": "^5.4|^6.0|^7.0",
!                 "symfony/error-handler": "^5.4|^6.0|^7.0",
!                 "symfony/expression-language": "^5.4|^6.0|^7.0",
!                 "symfony/http-foundation": "^5.4|^6.0|^7.0",
                  "symfony/service-contracts": "^2.5|^3",
!                 "symfony/stopwatch": "^5.4|^6.0|^7.0"
              },
              "type": "library",
              "autoload": {
--- 3706,3719 ----
              },
              "require-dev": {
                  "psr/log": "^1|^2|^3",
!                 "symfony/config": "^6.4|^7.0|^8.0",
!                 "symfony/dependency-injection": "^6.4|^7.0|^8.0",
!                 "symfony/error-handler": "^6.4|^7.0|^8.0",
!                 "symfony/expression-language": "^6.4|^7.0|^8.0",
!                 "symfony/framework-bundle": "^6.4|^7.0|^8.0",
!                 "symfony/http-foundation": "^6.4|^7.0|^8.0",
                  "symfony/service-contracts": "^2.5|^3",
!                 "symfony/stopwatch": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 3871,3877 ****
              "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.25"
              },
              "funding": [
                  {
--- 3741,3747 ----
              "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.4"
              },
              "funding": [
                  {
***************
*** 3891,3897 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-13T09:41:44+00:00"
          },
          {
              "name": "symfony/event-dispatcher-contracts",
--- 3761,3767 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-01-05T11:45:34+00:00"
          },
          {
              "name": "symfony/event-dispatcher-contracts",
***************
*** 3971,3995 ****
          },
          {
              "name": "symfony/filesystem",
!             "version": "v6.4.24",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/filesystem.git",
!                 "reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/filesystem/zipball/75ae2edb7cdcc0c53766c30b0a2512b8df574bd8",
!                 "reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "symfony/polyfill-ctype": "~1.8",
                  "symfony/polyfill-mbstring": "~1.8"
              },
              "require-dev": {
!                 "symfony/process": "^5.4|^6.4|^7.0"
              },
              "type": "library",
              "autoload": {
--- 3841,3865 ----
          },
          {
              "name": "symfony/filesystem",
!             "version": "v7.4.6",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/filesystem.git",
!                 "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/filesystem/zipball/3ebc794fa5315e59fd122561623c2e2e4280538e",
!                 "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "symfony/polyfill-ctype": "~1.8",
                  "symfony/polyfill-mbstring": "~1.8"
              },
              "require-dev": {
!                 "symfony/process": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 4017,4023 ****
              "description": "Provides basic utilities for the filesystem",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/filesystem/tree/v6.4.24"
              },
              "funding": [
                  {
--- 3887,3893 ----
              "description": "Provides basic utilities for the filesystem",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/filesystem/tree/v7.4.6"
              },
              "funding": [
                  {
***************
*** 4037,4063 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-07-10T08:14:14+00:00"
          },
          {
              "name": "symfony/finder",
!             "version": "v6.4.24",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/finder.git",
!                 "reference": "73089124388c8510efb8d2d1689285d285937b08"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/finder/zipball/73089124388c8510efb8d2d1689285d285937b08",
!                 "reference": "73089124388c8510efb8d2d1689285d285937b08",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1"
              },
              "require-dev": {
!                 "symfony/filesystem": "^6.0|^7.0"
              },
              "type": "library",
              "autoload": {
--- 3907,3933 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-02-25T16:50:00+00:00"
          },
          {
              "name": "symfony/finder",
!             "version": "v7.4.6",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/finder.git",
!                 "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf",
!                 "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2"
              },
              "require-dev": {
!                 "symfony/filesystem": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 4085,4091 ****
              "description": "Finds files and directories via an intuitive fluent interface",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/finder/tree/v6.4.24"
              },
              "funding": [
                  {
--- 3955,3961 ----
              "description": "Finds files and directories via an intuitive fluent interface",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/finder/tree/v7.4.6"
              },
              "funding": [
                  {
***************
*** 4105,4128 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-07-15T12:02:45+00:00"
          },
          {
              "name": "symfony/http-client",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/http-client.git",
!                 "reference": "b8e9dce2d8acba3c32af467bb58e0c3656886181"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/http-client/zipball/b8e9dce2d8acba3c32af467bb58e0c3656886181",
!                 "reference": "b8e9dce2d8acba3c32af467bb58e0c3656886181",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "psr/log": "^1|^2|^3",
                  "symfony/deprecation-contracts": "^2.5|^3",
                  "symfony/http-client-contracts": "~3.4.4|^3.5.2",
--- 3975,3998 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-01-29T09:40:50+00:00"
          },
          {
              "name": "symfony/http-client",
!             "version": "v7.4.7",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/http-client.git",
!                 "reference": "1010624285470eb60e88ed10035102c75b4ea6af"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/http-client/zipball/1010624285470eb60e88ed10035102c75b4ea6af",
!                 "reference": "1010624285470eb60e88ed10035102c75b4ea6af",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "psr/log": "^1|^2|^3",
                  "symfony/deprecation-contracts": "^2.5|^3",
                  "symfony/http-client-contracts": "~3.4.4|^3.5.2",
***************
*** 4130,4137 ****
                  "symfony/service-contracts": "^2.5|^3"
              },
              "conflict": {
                  "php-http/discovery": "<1.15",
!                 "symfony/http-foundation": "<6.3"
              },
              "provide": {
                  "php-http/async-client-implementation": "*",
--- 4000,4009 ----
                  "symfony/service-contracts": "^2.5|^3"
              },
              "conflict": {
+                 "amphp/amp": "<2.5",
+                 "amphp/socket": "<1.1",
                  "php-http/discovery": "<1.15",
!                 "symfony/http-foundation": "<6.4"
              },
              "provide": {
                  "php-http/async-client-implementation": "*",
***************
*** 4140,4158 ****
                  "symfony/http-client-implementation": "3.0"
              },
              "require-dev": {
!                 "amphp/amp": "^2.5",
!                 "amphp/http-client": "^4.2.1",
!                 "amphp/http-tunnel": "^1.0",
!                 "amphp/socket": "^1.1",
                  "guzzlehttp/promises": "^1.4|^2.0",
                  "nyholm/psr7": "^1.0",
                  "php-http/httplug": "^1.0|^2.0",
                  "psr/http-client": "^1.0",
!                 "symfony/dependency-injection": "^5.4|^6.0|^7.0",
!                 "symfony/http-kernel": "^5.4|^6.0|^7.0",
!                 "symfony/messenger": "^5.4|^6.0|^7.0",
!                 "symfony/process": "^5.4|^6.0|^7.0",
!                 "symfony/stopwatch": "^5.4|^6.0|^7.0"
              },
              "type": "library",
              "autoload": {
--- 4012,4031 ----
                  "symfony/http-client-implementation": "3.0"
              },
              "require-dev": {
!                 "amphp/http-client": "^4.2.1|^5.0",
!                 "amphp/http-tunnel": "^1.0|^2.0",
                  "guzzlehttp/promises": "^1.4|^2.0",
                  "nyholm/psr7": "^1.0",
                  "php-http/httplug": "^1.0|^2.0",
                  "psr/http-client": "^1.0",
!                 "symfony/amphp-http-client-meta": "^1.0|^2.0",
!                 "symfony/cache": "^6.4|^7.0|^8.0",
!                 "symfony/dependency-injection": "^6.4|^7.0|^8.0",
!                 "symfony/http-kernel": "^6.4|^7.0|^8.0",
!                 "symfony/messenger": "^6.4|^7.0|^8.0",
!                 "symfony/process": "^6.4|^7.0|^8.0",
!                 "symfony/rate-limiter": "^6.4|^7.0|^8.0",
!                 "symfony/stopwatch": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 4183,4189 ****
                  "http"
              ],
              "support": {
!                 "source": "https://github.com/symfony/http-client/tree/v6.4.25"
              },
              "funding": [
                  {
--- 4056,4062 ----
                  "http"
              ],
              "support": {
!                 "source": "https://github.com/symfony/http-client/tree/v7.4.7"
              },
              "funding": [
                  {
***************
*** 4203,4209 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-27T07:01:16+00:00"
          },
          {
              "name": "symfony/http-client-contracts",
--- 4076,4082 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-03-05T11:16:58+00:00"
          },
          {
              "name": "symfony/http-client-contracts",
***************
*** 4285,4320 ****
          },
          {
              "name": "symfony/http-foundation",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/http-foundation.git",
!                 "reference": "6bc974c0035b643aa497c58d46d9e25185e4b272"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6bc974c0035b643aa497c58d46d9e25185e4b272",
!                 "reference": "6bc974c0035b643aa497c58d46d9e25185e4b272",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "symfony/deprecation-contracts": "^2.5|^3",
!                 "symfony/polyfill-mbstring": "~1.1",
!                 "symfony/polyfill-php83": "^1.27"
              },
              "conflict": {
                  "symfony/cache": "<6.4.12|>=7.0,<7.1.5"
              },
              "require-dev": {
!                 "doctrine/dbal": "^2.13.1|^3|^4",
                  "predis/predis": "^1.1|^2.0",
!                 "symfony/cache": "^6.4.12|^7.1.5",
!                 "symfony/dependency-injection": "^5.4|^6.0|^7.0",
!                 "symfony/expression-language": "^5.4|^6.0|^7.0",
!                 "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0",
!                 "symfony/mime": "^5.4|^6.0|^7.0",
!                 "symfony/rate-limiter": "^5.4|^6.0|^7.0"
              },
              "type": "library",
              "autoload": {
--- 4158,4194 ----
          },
          {
              "name": "symfony/http-foundation",
!             "version": "v7.4.7",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/http-foundation.git",
!                 "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f94b3e7b7dafd40e666f0c9ff2084133bae41e81",
!                 "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "symfony/deprecation-contracts": "^2.5|^3",
!                 "symfony/polyfill-mbstring": "^1.1"
              },
              "conflict": {
+                 "doctrine/dbal": "<3.6",
                  "symfony/cache": "<6.4.12|>=7.0,<7.1.5"
              },
              "require-dev": {
!                 "doctrine/dbal": "^3.6|^4",
                  "predis/predis": "^1.1|^2.0",
!                 "symfony/cache": "^6.4.12|^7.1.5|^8.0",
!                 "symfony/clock": "^6.4|^7.0|^8.0",
!                 "symfony/dependency-injection": "^6.4|^7.0|^8.0",
!                 "symfony/expression-language": "^6.4|^7.0|^8.0",
!                 "symfony/http-kernel": "^6.4|^7.0|^8.0",
!                 "symfony/mime": "^6.4|^7.0|^8.0",
!                 "symfony/rate-limiter": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 4342,4348 ****
              "description": "Defines an object-oriented layer for the HTTP specification",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/http-foundation/tree/v6.4.25"
              },
              "funding": [
                  {
--- 4216,4222 ----
              "description": "Defines an object-oriented layer for the HTTP specification",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/http-foundation/tree/v7.4.7"
              },
              "funding": [
                  {
***************
*** 4362,4438 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-20T06:48:20+00:00"
          },
          {
              "name": "symfony/http-kernel",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/http-kernel.git",
!                 "reference": "a0ee3cea5cabf4ed960fd2ef57668ceeacdb6e15"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/http-kernel/zipball/a0ee3cea5cabf4ed960fd2ef57668ceeacdb6e15",
!                 "reference": "a0ee3cea5cabf4ed960fd2ef57668ceeacdb6e15",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "psr/log": "^1|^2|^3",
                  "symfony/deprecation-contracts": "^2.5|^3",
!                 "symfony/error-handler": "^6.4|^7.0",
!                 "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
!                 "symfony/http-foundation": "^6.4|^7.0",
                  "symfony/polyfill-ctype": "^1.8"
              },
              "conflict": {
!                 "symfony/browser-kit": "<5.4",
!                 "symfony/cache": "<5.4",
!                 "symfony/config": "<6.1",
!                 "symfony/console": "<5.4",
                  "symfony/dependency-injection": "<6.4",
!                 "symfony/doctrine-bridge": "<5.4",
!                 "symfony/form": "<5.4",
!                 "symfony/http-client": "<5.4",
                  "symfony/http-client-contracts": "<2.5",
!                 "symfony/mailer": "<5.4",
!                 "symfony/messenger": "<5.4",
!                 "symfony/translation": "<5.4",
                  "symfony/translation-contracts": "<2.5",
!                 "symfony/twig-bridge": "<5.4",
                  "symfony/validator": "<6.4",
!                 "symfony/var-dumper": "<6.3",
!                 "twig/twig": "<2.13"
              },
              "provide": {
                  "psr/log-implementation": "1.0|2.0|3.0"
              },
              "require-dev": {
                  "psr/cache": "^1.0|^2.0|^3.0",
!                 "symfony/browser-kit": "^5.4|^6.0|^7.0",
!                 "symfony/clock": "^6.2|^7.0",
!                 "symfony/config": "^6.1|^7.0",
!                 "symfony/console": "^5.4|^6.0|^7.0",
!                 "symfony/css-selector": "^5.4|^6.0|^7.0",
!                 "symfony/dependency-injection": "^6.4|^7.0",
!                 "symfony/dom-crawler": "^5.4|^6.0|^7.0",
!                 "symfony/expression-language": "^5.4|^6.0|^7.0",
!                 "symfony/finder": "^5.4|^6.0|^7.0",
                  "symfony/http-client-contracts": "^2.5|^3",
!                 "symfony/process": "^5.4|^6.0|^7.0",
!                 "symfony/property-access": "^5.4.5|^6.0.5|^7.0",
!                 "symfony/routing": "^5.4|^6.0|^7.0",
!                 "symfony/serializer": "^6.4.4|^7.0.4",
!                 "symfony/stopwatch": "^5.4|^6.0|^7.0",
!                 "symfony/translation": "^5.4|^6.0|^7.0",
                  "symfony/translation-contracts": "^2.5|^3",
!                 "symfony/uid": "^5.4|^6.0|^7.0",
!                 "symfony/validator": "^6.4|^7.0",
!                 "symfony/var-dumper": "^5.4|^6.4|^7.0",
!                 "symfony/var-exporter": "^6.2|^7.0",
!                 "twig/twig": "^2.13|^3.0.4"
              },
              "type": "library",
              "autoload": {
--- 4236,4313 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-03-06T13:15:18+00:00"
          },
          {
              "name": "symfony/http-kernel",
!             "version": "v7.4.7",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/http-kernel.git",
!                 "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3b3fcf386c809be990c922e10e4c620d6367cab1",
!                 "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "psr/log": "^1|^2|^3",
                  "symfony/deprecation-contracts": "^2.5|^3",
!                 "symfony/error-handler": "^6.4|^7.0|^8.0",
!                 "symfony/event-dispatcher": "^7.3|^8.0",
!                 "symfony/http-foundation": "^7.4|^8.0",
                  "symfony/polyfill-ctype": "^1.8"
              },
              "conflict": {
!                 "symfony/browser-kit": "<6.4",
!                 "symfony/cache": "<6.4",
!                 "symfony/config": "<6.4",
!                 "symfony/console": "<6.4",
                  "symfony/dependency-injection": "<6.4",
!                 "symfony/doctrine-bridge": "<6.4",
!                 "symfony/flex": "<2.10",
!                 "symfony/form": "<6.4",
!                 "symfony/http-client": "<6.4",
                  "symfony/http-client-contracts": "<2.5",
!                 "symfony/mailer": "<6.4",
!                 "symfony/messenger": "<6.4",
!                 "symfony/translation": "<6.4",
                  "symfony/translation-contracts": "<2.5",
!                 "symfony/twig-bridge": "<6.4",
                  "symfony/validator": "<6.4",
!                 "symfony/var-dumper": "<6.4",
!                 "twig/twig": "<3.12"
              },
              "provide": {
                  "psr/log-implementation": "1.0|2.0|3.0"
              },
              "require-dev": {
                  "psr/cache": "^1.0|^2.0|^3.0",
!                 "symfony/browser-kit": "^6.4|^7.0|^8.0",
!                 "symfony/clock": "^6.4|^7.0|^8.0",
!                 "symfony/config": "^6.4|^7.0|^8.0",
!                 "symfony/console": "^6.4|^7.0|^8.0",
!                 "symfony/css-selector": "^6.4|^7.0|^8.0",
!                 "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0",
!                 "symfony/dom-crawler": "^6.4|^7.0|^8.0",
!                 "symfony/expression-language": "^6.4|^7.0|^8.0",
!                 "symfony/finder": "^6.4|^7.0|^8.0",
                  "symfony/http-client-contracts": "^2.5|^3",
!                 "symfony/process": "^6.4|^7.0|^8.0",
!                 "symfony/property-access": "^7.1|^8.0",
!                 "symfony/routing": "^6.4|^7.0|^8.0",
!                 "symfony/serializer": "^7.1|^8.0",
!                 "symfony/stopwatch": "^6.4|^7.0|^8.0",
!                 "symfony/translation": "^6.4|^7.0|^8.0",
                  "symfony/translation-contracts": "^2.5|^3",
!                 "symfony/uid": "^6.4|^7.0|^8.0",
!                 "symfony/validator": "^6.4|^7.0|^8.0",
!                 "symfony/var-dumper": "^6.4|^7.0|^8.0",
!                 "symfony/var-exporter": "^6.4|^7.0|^8.0",
!                 "twig/twig": "^3.12"
              },
              "type": "library",
              "autoload": {
***************
*** 4460,4466 ****
              "description": "Provides a structured process for converting a Request into a Response",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/http-kernel/tree/v6.4.25"
              },
              "funding": [
                  {
--- 4335,4341 ----
              "description": "Provides a structured process for converting a Request into a Response",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/http-kernel/tree/v7.4.7"
              },
              "funding": [
                  {
***************
*** 4480,4522 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-29T07:55:45+00:00"
          },
          {
              "name": "symfony/mailer",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/mailer.git",
!                 "reference": "628b43b45a3e6b15c8a633fb22df547ed9b492a2"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/mailer/zipball/628b43b45a3e6b15c8a633fb22df547ed9b492a2",
!                 "reference": "628b43b45a3e6b15c8a633fb22df547ed9b492a2",
                  "shasum": ""
              },
              "require": {
                  "egulias/email-validator": "^2.1.10|^3|^4",
!                 "php": ">=8.1",
                  "psr/event-dispatcher": "^1",
                  "psr/log": "^1|^2|^3",
!                 "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
!                 "symfony/mime": "^6.2|^7.0",
                  "symfony/service-contracts": "^2.5|^3"
              },
              "conflict": {
                  "symfony/http-client-contracts": "<2.5",
!                 "symfony/http-kernel": "<5.4",
!                 "symfony/messenger": "<6.2",
!                 "symfony/mime": "<6.2",
!                 "symfony/twig-bridge": "<6.2.1"
              },
              "require-dev": {
!                 "symfony/console": "^5.4|^6.0|^7.0",
!                 "symfony/http-client": "^5.4|^6.0|^7.0",
!                 "symfony/messenger": "^6.2|^7.0",
!                 "symfony/twig-bridge": "^6.2|^7.0"
              },
              "type": "library",
              "autoload": {
--- 4355,4397 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-03-06T16:33:18+00:00"
          },
          {
              "name": "symfony/mailer",
!             "version": "v7.4.6",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/mailer.git",
!                 "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/mailer/zipball/b02726f39a20bc65e30364f5c750c4ddbf1f58e9",
!                 "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9",
                  "shasum": ""
              },
              "require": {
                  "egulias/email-validator": "^2.1.10|^3|^4",
!                 "php": ">=8.2",
                  "psr/event-dispatcher": "^1",
                  "psr/log": "^1|^2|^3",
!                 "symfony/event-dispatcher": "^6.4|^7.0|^8.0",
!                 "symfony/mime": "^7.2|^8.0",
                  "symfony/service-contracts": "^2.5|^3"
              },
              "conflict": {
                  "symfony/http-client-contracts": "<2.5",
!                 "symfony/http-kernel": "<6.4",
!                 "symfony/messenger": "<6.4",
!                 "symfony/mime": "<6.4",
!                 "symfony/twig-bridge": "<6.4"
              },
              "require-dev": {
!                 "symfony/console": "^6.4|^7.0|^8.0",
!                 "symfony/http-client": "^6.4|^7.0|^8.0",
!                 "symfony/messenger": "^6.4|^7.0|^8.0",
!                 "symfony/twig-bridge": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 4544,4550 ****
              "description": "Helps sending emails",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/mailer/tree/v6.4.25"
              },
              "funding": [
                  {
--- 4419,4425 ----
              "description": "Helps sending emails",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/mailer/tree/v7.4.6"
              },
              "funding": [
                  {
***************
*** 4564,4607 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-13T09:41:44+00:00"
          },
          {
              "name": "symfony/mime",
!             "version": "v6.4.24",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/mime.git",
!                 "reference": "664d5e844a2de5e11c8255d0aef6bc15a9660ac7"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/mime/zipball/664d5e844a2de5e11c8255d0aef6bc15a9660ac7",
!                 "reference": "664d5e844a2de5e11c8255d0aef6bc15a9660ac7",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "symfony/deprecation-contracts": "^2.5|^3",
                  "symfony/polyfill-intl-idn": "^1.10",
                  "symfony/polyfill-mbstring": "^1.0"
              },
              "conflict": {
                  "egulias/email-validator": "~3.0.0",
!                 "phpdocumentor/reflection-docblock": "<3.2.2",
!                 "phpdocumentor/type-resolver": "<1.4.0",
!                 "symfony/mailer": "<5.4",
                  "symfony/serializer": "<6.4.3|>7.0,<7.0.3"
              },
              "require-dev": {
                  "egulias/email-validator": "^2.1.10|^3.1|^4",
                  "league/html-to-markdown": "^5.0",
!                 "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
!                 "symfony/dependency-injection": "^5.4|^6.0|^7.0",
!                 "symfony/process": "^5.4|^6.4|^7.0",
!                 "symfony/property-access": "^5.4|^6.0|^7.0",
!                 "symfony/property-info": "^5.4|^6.0|^7.0",
!                 "symfony/serializer": "^6.4.3|^7.0.3"
              },
              "type": "library",
              "autoload": {
--- 4439,4482 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-02-25T16:50:00+00:00"
          },
          {
              "name": "symfony/mime",
!             "version": "v7.4.7",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/mime.git",
!                 "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/mime/zipball/da5ab4fde3f6c88ab06e96185b9922f48b677cd1",
!                 "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "symfony/deprecation-contracts": "^2.5|^3",
                  "symfony/polyfill-intl-idn": "^1.10",
                  "symfony/polyfill-mbstring": "^1.0"
              },
              "conflict": {
                  "egulias/email-validator": "~3.0.0",
!                 "phpdocumentor/reflection-docblock": "<5.2|>=7",
!                 "phpdocumentor/type-resolver": "<1.5.1",
!                 "symfony/mailer": "<6.4",
                  "symfony/serializer": "<6.4.3|>7.0,<7.0.3"
              },
              "require-dev": {
                  "egulias/email-validator": "^2.1.10|^3.1|^4",
                  "league/html-to-markdown": "^5.0",
!                 "phpdocumentor/reflection-docblock": "^5.2|^6.0",
!                 "symfony/dependency-injection": "^6.4|^7.0|^8.0",
!                 "symfony/process": "^6.4|^7.0|^8.0",
!                 "symfony/property-access": "^6.4|^7.0|^8.0",
!                 "symfony/property-info": "^6.4|^7.0|^8.0",
!                 "symfony/serializer": "^6.4.3|^7.0.3|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 4633,4639 ****
                  "mime-type"
              ],
              "support": {
!                 "source": "https://github.com/symfony/mime/tree/v6.4.24"
              },
              "funding": [
                  {
--- 4508,4514 ----
                  "mime-type"
              ],
              "support": {
!                 "source": "https://github.com/symfony/mime/tree/v7.4.7"
              },
              "funding": [
                  {
***************
*** 4653,4659 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-07-15T12:02:45+00:00"
          },
          {
              "name": "symfony/polyfill-ctype",
--- 4528,4534 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-03-05T15:24:09+00:00"
          },
          {
              "name": "symfony/polyfill-ctype",
***************
*** 5402,5430 ****
              "time": "2025-07-08T02:45:35+00:00"
          },
          {
!             "name": "symfony/process",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
!                 "url": "https://github.com/symfony/process.git",
!                 "reference": "6be2f0c9ab3428587c07bed03aa9e3d1b823c6c8"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/process/zipball/6be2f0c9ab3428587c07bed03aa9e3d1b823c6c8",
!                 "reference": "6be2f0c9ab3428587c07bed03aa9e3d1b823c6c8",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1"
              },
              "type": "library",
              "autoload": {
                  "psr-4": {
!                     "Symfony\\Component\\Process\\": ""
                  },
!                 "exclude-from-classmap": [
!                     "/Tests/"
                  ]
              },
              "notification-url": "https://packagist.org/downloads/",
--- 5277,5314 ----
              "time": "2025-07-08T02:45:35+00:00"
          },
          {
!             "name": "symfony/polyfill-php84",
!             "version": "v1.33.0",
              "source": {
                  "type": "git",
!                 "url": "https://github.com/symfony/polyfill-php84.git",
!                 "reference": "d8ced4d875142b6a7426000426b8abc631d6b191"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191",
!                 "reference": "d8ced4d875142b6a7426000426b8abc631d6b191",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=7.2"
              },
              "type": "library",
+             "extra": {
+                 "thanks": {
+                     "url": "https://github.com/symfony/polyfill",
+                     "name": "symfony/polyfill"
+                 }
+             },
              "autoload": {
+                 "files": [
+                     "bootstrap.php"
+                 ],
                  "psr-4": {
!                     "Symfony\\Polyfill\\Php84\\": ""
                  },
!                 "classmap": [
!                     "Resources/stubs"
                  ]
              },
              "notification-url": "https://packagist.org/downloads/",
***************
*** 5433,5450 ****
              ],
              "authors": [
                  {
!                     "name": "Fabien Potencier",
!                     "email": "fabien@symfony.com"
                  },
                  {
                      "name": "Symfony Community",
                      "homepage": "https://symfony.com/contributors"
                  }
              ],
!             "description": "Executes commands in sub-processes",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/process/tree/v6.4.25"
              },
              "funding": [
                  {
--- 5317,5340 ----
              ],
              "authors": [
                  {
!                     "name": "Nicolas Grekas",
!                     "email": "p@tchwork.com"
                  },
                  {
                      "name": "Symfony Community",
                      "homepage": "https://symfony.com/contributors"
                  }
              ],
!             "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions",
              "homepage": "https://symfony.com",
+             "keywords": [
+                 "compatibility",
+                 "polyfill",
+                 "portable",
+                 "shim"
+             ],
              "support": {
!                 "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0"
              },
              "funding": [
                  {
***************
*** 5464,5498 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-14T06:23:17+00:00"
          },
          {
!             "name": "symfony/proxy-manager-bridge",
!             "version": "v6.4.24",
              "source": {
                  "type": "git",
!                 "url": "https://github.com/symfony/proxy-manager-bridge.git",
!                 "reference": "2a14a1539f2854a8adb73319abf8923b1d7a6589"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/2a14a1539f2854a8adb73319abf8923b1d7a6589",
!                 "reference": "2a14a1539f2854a8adb73319abf8923b1d7a6589",
                  "shasum": ""
              },
              "require": {
!                 "friendsofphp/proxy-manager-lts": "^1.0.2",
!                 "php": ">=8.1",
!                 "symfony/dependency-injection": "^6.3|^7.0",
!                 "symfony/deprecation-contracts": "^2.5|^3"
              },
!             "require-dev": {
!                 "symfony/config": "^6.1|^7.0"
              },
!             "type": "symfony-bridge",
              "autoload": {
                  "psr-4": {
!                     "Symfony\\Bridge\\ProxyManager\\": ""
                  },
                  "exclude-from-classmap": [
                      "/Tests/"
--- 5354,5462 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-06-24T13:30:11+00:00"
          },
          {
!             "name": "symfony/polyfill-php85",
!             "version": "v1.33.0",
              "source": {
                  "type": "git",
!                 "url": "https://github.com/symfony/polyfill-php85.git",
!                 "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91",
!                 "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=7.2"
              },
!             "type": "library",
!             "extra": {
!                 "thanks": {
!                     "url": "https://github.com/symfony/polyfill",
!                     "name": "symfony/polyfill"
!                 }
              },
!             "autoload": {
!                 "files": [
!                     "bootstrap.php"
!                 ],
!                 "psr-4": {
!                     "Symfony\\Polyfill\\Php85\\": ""
!                 },
!                 "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.5+ features to lower PHP versions",
!             "homepage": "https://symfony.com",
!             "keywords": [
!                 "compatibility",
!                 "polyfill",
!                 "portable",
!                 "shim"
!             ],
!             "support": {
!                 "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0"
!             },
!             "funding": [
!                 {
!                     "url": "https://symfony.com/sponsor",
!                     "type": "custom"
!                 },
!                 {
!                     "url": "https://github.com/fabpot",
!                     "type": "github"
!                 },
!                 {
!                     "url": "https://github.com/nicolas-grekas",
!                     "type": "github"
!                 },
!                 {
!                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
!                     "type": "tidelift"
!                 }
!             ],
!             "time": "2025-06-23T16:12:55+00:00"
!         },
!         {
!             "name": "symfony/process",
!             "version": "v7.4.5",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/symfony/process.git",
!                 "reference": "608476f4604102976d687c483ac63a79ba18cc97"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97",
!                 "reference": "608476f4604102976d687c483ac63a79ba18cc97",
!                 "shasum": ""
!             },
!             "require": {
!                 "php": ">=8.2"
!             },
!             "type": "library",
              "autoload": {
                  "psr-4": {
!                     "Symfony\\Component\\Process\\": ""
                  },
                  "exclude-from-classmap": [
                      "/Tests/"
***************
*** 5512,5521 ****
                      "homepage": "https://symfony.com/contributors"
                  }
              ],
!             "description": "Provides integration for ProxyManager with various Symfony components",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/proxy-manager-bridge/tree/v6.4.24"
              },
              "funding": [
                  {
--- 5476,5485 ----
                      "homepage": "https://symfony.com/contributors"
                  }
              ],
!             "description": "Executes commands in sub-processes",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/process/tree/v7.4.5"
              },
              "funding": [
                  {
***************
*** 5535,5574 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-07-14T16:38:25+00:00"
          },
          {
              "name": "symfony/routing",
!             "version": "v6.4.24",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/routing.git",
!                 "reference": "e4f94e625c8e6f910aa004a0042f7b2d398278f5"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/routing/zipball/e4f94e625c8e6f910aa004a0042f7b2d398278f5",
!                 "reference": "e4f94e625c8e6f910aa004a0042f7b2d398278f5",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "symfony/deprecation-contracts": "^2.5|^3"
              },
              "conflict": {
!                 "doctrine/annotations": "<1.12",
!                 "symfony/config": "<6.2",
!                 "symfony/dependency-injection": "<5.4",
!                 "symfony/yaml": "<5.4"
              },
              "require-dev": {
-                 "doctrine/annotations": "^1.12|^2",
                  "psr/log": "^1|^2|^3",
!                 "symfony/config": "^6.2|^7.0",
!                 "symfony/dependency-injection": "^5.4|^6.0|^7.0",
!                 "symfony/expression-language": "^5.4|^6.0|^7.0",
!                 "symfony/http-foundation": "^5.4|^6.0|^7.0",
!                 "symfony/yaml": "^5.4|^6.0|^7.0"
              },
              "type": "library",
              "autoload": {
--- 5499,5536 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-01-26T15:07:59+00:00"
          },
          {
              "name": "symfony/routing",
!             "version": "v7.4.6",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/routing.git",
!                 "reference": "238d749c56b804b31a9bf3e26519d93b65a60938"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/routing/zipball/238d749c56b804b31a9bf3e26519d93b65a60938",
!                 "reference": "238d749c56b804b31a9bf3e26519d93b65a60938",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "symfony/deprecation-contracts": "^2.5|^3"
              },
              "conflict": {
!                 "symfony/config": "<6.4",
!                 "symfony/dependency-injection": "<6.4",
!                 "symfony/yaml": "<6.4"
              },
              "require-dev": {
                  "psr/log": "^1|^2|^3",
!                 "symfony/config": "^6.4|^7.0|^8.0",
!                 "symfony/dependency-injection": "^6.4|^7.0|^8.0",
!                 "symfony/expression-language": "^6.4|^7.0|^8.0",
!                 "symfony/http-foundation": "^6.4|^7.0|^8.0",
!                 "symfony/yaml": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 5602,5608 ****
                  "url"
              ],
              "support": {
!                 "source": "https://github.com/symfony/routing/tree/v6.4.24"
              },
              "funding": [
                  {
--- 5564,5570 ----
                  "url"
              ],
              "support": {
!                 "source": "https://github.com/symfony/routing/tree/v7.4.6"
              },
              "funding": [
                  {
***************
*** 5622,5641 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-07-15T08:46:37+00:00"
          },
          {
              "name": "symfony/service-contracts",
!             "version": "v3.6.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/service-contracts.git",
!                 "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
!                 "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
                  "shasum": ""
              },
              "require": {
--- 5584,5603 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-02-25T16:50:00+00:00"
          },
          {
              "name": "symfony/service-contracts",
!             "version": "v3.6.1",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/service-contracts.git",
!                 "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43",
!                 "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43",
                  "shasum": ""
              },
              "require": {
***************
*** 5689,5695 ****
                  "standards"
              ],
              "support": {
!                 "source": "https://github.com/symfony/service-contracts/tree/v3.6.0"
              },
              "funding": [
                  {
--- 5651,5657 ----
                  "standards"
              ],
              "support": {
!                 "source": "https://github.com/symfony/service-contracts/tree/v3.6.1"
              },
              "funding": [
                  {
***************
*** 5701,5730 ****
                      "type": "github"
                  },
                  {
                      "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-04-25T09:37:31+00:00"
          },
          {
              "name": "symfony/string",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/string.git",
!                 "reference": "7cdec7edfaf2cdd9c18901e35bcf9653d6209ff1"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/string/zipball/7cdec7edfaf2cdd9c18901e35bcf9653d6209ff1",
!                 "reference": "7cdec7edfaf2cdd9c18901e35bcf9653d6209ff1",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "symfony/polyfill-ctype": "~1.8",
!                 "symfony/polyfill-intl-grapheme": "~1.0",
                  "symfony/polyfill-intl-normalizer": "~1.0",
                  "symfony/polyfill-mbstring": "~1.0"
              },
--- 5663,5697 ----
                      "type": "github"
                  },
                  {
+                     "url": "https://github.com/nicolas-grekas",
+                     "type": "github"
+                 },
+                 {
                      "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-07-15T11:30:57+00:00"
          },
          {
              "name": "symfony/string",
!             "version": "v7.4.6",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/string.git",
!                 "reference": "9f209231affa85aa930a5e46e6eb03381424b30b"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b",
!                 "reference": "9f209231affa85aa930a5e46e6eb03381424b30b",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
!                 "symfony/deprecation-contracts": "^2.5|^3.0",
                  "symfony/polyfill-ctype": "~1.8",
!                 "symfony/polyfill-intl-grapheme": "~1.33",
                  "symfony/polyfill-intl-normalizer": "~1.0",
                  "symfony/polyfill-mbstring": "~1.0"
              },
***************
*** 5732,5742 ****
                  "symfony/translation-contracts": "<2.5"
              },
              "require-dev": {
!                 "symfony/error-handler": "^5.4|^6.0|^7.0",
!                 "symfony/http-client": "^5.4|^6.0|^7.0",
!                 "symfony/intl": "^6.2|^7.0",
                  "symfony/translation-contracts": "^2.5|^3.0",
!                 "symfony/var-exporter": "^5.4|^6.0|^7.0"
              },
              "type": "library",
              "autoload": {
--- 5699,5709 ----
                  "symfony/translation-contracts": "<2.5"
              },
              "require-dev": {
!                 "symfony/emoji": "^7.1|^8.0",
!                 "symfony/http-client": "^6.4|^7.0|^8.0",
!                 "symfony/intl": "^6.4|^7.0|^8.0",
                  "symfony/translation-contracts": "^2.5|^3.0",
!                 "symfony/var-exporter": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 5775,5781 ****
                  "utf8"
              ],
              "support": {
!                 "source": "https://github.com/symfony/string/tree/v6.4.25"
              },
              "funding": [
                  {
--- 5742,5748 ----
                  "utf8"
              ],
              "support": {
!                 "source": "https://github.com/symfony/string/tree/v7.4.6"
              },
              "funding": [
                  {
***************
*** 5795,5814 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-22T12:33:20+00:00"
          },
          {
              "name": "symfony/translation-contracts",
!             "version": "v3.6.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/translation-contracts.git",
!                 "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d",
!                 "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d",
                  "shasum": ""
              },
              "require": {
--- 5762,5781 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-02-09T09:33:46+00:00"
          },
          {
              "name": "symfony/translation-contracts",
!             "version": "v3.6.1",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/translation-contracts.git",
!                 "reference": "65a8bc82080447fae78373aa10f8d13b38338977"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977",
!                 "reference": "65a8bc82080447fae78373aa10f8d13b38338977",
                  "shasum": ""
              },
              "require": {
***************
*** 5857,5863 ****
                  "standards"
              ],
              "support": {
!                 "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0"
              },
              "funding": [
                  {
--- 5824,5830 ----
                  "standards"
              ],
              "support": {
!                 "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1"
              },
              "funding": [
                  {
***************
*** 5869,5944 ****
                      "type": "github"
                  },
                  {
                      "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
                      "type": "tidelift"
                  }
              ],
!             "time": "2024-09-27T08:32:26+00:00"
          },
          {
              "name": "symfony/twig-bridge",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/twig-bridge.git",
!                 "reference": "9d13e87591c9de3221c8d6f23cd9a2b5958607bf"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/9d13e87591c9de3221c8d6f23cd9a2b5958607bf",
!                 "reference": "9d13e87591c9de3221c8d6f23cd9a2b5958607bf",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "symfony/deprecation-contracts": "^2.5|^3",
                  "symfony/translation-contracts": "^2.5|^3",
!                 "twig/twig": "^2.13|^3.0.4"
              },
              "conflict": {
!                 "phpdocumentor/reflection-docblock": "<3.2.2",
!                 "phpdocumentor/type-resolver": "<1.4.0",
!                 "symfony/console": "<5.4",
!                 "symfony/form": "<6.3",
!                 "symfony/http-foundation": "<5.4",
                  "symfony/http-kernel": "<6.4",
!                 "symfony/mime": "<6.2",
                  "symfony/serializer": "<6.4",
!                 "symfony/translation": "<5.4",
!                 "symfony/workflow": "<5.4"
              },
              "require-dev": {
                  "egulias/email-validator": "^2.1.10|^3|^4",
                  "league/html-to-markdown": "^5.0",
!                 "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
!                 "symfony/asset": "^5.4|^6.0|^7.0",
!                 "symfony/asset-mapper": "^6.3|^7.0",
!                 "symfony/console": "^5.4|^6.0|^7.0",
!                 "symfony/dependency-injection": "^5.4|^6.0|^7.0",
!                 "symfony/expression-language": "^5.4|^6.0|^7.0",
!                 "symfony/finder": "^5.4|^6.0|^7.0",
!                 "symfony/form": "^6.4.20|^7.2.5",
!                 "symfony/html-sanitizer": "^6.1|^7.0",
!                 "symfony/http-foundation": "^5.4|^6.0|^7.0",
!                 "symfony/http-kernel": "^6.4|^7.0",
!                 "symfony/intl": "^5.4|^6.0|^7.0",
!                 "symfony/mime": "^6.2|^7.0",
                  "symfony/polyfill-intl-icu": "~1.0",
!                 "symfony/property-info": "^5.4|^6.0|^7.0",
!                 "symfony/routing": "^5.4|^6.0|^7.0",
                  "symfony/security-acl": "^2.8|^3.0",
!                 "symfony/security-core": "^5.4|^6.0|^7.0",
!                 "symfony/security-csrf": "^5.4|^6.0|^7.0",
!                 "symfony/security-http": "^5.4|^6.0|^7.0",
!                 "symfony/serializer": "^6.4.3|^7.0.3",
!                 "symfony/stopwatch": "^5.4|^6.0|^7.0",
!                 "symfony/translation": "^6.1|^7.0",
!                 "symfony/web-link": "^5.4|^6.0|^7.0",
!                 "symfony/workflow": "^5.4|^6.0|^7.0",
!                 "symfony/yaml": "^5.4|^6.0|^7.0",
!                 "twig/cssinliner-extra": "^2.12|^3",
!                 "twig/inky-extra": "^2.12|^3",
!                 "twig/markdown-extra": "^2.12|^3"
              },
              "type": "symfony-bridge",
              "autoload": {
--- 5836,5917 ----
                      "type": "github"
                  },
                  {
+                     "url": "https://github.com/nicolas-grekas",
+                     "type": "github"
+                 },
+                 {
                      "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-07-15T13:41:35+00:00"
          },
          {
              "name": "symfony/twig-bridge",
!             "version": "v7.4.7",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/twig-bridge.git",
!                 "reference": "c67219ca6b79a57b64e36bbb2cd8ba741286587e"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/c67219ca6b79a57b64e36bbb2cd8ba741286587e",
!                 "reference": "c67219ca6b79a57b64e36bbb2cd8ba741286587e",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "symfony/deprecation-contracts": "^2.5|^3",
                  "symfony/translation-contracts": "^2.5|^3",
!                 "twig/twig": "^3.21"
              },
              "conflict": {
!                 "phpdocumentor/reflection-docblock": "<5.2|>=7",
!                 "phpdocumentor/type-resolver": "<1.5.1",
!                 "symfony/console": "<6.4",
!                 "symfony/form": "<6.4.32|>7,<7.3.10|>7.4,<7.4.4|>8.0,<8.0.4",
!                 "symfony/http-foundation": "<6.4",
                  "symfony/http-kernel": "<6.4",
!                 "symfony/mime": "<6.4",
                  "symfony/serializer": "<6.4",
!                 "symfony/translation": "<6.4",
!                 "symfony/workflow": "<6.4"
              },
              "require-dev": {
                  "egulias/email-validator": "^2.1.10|^3|^4",
                  "league/html-to-markdown": "^5.0",
!                 "phpdocumentor/reflection-docblock": "^5.2|^6.0",
!                 "symfony/asset": "^6.4|^7.0|^8.0",
!                 "symfony/asset-mapper": "^6.4|^7.0|^8.0",
!                 "symfony/console": "^6.4|^7.0|^8.0",
!                 "symfony/dependency-injection": "^6.4|^7.0|^8.0",
!                 "symfony/emoji": "^7.1|^8.0",
!                 "symfony/expression-language": "^6.4|^7.0|^8.0",
!                 "symfony/finder": "^6.4|^7.0|^8.0",
!                 "symfony/form": "^6.4.32|~7.3.10|^7.4.4|^8.0.4",
!                 "symfony/html-sanitizer": "^6.4|^7.0|^8.0",
!                 "symfony/http-foundation": "^7.3|^8.0",
!                 "symfony/http-kernel": "^6.4|^7.0|^8.0",
!                 "symfony/intl": "^6.4|^7.0|^8.0",
!                 "symfony/mime": "^6.4|^7.0|^8.0",
                  "symfony/polyfill-intl-icu": "~1.0",
!                 "symfony/property-info": "^6.4|^7.0|^8.0",
!                 "symfony/routing": "^6.4|^7.0|^8.0",
                  "symfony/security-acl": "^2.8|^3.0",
!                 "symfony/security-core": "^6.4|^7.0|^8.0",
!                 "symfony/security-csrf": "^6.4|^7.0|^8.0",
!                 "symfony/security-http": "^6.4|^7.0|^8.0",
!                 "symfony/serializer": "^6.4.3|^7.0.3|^8.0",
!                 "symfony/stopwatch": "^6.4|^7.0|^8.0",
!                 "symfony/translation": "^6.4|^7.0|^8.0",
!                 "symfony/validator": "^6.4|^7.0|^8.0",
!                 "symfony/web-link": "^6.4|^7.0|^8.0",
!                 "symfony/workflow": "^6.4|^7.0|^8.0",
!                 "symfony/yaml": "^6.4|^7.0|^8.0",
!                 "twig/cssinliner-extra": "^3",
!                 "twig/inky-extra": "^3",
!                 "twig/markdown-extra": "^3"
              },
              "type": "symfony-bridge",
              "autoload": {
***************
*** 5966,5972 ****
              "description": "Provides integration for Twig with various Symfony components",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/twig-bridge/tree/v6.4.25"
              },
              "funding": [
                  {
--- 5939,5945 ----
              "description": "Provides integration for Twig with various Symfony components",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/twig-bridge/tree/v7.4.7"
              },
              "funding": [
                  {
***************
*** 5986,6022 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-13T09:41:44+00:00"
          },
          {
              "name": "symfony/var-dumper",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/var-dumper.git",
!                 "reference": "c6cd92486e9fc32506370822c57bc02353a5a92c"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c6cd92486e9fc32506370822c57bc02353a5a92c",
!                 "reference": "c6cd92486e9fc32506370822c57bc02353a5a92c",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "symfony/deprecation-contracts": "^2.5|^3",
                  "symfony/polyfill-mbstring": "~1.0"
              },
              "conflict": {
!                 "symfony/console": "<5.4"
              },
              "require-dev": {
!                 "symfony/console": "^5.4|^6.0|^7.0",
!                 "symfony/error-handler": "^6.3|^7.0",
!                 "symfony/http-kernel": "^5.4|^6.0|^7.0",
!                 "symfony/process": "^5.4|^6.0|^7.0",
!                 "symfony/uid": "^5.4|^6.0|^7.0",
!                 "twig/twig": "^2.13|^3.0.4"
              },
              "bin": [
                  "Resources/bin/var-dump-server"
--- 5959,5994 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-03-04T15:37:05+00:00"
          },
          {
              "name": "symfony/var-dumper",
!             "version": "v7.4.6",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/var-dumper.git",
!                 "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/var-dumper/zipball/045321c440ac18347b136c63d2e9bf28a2dc0291",
!                 "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "symfony/deprecation-contracts": "^2.5|^3",
                  "symfony/polyfill-mbstring": "~1.0"
              },
              "conflict": {
!                 "symfony/console": "<6.4"
              },
              "require-dev": {
!                 "symfony/console": "^6.4|^7.0|^8.0",
!                 "symfony/http-kernel": "^6.4|^7.0|^8.0",
!                 "symfony/process": "^6.4|^7.0|^8.0",
!                 "symfony/uid": "^6.4|^7.0|^8.0",
!                 "twig/twig": "^3.12"
              },
              "bin": [
                  "Resources/bin/var-dump-server"
***************
*** 6054,6060 ****
                  "dump"
              ],
              "support": {
!                 "source": "https://github.com/symfony/var-dumper/tree/v6.4.25"
              },
              "funding": [
                  {
--- 6026,6032 ----
                  "dump"
              ],
              "support": {
!                 "source": "https://github.com/symfony/var-dumper/tree/v7.4.6"
              },
              "funding": [
                  {
***************
*** 6074,6103 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-13T09:41:44+00:00"
          },
          {
              "name": "symfony/var-exporter",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/var-exporter.git",
!                 "reference": "4ff50a1b7c75d1d596aca50899d0c8c7e3de8358"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/var-exporter/zipball/4ff50a1b7c75d1d596aca50899d0c8c7e3de8358",
!                 "reference": "4ff50a1b7c75d1d596aca50899d0c8c7e3de8358",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "symfony/deprecation-contracts": "^2.5|^3"
              },
              "require-dev": {
!                 "symfony/property-access": "^6.4|^7.0",
!                 "symfony/serializer": "^6.4|^7.0",
!                 "symfony/var-dumper": "^5.4|^6.0|^7.0"
              },
              "type": "library",
              "autoload": {
--- 6046,6075 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-02-15T10:53:20+00:00"
          },
          {
              "name": "symfony/var-exporter",
!             "version": "v7.4.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/var-exporter.git",
!                 "reference": "03a60f169c79a28513a78c967316fbc8bf17816f"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/var-exporter/zipball/03a60f169c79a28513a78c967316fbc8bf17816f",
!                 "reference": "03a60f169c79a28513a78c967316fbc8bf17816f",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "symfony/deprecation-contracts": "^2.5|^3"
              },
              "require-dev": {
!                 "symfony/property-access": "^6.4|^7.0|^8.0",
!                 "symfony/serializer": "^6.4|^7.0|^8.0",
!                 "symfony/var-dumper": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 6135,6141 ****
                  "serialize"
              ],
              "support": {
!                 "source": "https://github.com/symfony/var-exporter/tree/v6.4.25"
              },
              "funding": [
                  {
--- 6107,6113 ----
                  "serialize"
              ],
              "support": {
!                 "source": "https://github.com/symfony/var-exporter/tree/v7.4.0"
              },
              "funding": [
                  {
***************
*** 6155,6186 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-18T13:06:32+00:00"
          },
          {
              "name": "symfony/yaml",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/yaml.git",
!                 "reference": "e54b060bc9c3dc3d4258bf0d165d0064e755f565"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/yaml/zipball/e54b060bc9c3dc3d4258bf0d165d0064e755f565",
!                 "reference": "e54b060bc9c3dc3d4258bf0d165d0064e755f565",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "symfony/deprecation-contracts": "^2.5|^3",
                  "symfony/polyfill-ctype": "^1.8"
              },
              "conflict": {
!                 "symfony/console": "<5.4"
              },
              "require-dev": {
!                 "symfony/console": "^5.4|^6.0|^7.0"
              },
              "bin": [
                  "Resources/bin/yaml-lint"
--- 6127,6158 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-09-11T10:15:23+00:00"
          },
          {
              "name": "symfony/yaml",
!             "version": "v7.4.6",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/yaml.git",
!                 "reference": "58751048de17bae71c5aa0d13cb19d79bca26391"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/yaml/zipball/58751048de17bae71c5aa0d13cb19d79bca26391",
!                 "reference": "58751048de17bae71c5aa0d13cb19d79bca26391",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "symfony/deprecation-contracts": "^2.5|^3",
                  "symfony/polyfill-ctype": "^1.8"
              },
              "conflict": {
!                 "symfony/console": "<6.4"
              },
              "require-dev": {
!                 "symfony/console": "^6.4|^7.0|^8.0"
              },
              "bin": [
                  "Resources/bin/yaml-lint"
***************
*** 6211,6217 ****
              "description": "Loads and dumps YAML files",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/yaml/tree/v6.4.25"
              },
              "funding": [
                  {
--- 6183,6189 ----
              "description": "Loads and dumps YAML files",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/yaml/tree/v7.4.6"
              },
              "funding": [
                  {
***************
*** 6231,6250 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-26T16:59:00+00:00"
          },
          {
              "name": "twig/twig",
!             "version": "v3.21.1",
              "source": {
                  "type": "git",
                  "url": "https://github.com/twigphp/Twig.git",
!                 "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d",
!                 "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d",
                  "shasum": ""
              },
              "require": {
--- 6203,6222 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-02-09T09:33:46+00:00"
          },
          {
              "name": "twig/twig",
!             "version": "v3.22.2",
              "source": {
                  "type": "git",
                  "url": "https://github.com/twigphp/Twig.git",
!                 "reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/twigphp/Twig/zipball/946ddeafa3c9f4ce279d1f34051af041db0e16f2",
!                 "reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2",
                  "shasum": ""
              },
              "require": {
***************
*** 6298,6304 ****
              ],
              "support": {
                  "issues": "https://github.com/twigphp/Twig/issues",
!                 "source": "https://github.com/twigphp/Twig/tree/v3.21.1"
              },
              "funding": [
                  {
--- 6270,6276 ----
              ],
              "support": {
                  "issues": "https://github.com/twigphp/Twig/issues",
!                 "source": "https://github.com/twigphp/Twig/tree/v3.22.2"
              },
              "funding": [
                  {
***************
*** 6310,6316 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-05-03T07:21:55+00:00"
          },
          {
              "name": "web-token/jwt-key-mgmt",
--- 6282,6288 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-12-14T11:28:47+00:00"
          },
          {
              "name": "web-token/jwt-key-mgmt",
***************
*** 6382,6397 ****
          },
          {
              "name": "web-token/jwt-library",
!             "version": "3.4.8",
              "source": {
                  "type": "git",
                  "url": "https://github.com/web-token/jwt-library.git",
!                 "reference": "92445671cc788fa5f639898a67c06f9fd0bf491f"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/web-token/jwt-library/zipball/92445671cc788fa5f639898a67c06f9fd0bf491f",
!                 "reference": "92445671cc788fa5f639898a67c06f9fd0bf491f",
                  "shasum": ""
              },
              "require": {
--- 6354,6369 ----
          },
          {
              "name": "web-token/jwt-library",
!             "version": "3.4.9",
              "source": {
                  "type": "git",
                  "url": "https://github.com/web-token/jwt-library.git",
!                 "reference": "8fe1650bf3a73673a9c520feff8f9a0e9cbbcd8f"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/web-token/jwt-library/zipball/8fe1650bf3a73673a9c520feff8f9a0e9cbbcd8f",
!                 "reference": "8fe1650bf3a73673a9c520feff8f9a0e9cbbcd8f",
                  "shasum": ""
              },
              "require": {
***************
*** 6464,6470 ****
              ],
              "support": {
                  "issues": "https://github.com/web-token/jwt-library/issues",
!                 "source": "https://github.com/web-token/jwt-library/tree/3.4.8"
              },
              "funding": [
                  {
--- 6436,6442 ----
              ],
              "support": {
                  "issues": "https://github.com/web-token/jwt-library/issues",
!                 "source": "https://github.com/web-token/jwt-library/tree/3.4.9"
              },
              "funding": [
                  {
***************
*** 6476,6482 ****
                      "type": "patreon"
                  }
              ],
!             "time": "2025-05-07T09:11:18+00:00"
          },
          {
              "name": "web-token/jwt-signature",
--- 6448,6454 ----
                      "type": "patreon"
                  }
              ],
!             "time": "2025-11-17T20:20:37+00:00"
          },
          {
              "name": "web-token/jwt-signature",
***************
*** 6679,6716 ****
      "packages-dev": [
          {
              "name": "amphp/amp",
!             "version": "v2.6.5",
              "source": {
                  "type": "git",
                  "url": "https://github.com/amphp/amp.git",
!                 "reference": "d7dda98dae26e56f3f6fcfbf1c1f819c9a993207"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/amphp/amp/zipball/d7dda98dae26e56f3f6fcfbf1c1f819c9a993207",
!                 "reference": "d7dda98dae26e56f3f6fcfbf1c1f819c9a993207",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=7.1"
              },
              "require-dev": {
!                 "amphp/php-cs-fixer-config": "dev-master",
!                 "amphp/phpunit-util": "^1",
!                 "ext-json": "*",
!                 "jetbrains/phpstorm-stubs": "^2019.3",
!                 "phpunit/phpunit": "^7 | ^8 | ^9",
!                 "react/promise": "^2",
!                 "vimeo/psalm": "^3.12"
              },
              "type": "library",
              "autoload": {
                  "files": [
!                     "lib/functions.php",
!                     "lib/Internal/functions.php"
                  ],
                  "psr-4": {
!                     "Amp\\": "lib"
                  }
              },
              "notification-url": "https://packagist.org/downloads/",
--- 6651,6686 ----
      "packages-dev": [
          {
              "name": "amphp/amp",
!             "version": "v3.1.1",
              "source": {
                  "type": "git",
                  "url": "https://github.com/amphp/amp.git",
!                 "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/amphp/amp/zipball/fa0ab33a6f47a82929c38d03ca47ebb71086a93f",
!                 "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
!                 "revolt/event-loop": "^1 || ^0.2"
              },
              "require-dev": {
!                 "amphp/php-cs-fixer-config": "^2",
!                 "phpunit/phpunit": "^9",
!                 "psalm/phar": "5.23.1"
              },
              "type": "library",
              "autoload": {
                  "files": [
!                     "src/functions.php",
!                     "src/Future/functions.php",
!                     "src/Internal/functions.php"
                  ],
                  "psr-4": {
!                     "Amp\\": "src"
                  }
              },
              "notification-url": "https://packagist.org/downloads/",
***************
*** 6719,6728 ****
              ],
              "authors": [
                  {
-                     "name": "Daniel Lowrey",
-                     "email": "rdlowrey@php.net"
-                 },
-                 {
                      "name": "Aaron Piotrowski",
                      "email": "aaron@trowski.com"
                  },
--- 6689,6694 ----
***************
*** 6733,6738 ****
--- 6699,6708 ----
                  {
                      "name": "Niklas Keller",
                      "email": "me@kelunik.com"
+                 },
+                 {
+                     "name": "Daniel Lowrey",
+                     "email": "rdlowrey@php.net"
                  }
              ],
              "description": "A non-blocking concurrency framework for PHP applications.",
***************
*** 6749,6757 ****
                  "promise"
              ],
              "support": {
-                 "irc": "irc://irc.freenode.org/amphp",
                  "issues": "https://github.com/amphp/amp/issues",
!                 "source": "https://github.com/amphp/amp/tree/v2.6.5"
              },
              "funding": [
                  {
--- 6719,6726 ----
                  "promise"
              ],
              "support": {
                  "issues": "https://github.com/amphp/amp/issues",
!                 "source": "https://github.com/amphp/amp/tree/v3.1.1"
              },
              "funding": [
                  {
***************
*** 6759,6799 ****
                      "type": "github"
                  }
              ],
!             "time": "2025-09-03T19:41:28+00:00"
          },
          {
              "name": "amphp/byte-stream",
!             "version": "v1.8.2",
              "source": {
                  "type": "git",
                  "url": "https://github.com/amphp/byte-stream.git",
!                 "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc",
!                 "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc",
                  "shasum": ""
              },
              "require": {
!                 "amphp/amp": "^2",
!                 "php": ">=7.1"
              },
              "require-dev": {
!                 "amphp/php-cs-fixer-config": "dev-master",
!                 "amphp/phpunit-util": "^1.4",
!                 "friendsofphp/php-cs-fixer": "^2.3",
!                 "jetbrains/phpstorm-stubs": "^2019.3",
!                 "phpunit/phpunit": "^6 || ^7 || ^8",
!                 "psalm/phar": "^3.11.4"
              },
              "type": "library",
              "autoload": {
                  "files": [
!                     "lib/functions.php"
                  ],
                  "psr-4": {
!                     "Amp\\ByteStream\\": "lib"
                  }
              },
              "notification-url": "https://packagist.org/downloads/",
--- 6728,6772 ----
                      "type": "github"
                  }
              ],
!             "time": "2025-08-27T21:42:00+00:00"
          },
          {
              "name": "amphp/byte-stream",
!             "version": "v2.1.2",
              "source": {
                  "type": "git",
                  "url": "https://github.com/amphp/byte-stream.git",
!                 "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/amphp/byte-stream/zipball/55a6bd071aec26fa2a3e002618c20c35e3df1b46",
!                 "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46",
                  "shasum": ""
              },
              "require": {
!                 "amphp/amp": "^3",
!                 "amphp/parser": "^1.1",
!                 "amphp/pipeline": "^1",
!                 "amphp/serialization": "^1",
!                 "amphp/sync": "^2",
!                 "php": ">=8.1",
!                 "revolt/event-loop": "^1 || ^0.2.3"
              },
              "require-dev": {
!                 "amphp/php-cs-fixer-config": "^2",
!                 "amphp/phpunit-util": "^3",
!                 "phpunit/phpunit": "^9",
!                 "psalm/phar": "5.22.1"
              },
              "type": "library",
              "autoload": {
                  "files": [
!                     "src/functions.php",
!                     "src/Internal/functions.php"
                  ],
                  "psr-4": {
!                     "Amp\\ByteStream\\": "src"
                  }
              },
              "notification-url": "https://packagist.org/downloads/",
***************
*** 6822,6828 ****
              ],
              "support": {
                  "issues": "https://github.com/amphp/byte-stream/issues",
!                 "source": "https://github.com/amphp/byte-stream/tree/v1.8.2"
              },
              "funding": [
                  {
--- 6795,6801 ----
              ],
              "support": {
                  "issues": "https://github.com/amphp/byte-stream/issues",
!                 "source": "https://github.com/amphp/byte-stream/tree/v2.1.2"
              },
              "funding": [
                  {
***************
*** 6830,6895 ****
                      "type": "github"
                  }
              ],
!             "time": "2024-04-13T18:00:56+00:00"
          },
          {
!             "name": "dnoegel/php-xdg-base-dir",
!             "version": "v0.1.1",
              "source": {
                  "type": "git",
!                 "url": "https://github.com/dnoegel/php-xdg-base-dir.git",
!                 "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd",
!                 "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=5.3.2"
              },
              "require-dev": {
!                 "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35"
              },
              "type": "library",
              "autoload": {
                  "psr-4": {
!                     "XdgBaseDir\\": "src/"
                  }
              },
              "notification-url": "https://packagist.org/downloads/",
              "license": [
                  "MIT"
              ],
!             "description": "implementation of xdg base directory specification for php",
              "support": {
!                 "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues",
!                 "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1"
              },
!             "time": "2019-12-04T15:06:13+00:00"
          },
          {
!             "name": "felixfbecker/advanced-json-rpc",
!             "version": "v3.2.1",
              "source": {
                  "type": "git",
!                 "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git",
!                 "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447",
!                 "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447",
                  "shasum": ""
              },
              "require": {
!                 "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
!                 "php": "^7.1 || ^8.0",
!                 "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0"
              },
              "require-dev": {
!                 "phpunit/phpunit": "^7.0 || ^8.0"
              },
              "type": "library",
              "autoload": {
--- 6803,7486 ----
                      "type": "github"
                  }
              ],
!             "time": "2025-03-16T17:10:27+00:00"
          },
          {
!             "name": "amphp/cache",
!             "version": "v2.0.1",
              "source": {
                  "type": "git",
!                 "url": "https://github.com/amphp/cache.git",
!                 "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/amphp/cache/zipball/46912e387e6aa94933b61ea1ead9cf7540b7797c",
!                 "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c",
                  "shasum": ""
              },
              "require": {
!                 "amphp/amp": "^3",
!                 "amphp/serialization": "^1",
!                 "amphp/sync": "^2",
!                 "php": ">=8.1",
!                 "revolt/event-loop": "^1 || ^0.2"
              },
              "require-dev": {
!                 "amphp/php-cs-fixer-config": "^2",
!                 "amphp/phpunit-util": "^3",
!                 "phpunit/phpunit": "^9",
!                 "psalm/phar": "^5.4"
              },
              "type": "library",
              "autoload": {
                  "psr-4": {
!                     "Amp\\Cache\\": "src"
                  }
              },
              "notification-url": "https://packagist.org/downloads/",
              "license": [
                  "MIT"
              ],
!             "authors": [
!                 {
!                     "name": "Niklas Keller",
!                     "email": "me@kelunik.com"
!                 },
!                 {
!                     "name": "Aaron Piotrowski",
!                     "email": "aaron@trowski.com"
!                 },
!                 {
!                     "name": "Daniel Lowrey",
!                     "email": "rdlowrey@php.net"
!                 }
!             ],
!             "description": "A fiber-aware cache API based on Amp and Revolt.",
!             "homepage": "https://amphp.org/cache",
              "support": {
!                 "issues": "https://github.com/amphp/cache/issues",
!                 "source": "https://github.com/amphp/cache/tree/v2.0.1"
              },
!             "funding": [
!                 {
!                     "url": "https://github.com/amphp",
!                     "type": "github"
!                 }
!             ],
!             "time": "2024-04-19T03:38:06+00:00"
!         },
!         {
!             "name": "amphp/dns",
!             "version": "v2.4.0",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/amphp/dns.git",
!                 "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/amphp/dns/zipball/78eb3db5fc69bf2fc0cb503c4fcba667bc223c71",
!                 "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71",
!                 "shasum": ""
!             },
!             "require": {
!                 "amphp/amp": "^3",
!                 "amphp/byte-stream": "^2",
!                 "amphp/cache": "^2",
!                 "amphp/parser": "^1",
!                 "amphp/process": "^2",
!                 "daverandom/libdns": "^2.0.2",
!                 "ext-filter": "*",
!                 "ext-json": "*",
!                 "php": ">=8.1",
!                 "revolt/event-loop": "^1 || ^0.2"
!             },
!             "require-dev": {
!                 "amphp/php-cs-fixer-config": "^2",
!                 "amphp/phpunit-util": "^3",
!                 "phpunit/phpunit": "^9",
!                 "psalm/phar": "5.20"
!             },
!             "type": "library",
!             "autoload": {
!                 "files": [
!                     "src/functions.php"
!                 ],
!                 "psr-4": {
!                     "Amp\\Dns\\": "src"
!                 }
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "MIT"
!             ],
!             "authors": [
!                 {
!                     "name": "Chris Wright",
!                     "email": "addr@daverandom.com"
!                 },
!                 {
!                     "name": "Daniel Lowrey",
!                     "email": "rdlowrey@php.net"
!                 },
!                 {
!                     "name": "Bob Weinand",
!                     "email": "bobwei9@hotmail.com"
!                 },
!                 {
!                     "name": "Niklas Keller",
!                     "email": "me@kelunik.com"
!                 },
!                 {
!                     "name": "Aaron Piotrowski",
!                     "email": "aaron@trowski.com"
!                 }
!             ],
!             "description": "Async DNS resolution for Amp.",
!             "homepage": "https://github.com/amphp/dns",
!             "keywords": [
!                 "amp",
!                 "amphp",
!                 "async",
!                 "client",
!                 "dns",
!                 "resolve"
!             ],
!             "support": {
!                 "issues": "https://github.com/amphp/dns/issues",
!                 "source": "https://github.com/amphp/dns/tree/v2.4.0"
!             },
!             "funding": [
!                 {
!                     "url": "https://github.com/amphp",
!                     "type": "github"
!                 }
!             ],
!             "time": "2025-01-19T15:43:40+00:00"
!         },
!         {
!             "name": "amphp/parallel",
!             "version": "v2.3.3",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/amphp/parallel.git",
!                 "reference": "296b521137a54d3a02425b464e5aee4c93db2c60"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/amphp/parallel/zipball/296b521137a54d3a02425b464e5aee4c93db2c60",
!                 "reference": "296b521137a54d3a02425b464e5aee4c93db2c60",
!                 "shasum": ""
!             },
!             "require": {
!                 "amphp/amp": "^3",
!                 "amphp/byte-stream": "^2",
!                 "amphp/cache": "^2",
!                 "amphp/parser": "^1",
!                 "amphp/pipeline": "^1",
!                 "amphp/process": "^2",
!                 "amphp/serialization": "^1",
!                 "amphp/socket": "^2",
!                 "amphp/sync": "^2",
!                 "php": ">=8.1",
!                 "revolt/event-loop": "^1"
!             },
!             "require-dev": {
!                 "amphp/php-cs-fixer-config": "^2",
!                 "amphp/phpunit-util": "^3",
!                 "phpunit/phpunit": "^9",
!                 "psalm/phar": "^5.18"
!             },
!             "type": "library",
!             "autoload": {
!                 "files": [
!                     "src/Context/functions.php",
!                     "src/Context/Internal/functions.php",
!                     "src/Ipc/functions.php",
!                     "src/Worker/functions.php"
!                 ],
!                 "psr-4": {
!                     "Amp\\Parallel\\": "src"
!                 }
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "MIT"
!             ],
!             "authors": [
!                 {
!                     "name": "Aaron Piotrowski",
!                     "email": "aaron@trowski.com"
!                 },
!                 {
!                     "name": "Niklas Keller",
!                     "email": "me@kelunik.com"
!                 },
!                 {
!                     "name": "Stephen Coakley",
!                     "email": "me@stephencoakley.com"
!                 }
!             ],
!             "description": "Parallel processing component for Amp.",
!             "homepage": "https://github.com/amphp/parallel",
!             "keywords": [
!                 "async",
!                 "asynchronous",
!                 "concurrent",
!                 "multi-processing",
!                 "multi-threading"
!             ],
!             "support": {
!                 "issues": "https://github.com/amphp/parallel/issues",
!                 "source": "https://github.com/amphp/parallel/tree/v2.3.3"
!             },
!             "funding": [
!                 {
!                     "url": "https://github.com/amphp",
!                     "type": "github"
!                 }
!             ],
!             "time": "2025-11-15T06:23:42+00:00"
!         },
!         {
!             "name": "amphp/parser",
!             "version": "v1.1.1",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/amphp/parser.git",
!                 "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/amphp/parser/zipball/3cf1f8b32a0171d4b1bed93d25617637a77cded7",
!                 "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7",
!                 "shasum": ""
!             },
!             "require": {
!                 "php": ">=7.4"
!             },
!             "require-dev": {
!                 "amphp/php-cs-fixer-config": "^2",
!                 "phpunit/phpunit": "^9",
!                 "psalm/phar": "^5.4"
!             },
!             "type": "library",
!             "autoload": {
!                 "psr-4": {
!                     "Amp\\Parser\\": "src"
!                 }
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "MIT"
!             ],
!             "authors": [
!                 {
!                     "name": "Aaron Piotrowski",
!                     "email": "aaron@trowski.com"
!                 },
!                 {
!                     "name": "Niklas Keller",
!                     "email": "me@kelunik.com"
!                 }
!             ],
!             "description": "A generator parser to make streaming parsers simple.",
!             "homepage": "https://github.com/amphp/parser",
!             "keywords": [
!                 "async",
!                 "non-blocking",
!                 "parser",
!                 "stream"
!             ],
!             "support": {
!                 "issues": "https://github.com/amphp/parser/issues",
!                 "source": "https://github.com/amphp/parser/tree/v1.1.1"
!             },
!             "funding": [
!                 {
!                     "url": "https://github.com/amphp",
!                     "type": "github"
!                 }
!             ],
!             "time": "2024-03-21T19:16:53+00:00"
!         },
!         {
!             "name": "amphp/pipeline",
!             "version": "v1.2.3",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/amphp/pipeline.git",
!                 "reference": "7b52598c2e9105ebcddf247fc523161581930367"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/amphp/pipeline/zipball/7b52598c2e9105ebcddf247fc523161581930367",
!                 "reference": "7b52598c2e9105ebcddf247fc523161581930367",
!                 "shasum": ""
!             },
!             "require": {
!                 "amphp/amp": "^3",
!                 "php": ">=8.1",
!                 "revolt/event-loop": "^1"
!             },
!             "require-dev": {
!                 "amphp/php-cs-fixer-config": "^2",
!                 "amphp/phpunit-util": "^3",
!                 "phpunit/phpunit": "^9",
!                 "psalm/phar": "^5.18"
!             },
!             "type": "library",
!             "autoload": {
!                 "psr-4": {
!                     "Amp\\Pipeline\\": "src"
!                 }
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "MIT"
!             ],
!             "authors": [
!                 {
!                     "name": "Aaron Piotrowski",
!                     "email": "aaron@trowski.com"
!                 },
!                 {
!                     "name": "Niklas Keller",
!                     "email": "me@kelunik.com"
!                 }
!             ],
!             "description": "Asynchronous iterators and operators.",
!             "homepage": "https://amphp.org/pipeline",
!             "keywords": [
!                 "amp",
!                 "amphp",
!                 "async",
!                 "io",
!                 "iterator",
!                 "non-blocking"
!             ],
!             "support": {
!                 "issues": "https://github.com/amphp/pipeline/issues",
!                 "source": "https://github.com/amphp/pipeline/tree/v1.2.3"
!             },
!             "funding": [
!                 {
!                     "url": "https://github.com/amphp",
!                     "type": "github"
!                 }
!             ],
!             "time": "2025-03-16T16:33:53+00:00"
!         },
!         {
!             "name": "amphp/process",
!             "version": "v2.0.3",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/amphp/process.git",
!                 "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/amphp/process/zipball/52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d",
!                 "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d",
!                 "shasum": ""
!             },
!             "require": {
!                 "amphp/amp": "^3",
!                 "amphp/byte-stream": "^2",
!                 "amphp/sync": "^2",
!                 "php": ">=8.1",
!                 "revolt/event-loop": "^1 || ^0.2"
!             },
!             "require-dev": {
!                 "amphp/php-cs-fixer-config": "^2",
!                 "amphp/phpunit-util": "^3",
!                 "phpunit/phpunit": "^9",
!                 "psalm/phar": "^5.4"
!             },
!             "type": "library",
!             "autoload": {
!                 "files": [
!                     "src/functions.php"
!                 ],
!                 "psr-4": {
!                     "Amp\\Process\\": "src"
!                 }
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "MIT"
!             ],
!             "authors": [
!                 {
!                     "name": "Bob Weinand",
!                     "email": "bobwei9@hotmail.com"
!                 },
!                 {
!                     "name": "Aaron Piotrowski",
!                     "email": "aaron@trowski.com"
!                 },
!                 {
!                     "name": "Niklas Keller",
!                     "email": "me@kelunik.com"
!                 }
!             ],
!             "description": "A fiber-aware process manager based on Amp and Revolt.",
!             "homepage": "https://amphp.org/process",
!             "support": {
!                 "issues": "https://github.com/amphp/process/issues",
!                 "source": "https://github.com/amphp/process/tree/v2.0.3"
!             },
!             "funding": [
!                 {
!                     "url": "https://github.com/amphp",
!                     "type": "github"
!                 }
!             ],
!             "time": "2024-04-19T03:13:44+00:00"
!         },
!         {
!             "name": "amphp/serialization",
!             "version": "v1.0.0",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/amphp/serialization.git",
!                 "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1",
!                 "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1",
!                 "shasum": ""
!             },
!             "require": {
!                 "php": ">=7.1"
!             },
!             "require-dev": {
!                 "amphp/php-cs-fixer-config": "dev-master",
!                 "phpunit/phpunit": "^9 || ^8 || ^7"
!             },
!             "type": "library",
!             "autoload": {
!                 "files": [
!                     "src/functions.php"
!                 ],
!                 "psr-4": {
!                     "Amp\\Serialization\\": "src"
!                 }
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "MIT"
!             ],
!             "authors": [
!                 {
!                     "name": "Aaron Piotrowski",
!                     "email": "aaron@trowski.com"
!                 },
!                 {
!                     "name": "Niklas Keller",
!                     "email": "me@kelunik.com"
!                 }
!             ],
!             "description": "Serialization tools for IPC and data storage in PHP.",
!             "homepage": "https://github.com/amphp/serialization",
!             "keywords": [
!                 "async",
!                 "asynchronous",
!                 "serialization",
!                 "serialize"
!             ],
!             "support": {
!                 "issues": "https://github.com/amphp/serialization/issues",
!                 "source": "https://github.com/amphp/serialization/tree/master"
!             },
!             "time": "2020-03-25T21:39:07+00:00"
!         },
!         {
!             "name": "amphp/socket",
!             "version": "v2.3.1",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/amphp/socket.git",
!                 "reference": "58e0422221825b79681b72c50c47a930be7bf1e1"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/amphp/socket/zipball/58e0422221825b79681b72c50c47a930be7bf1e1",
!                 "reference": "58e0422221825b79681b72c50c47a930be7bf1e1",
!                 "shasum": ""
!             },
!             "require": {
!                 "amphp/amp": "^3",
!                 "amphp/byte-stream": "^2",
!                 "amphp/dns": "^2",
!                 "ext-openssl": "*",
!                 "kelunik/certificate": "^1.1",
!                 "league/uri": "^6.5 | ^7",
!                 "league/uri-interfaces": "^2.3 | ^7",
!                 "php": ">=8.1",
!                 "revolt/event-loop": "^1 || ^0.2"
!             },
!             "require-dev": {
!                 "amphp/php-cs-fixer-config": "^2",
!                 "amphp/phpunit-util": "^3",
!                 "amphp/process": "^2",
!                 "phpunit/phpunit": "^9",
!                 "psalm/phar": "5.20"
!             },
!             "type": "library",
!             "autoload": {
!                 "files": [
!                     "src/functions.php",
!                     "src/Internal/functions.php",
!                     "src/SocketAddress/functions.php"
!                 ],
!                 "psr-4": {
!                     "Amp\\Socket\\": "src"
!                 }
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "MIT"
!             ],
!             "authors": [
!                 {
!                     "name": "Daniel Lowrey",
!                     "email": "rdlowrey@gmail.com"
!                 },
!                 {
!                     "name": "Aaron Piotrowski",
!                     "email": "aaron@trowski.com"
!                 },
!                 {
!                     "name": "Niklas Keller",
!                     "email": "me@kelunik.com"
!                 }
!             ],
!             "description": "Non-blocking socket connection / server implementations based on Amp and Revolt.",
!             "homepage": "https://github.com/amphp/socket",
!             "keywords": [
!                 "amp",
!                 "async",
!                 "encryption",
!                 "non-blocking",
!                 "sockets",
!                 "tcp",
!                 "tls"
!             ],
!             "support": {
!                 "issues": "https://github.com/amphp/socket/issues",
!                 "source": "https://github.com/amphp/socket/tree/v2.3.1"
!             },
!             "funding": [
!                 {
!                     "url": "https://github.com/amphp",
!                     "type": "github"
!                 }
!             ],
!             "time": "2024-04-21T14:33:03+00:00"
!         },
!         {
!             "name": "amphp/sync",
!             "version": "v2.3.0",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/amphp/sync.git",
!                 "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/amphp/sync/zipball/217097b785130d77cfcc58ff583cf26cd1770bf1",
!                 "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1",
!                 "shasum": ""
!             },
!             "require": {
!                 "amphp/amp": "^3",
!                 "amphp/pipeline": "^1",
!                 "amphp/serialization": "^1",
!                 "php": ">=8.1",
!                 "revolt/event-loop": "^1 || ^0.2"
!             },
!             "require-dev": {
!                 "amphp/php-cs-fixer-config": "^2",
!                 "amphp/phpunit-util": "^3",
!                 "phpunit/phpunit": "^9",
!                 "psalm/phar": "5.23"
!             },
!             "type": "library",
!             "autoload": {
!                 "files": [
!                     "src/functions.php"
!                 ],
!                 "psr-4": {
!                     "Amp\\Sync\\": "src"
!                 }
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "MIT"
!             ],
!             "authors": [
!                 {
!                     "name": "Aaron Piotrowski",
!                     "email": "aaron@trowski.com"
!                 },
!                 {
!                     "name": "Niklas Keller",
!                     "email": "me@kelunik.com"
!                 },
!                 {
!                     "name": "Stephen Coakley",
!                     "email": "me@stephencoakley.com"
!                 }
!             ],
!             "description": "Non-blocking synchronization primitives for PHP based on Amp and Revolt.",
!             "homepage": "https://github.com/amphp/sync",
!             "keywords": [
!                 "async",
!                 "asynchronous",
!                 "mutex",
!                 "semaphore",
!                 "synchronization"
!             ],
!             "support": {
!                 "issues": "https://github.com/amphp/sync/issues",
!                 "source": "https://github.com/amphp/sync/tree/v2.3.0"
!             },
!             "funding": [
!                 {
!                     "url": "https://github.com/amphp",
!                     "type": "github"
!                 }
!             ],
!             "time": "2024-08-03T19:31:26+00:00"
          },
          {
!             "name": "danog/advanced-json-rpc",
!             "version": "v3.2.3",
              "source": {
                  "type": "git",
!                 "url": "https://github.com/danog/php-advanced-json-rpc.git",
!                 "reference": "ae703ea7b4811797a10590b6078de05b3b33dd91"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/danog/php-advanced-json-rpc/zipball/ae703ea7b4811797a10590b6078de05b3b33dd91",
!                 "reference": "ae703ea7b4811797a10590b6078de05b3b33dd91",
                  "shasum": ""
              },
              "require": {
!                 "netresearch/jsonmapper": "^5",
!                 "php": ">=8.1",
!                 "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0 || ^6"
!             },
!             "replace": {
!                 "felixfbecker/php-advanced-json-rpc": "^3"
              },
              "require-dev": {
!                 "phpunit/phpunit": "^9"
              },
              "type": "library",
              "autoload": {
***************
*** 6905,6918 ****
                  {
                      "name": "Felix Becker",
                      "email": "felix.b@outlook.com"
                  }
              ],
              "description": "A more advanced JSONRPC implementation",
              "support": {
!                 "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues",
!                 "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1"
              },
!             "time": "2021-06-11T22:34:44+00:00"
          },
          {
              "name": "felixfbecker/language-server-protocol",
--- 7496,7594 ----
                  {
                      "name": "Felix Becker",
                      "email": "felix.b@outlook.com"
+                 },
+                 {
+                     "name": "Daniil Gentili",
+                     "email": "daniil@daniil.it"
                  }
              ],
              "description": "A more advanced JSONRPC implementation",
              "support": {
!                 "issues": "https://github.com/danog/php-advanced-json-rpc/issues",
!                 "source": "https://github.com/danog/php-advanced-json-rpc/tree/v3.2.3"
!             },
!             "time": "2026-01-12T21:07:10+00:00"
!         },
!         {
!             "name": "daverandom/libdns",
!             "version": "v2.1.0",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/DaveRandom/LibDNS.git",
!                 "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/DaveRandom/LibDNS/zipball/b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a",
!                 "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a",
!                 "shasum": ""
!             },
!             "require": {
!                 "ext-ctype": "*",
!                 "php": ">=7.1"
!             },
!             "suggest": {
!                 "ext-intl": "Required for IDN support"
!             },
!             "type": "library",
!             "autoload": {
!                 "files": [
!                     "src/functions.php"
!                 ],
!                 "psr-4": {
!                     "LibDNS\\": "src/"
!                 }
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "MIT"
!             ],
!             "description": "DNS protocol implementation written in pure PHP",
!             "keywords": [
!                 "dns"
!             ],
!             "support": {
!                 "issues": "https://github.com/DaveRandom/LibDNS/issues",
!                 "source": "https://github.com/DaveRandom/LibDNS/tree/v2.1.0"
              },
!             "time": "2024-04-12T12:12:48+00:00"
!         },
!         {
!             "name": "dnoegel/php-xdg-base-dir",
!             "version": "v0.1.1",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/dnoegel/php-xdg-base-dir.git",
!                 "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd",
!                 "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd",
!                 "shasum": ""
!             },
!             "require": {
!                 "php": ">=5.3.2"
!             },
!             "require-dev": {
!                 "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35"
!             },
!             "type": "library",
!             "autoload": {
!                 "psr-4": {
!                     "XdgBaseDir\\": "src/"
!                 }
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "MIT"
!             ],
!             "description": "implementation of xdg base directory specification for php",
!             "support": {
!                 "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues",
!                 "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1"
!             },
!             "time": "2019-12-04T15:06:13+00:00"
          },
          {
              "name": "felixfbecker/language-server-protocol",
***************
*** 7032,7067 ****
              "time": "2025-08-14T07:29:31+00:00"
          },
          {
!             "name": "laravel/homestead",
!             "version": "v14.5.0",
              "source": {
                  "type": "git",
!                 "url": "https://github.com/laravel/homestead.git",
!                 "reference": "78d44a2f38d27f057d87c7dcff5ed2f67e07f898"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/laravel/homestead/zipball/78d44a2f38d27f057d87c7dcff5ed2f67e07f898",
!                 "reference": "78d44a2f38d27f057d87c7dcff5ed2f67e07f898",
                  "shasum": ""
              },
              "require": {
!                 "php": "^8.0 || <8.3",
!                 "symfony/console": "^5.0 || ^6.0",
!                 "symfony/process": "^5.0 || ^6.0",
!                 "symfony/yaml": "^5.0 || ^6.0"
              },
              "require-dev": {
!                 "dms/phpunit-arraysubset-asserts": "^0.2.1",
!                 "phpunit/phpunit": "^9.5"
              },
!             "bin": [
!                 "bin/homestead"
              ],
              "type": "library",
              "autoload": {
                  "psr-4": {
!                     "Laravel\\Homestead\\": "src/"
                  }
              },
              "notification-url": "https://packagist.org/downloads/",
--- 7708,7815 ----
              "time": "2025-08-14T07:29:31+00:00"
          },
          {
!             "name": "kelunik/certificate",
!             "version": "v1.1.3",
              "source": {
                  "type": "git",
!                 "url": "https://github.com/kelunik/certificate.git",
!                 "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/kelunik/certificate/zipball/7e00d498c264d5eb4f78c69f41c8bd6719c0199e",
!                 "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e",
                  "shasum": ""
              },
              "require": {
!                 "ext-openssl": "*",
!                 "php": ">=7.0"
              },
              "require-dev": {
!                 "amphp/php-cs-fixer-config": "^2",
!                 "phpunit/phpunit": "^6 | 7 | ^8 | ^9"
              },
!             "type": "library",
!             "extra": {
!                 "branch-alias": {
!                     "dev-master": "1.x-dev"
!                 }
!             },
!             "autoload": {
!                 "psr-4": {
!                     "Kelunik\\Certificate\\": "src"
!                 }
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "MIT"
!             ],
!             "authors": [
!                 {
!                     "name": "Niklas Keller",
!                     "email": "me@kelunik.com"
!                 }
              ],
+             "description": "Access certificate details and transform between different formats.",
+             "keywords": [
+                 "DER",
+                 "certificate",
+                 "certificates",
+                 "openssl",
+                 "pem",
+                 "x509"
+             ],
+             "support": {
+                 "issues": "https://github.com/kelunik/certificate/issues",
+                 "source": "https://github.com/kelunik/certificate/tree/v1.1.3"
+             },
+             "time": "2023-02-03T21:26:53+00:00"
+         },
+         {
+             "name": "league/uri",
+             "version": "7.8.1",
+             "source": {
+                 "type": "git",
+                 "url": "https://github.com/thephpleague/uri.git",
+                 "reference": "08cf38e3924d4f56238125547b5720496fac8fd4"
+             },
+             "dist": {
+                 "type": "zip",
+                 "url": "https://api.github.com/repos/thephpleague/uri/zipball/08cf38e3924d4f56238125547b5720496fac8fd4",
+                 "reference": "08cf38e3924d4f56238125547b5720496fac8fd4",
+                 "shasum": ""
+             },
+             "require": {
+                 "league/uri-interfaces": "^7.8.1",
+                 "php": "^8.1",
+                 "psr/http-factory": "^1"
+             },
+             "conflict": {
+                 "league/uri-schemes": "^1.0"
+             },
+             "suggest": {
+                 "ext-bcmath": "to improve IPV4 host parsing",
+                 "ext-dom": "to convert the URI into an HTML anchor tag",
+                 "ext-fileinfo": "to create Data URI from file contennts",
+                 "ext-gmp": "to improve IPV4 host parsing",
+                 "ext-intl": "to handle IDN host with the best performance",
+                 "ext-uri": "to use the PHP native URI class",
+                 "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain",
+                 "league/uri-components": "to provide additional tools to manipulate URI objects components",
+                 "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP",
+                 "php-64bit": "to improve IPV4 host parsing",
+                 "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification",
+                 "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
+             },
              "type": "library",
+             "extra": {
+                 "branch-alias": {
+                     "dev-master": "7.x-dev"
+                 }
+             },
              "autoload": {
                  "psr-4": {
!                     "League\\Uri\\": ""
                  }
              },
              "notification-url": "https://packagist.org/downloads/",
***************
*** 7070,7090 ****
              ],
              "authors": [
                  {
!                     "name": "Taylor Otwell",
!                     "email": "taylor@laravel.com"
!                 },
                  {
!                     "name": "Joe Ferguson",
!                     "email": "joe@joeferguson.me"
                  }
              ],
!             "description": "A virtual machine for web artisans.",
              "support": {
!                 "issues": "https://github.com/laravel/homestead/issues",
!                 "source": "https://github.com/laravel/homestead/tree/v14.5.0"
              },
!             "abandoned": true,
!             "time": "2023-10-06T03:16:47+00:00"
          },
          {
              "name": "masterminds/html5",
--- 7818,7951 ----
              ],
              "authors": [
                  {
!                     "name": "Ignace Nyamagana Butera",
!                     "email": "nyamsprod@gmail.com",
!                     "homepage": "https://nyamsprod.com"
!                 }
!             ],
!             "description": "URI manipulation library",
!             "homepage": "https://uri.thephpleague.com",
!             "keywords": [
!                 "URN",
!                 "data-uri",
!                 "file-uri",
!                 "ftp",
!                 "hostname",
!                 "http",
!                 "https",
!                 "middleware",
!                 "parse_str",
!                 "parse_url",
!                 "psr-7",
!                 "query-string",
!                 "querystring",
!                 "rfc2141",
!                 "rfc3986",
!                 "rfc3987",
!                 "rfc6570",
!                 "rfc8141",
!                 "uri",
!                 "uri-template",
!                 "url",
!                 "ws"
!             ],
!             "support": {
!                 "docs": "https://uri.thephpleague.com",
!                 "forum": "https://thephpleague.slack.com",
!                 "issues": "https://github.com/thephpleague/uri-src/issues",
!                 "source": "https://github.com/thephpleague/uri/tree/7.8.1"
!             },
!             "funding": [
!                 {
!                     "url": "https://github.com/sponsors/nyamsprod",
!                     "type": "github"
!                 }
!             ],
!             "time": "2026-03-15T20:22:25+00:00"
!         },
!         {
!             "name": "league/uri-interfaces",
!             "version": "7.8.1",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/thephpleague/uri-interfaces.git",
!                 "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/85d5c77c5d6d3af6c54db4a78246364908f3c928",
!                 "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928",
!                 "shasum": ""
!             },
!             "require": {
!                 "ext-filter": "*",
!                 "php": "^8.1",
!                 "psr/http-message": "^1.1 || ^2.0"
!             },
!             "suggest": {
!                 "ext-bcmath": "to improve IPV4 host parsing",
!                 "ext-gmp": "to improve IPV4 host parsing",
!                 "ext-intl": "to handle IDN host with the best performance",
!                 "php-64bit": "to improve IPV4 host parsing",
!                 "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification",
!                 "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
!             },
!             "type": "library",
!             "extra": {
!                 "branch-alias": {
!                     "dev-master": "7.x-dev"
!                 }
!             },
!             "autoload": {
!                 "psr-4": {
!                     "League\\Uri\\": ""
!                 }
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "MIT"
!             ],
!             "authors": [
                  {
!                     "name": "Ignace Nyamagana Butera",
!                     "email": "nyamsprod@gmail.com",
!                     "homepage": "https://nyamsprod.com"
                  }
              ],
!             "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI",
!             "homepage": "https://uri.thephpleague.com",
!             "keywords": [
!                 "data-uri",
!                 "file-uri",
!                 "ftp",
!                 "hostname",
!                 "http",
!                 "https",
!                 "parse_str",
!                 "parse_url",
!                 "psr-7",
!                 "query-string",
!                 "querystring",
!                 "rfc3986",
!                 "rfc3987",
!                 "rfc6570",
!                 "uri",
!                 "url",
!                 "ws"
!             ],
              "support": {
!                 "docs": "https://uri.thephpleague.com",
!                 "forum": "https://thephpleague.slack.com",
!                 "issues": "https://github.com/thephpleague/uri-src/issues",
!                 "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.1"
              },
!             "funding": [
!                 {
!                     "url": "https://github.com/sponsors/nyamsprod",
!                     "type": "github"
!                 }
!             ],
!             "time": "2026-03-08T20:05:35+00:00"
          },
          {
              "name": "masterminds/html5",
***************
*** 7270,7285 ****
          },
          {
              "name": "netresearch/jsonmapper",
!             "version": "v4.5.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/cweiske/jsonmapper.git",
!                 "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8e76efb98ee8b6afc54687045e1b8dba55ac76e5",
!                 "reference": "8e76efb98ee8b6afc54687045e1b8dba55ac76e5",
                  "shasum": ""
              },
              "require": {
--- 8131,8146 ----
          },
          {
              "name": "netresearch/jsonmapper",
!             "version": "v5.0.1",
              "source": {
                  "type": "git",
                  "url": "https://github.com/cweiske/jsonmapper.git",
!                 "reference": "980674efdda65913492d29a8fd51c82270dd37bb"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/980674efdda65913492d29a8fd51c82270dd37bb",
!                 "reference": "980674efdda65913492d29a8fd51c82270dd37bb",
                  "shasum": ""
              },
              "require": {
***************
*** 7315,7345 ****
              "support": {
                  "email": "cweiske@cweiske.de",
                  "issues": "https://github.com/cweiske/jsonmapper/issues",
!                 "source": "https://github.com/cweiske/jsonmapper/tree/v4.5.0"
              },
!             "time": "2024-09-08T10:13:13+00:00"
          },
          {
              "name": "nikic/php-parser",
!             "version": "v4.19.4",
              "source": {
                  "type": "git",
                  "url": "https://github.com/nikic/PHP-Parser.git",
!                 "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/715f4d25e225bc47b293a8b997fe6ce99bf987d2",
!                 "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2",
                  "shasum": ""
              },
              "require": {
                  "ext-tokenizer": "*",
!                 "php": ">=7.1"
              },
              "require-dev": {
                  "ircmaxell/php-yacc": "^0.0.7",
!                 "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
              },
              "bin": [
                  "bin/php-parse"
--- 8176,8208 ----
              "support": {
                  "email": "cweiske@cweiske.de",
                  "issues": "https://github.com/cweiske/jsonmapper/issues",
!                 "source": "https://github.com/cweiske/jsonmapper/tree/v5.0.1"
              },
!             "time": "2026-02-22T16:28:03+00:00"
          },
          {
              "name": "nikic/php-parser",
!             "version": "v5.7.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/nikic/PHP-Parser.git",
!                 "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
!                 "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
                  "shasum": ""
              },
              "require": {
+                 "ext-ctype": "*",
+                 "ext-json": "*",
                  "ext-tokenizer": "*",
!                 "php": ">=7.4"
              },
              "require-dev": {
                  "ircmaxell/php-yacc": "^0.0.7",
!                 "phpunit/phpunit": "^9.0"
              },
              "bin": [
                  "bin/php-parse"
***************
*** 7347,7353 ****
              "type": "library",
              "extra": {
                  "branch-alias": {
!                     "dev-master": "4.9-dev"
                  }
              },
              "autoload": {
--- 8210,8216 ----
              "type": "library",
              "extra": {
                  "branch-alias": {
!                     "dev-master": "5.x-dev"
                  }
              },
              "autoload": {
***************
*** 7371,7379 ****
              ],
              "support": {
                  "issues": "https://github.com/nikic/PHP-Parser/issues",
!                 "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.4"
              },
!             "time": "2024-09-29T15:01:53+00:00"
          },
          {
              "name": "phar-io/manifest",
--- 8234,8242 ----
              ],
              "support": {
                  "issues": "https://github.com/nikic/PHP-Parser/issues",
!                 "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
              },
!             "time": "2025-12-06T11:56:16+00:00"
          },
          {
              "name": "phar-io/manifest",
***************
*** 7660,7675 ****
          },
          {
              "name": "phpdocumentor/reflection-docblock",
!             "version": "5.6.3",
              "source": {
                  "type": "git",
                  "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
!                 "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94f8051919d1b0369a6bcc7931d679a511c03fe9",
!                 "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9",
                  "shasum": ""
              },
              "require": {
--- 8523,8538 ----
          },
          {
              "name": "phpdocumentor/reflection-docblock",
!             "version": "6.0.3",
              "source": {
                  "type": "git",
                  "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
!                 "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/7bae67520aa9f5ecc506d646810bd40d9da54582",
!                 "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582",
                  "shasum": ""
              },
              "require": {
***************
*** 7677,7685 ****
                  "ext-filter": "*",
                  "php": "^7.4 || ^8.0",
                  "phpdocumentor/reflection-common": "^2.2",
!                 "phpdocumentor/type-resolver": "^1.7",
!                 "phpstan/phpdoc-parser": "^1.7|^2.0",
!                 "webmozart/assert": "^1.9.1"
              },
              "require-dev": {
                  "mockery/mockery": "~1.3.5 || ~1.6.0",
--- 8540,8548 ----
                  "ext-filter": "*",
                  "php": "^7.4 || ^8.0",
                  "phpdocumentor/reflection-common": "^2.2",
!                 "phpdocumentor/type-resolver": "^2.0",
!                 "phpstan/phpdoc-parser": "^2.0",
!                 "webmozart/assert": "^1.9.1 || ^2"
              },
              "require-dev": {
                  "mockery/mockery": "~1.3.5 || ~1.6.0",
***************
*** 7688,7694 ****
                  "phpstan/phpstan-mockery": "^1.1",
                  "phpstan/phpstan-webmozart-assert": "^1.2",
                  "phpunit/phpunit": "^9.5",
!                 "psalm/phar": "^5.26"
              },
              "type": "library",
              "extra": {
--- 8551,8558 ----
                  "phpstan/phpstan-mockery": "^1.1",
                  "phpstan/phpstan-webmozart-assert": "^1.2",
                  "phpunit/phpunit": "^9.5",
!                 "psalm/phar": "^5.26",
!                 "shipmonk/dead-code-detector": "^0.5.1"
              },
              "type": "library",
              "extra": {
***************
*** 7718,7761 ****
              "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
              "support": {
                  "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
!                 "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.3"
              },
!             "time": "2025-08-01T19:43:32+00:00"
          },
          {
              "name": "phpdocumentor/type-resolver",
!             "version": "1.10.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/phpDocumentor/TypeResolver.git",
!                 "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a",
!                 "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a",
                  "shasum": ""
              },
              "require": {
                  "doctrine/deprecations": "^1.0",
!                 "php": "^7.3 || ^8.0",
                  "phpdocumentor/reflection-common": "^2.0",
!                 "phpstan/phpdoc-parser": "^1.18|^2.0"
              },
              "require-dev": {
                  "ext-tokenizer": "*",
                  "phpbench/phpbench": "^1.2",
!                 "phpstan/extension-installer": "^1.1",
!                 "phpstan/phpstan": "^1.8",
!                 "phpstan/phpstan-phpunit": "^1.1",
                  "phpunit/phpunit": "^9.5",
!                 "rector/rector": "^0.13.9",
!                 "vimeo/psalm": "^4.25"
              },
              "type": "library",
              "extra": {
                  "branch-alias": {
!                     "dev-1.x": "1.x-dev"
                  }
              },
              "autoload": {
--- 8582,8625 ----
              "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
              "support": {
                  "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
!                 "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.3"
              },
!             "time": "2026-03-18T20:49:53+00:00"
          },
          {
              "name": "phpdocumentor/type-resolver",
!             "version": "2.0.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/phpDocumentor/TypeResolver.git",
!                 "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9",
!                 "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9",
                  "shasum": ""
              },
              "require": {
                  "doctrine/deprecations": "^1.0",
!                 "php": "^7.4 || ^8.0",
                  "phpdocumentor/reflection-common": "^2.0",
!                 "phpstan/phpdoc-parser": "^2.0"
              },
              "require-dev": {
                  "ext-tokenizer": "*",
                  "phpbench/phpbench": "^1.2",
!                 "phpstan/extension-installer": "^1.4",
!                 "phpstan/phpstan": "^2.1",
!                 "phpstan/phpstan-phpunit": "^2.0",
                  "phpunit/phpunit": "^9.5",
!                 "psalm/phar": "^4"
              },
              "type": "library",
              "extra": {
                  "branch-alias": {
!                     "dev-1.x": "1.x-dev",
!                     "dev-2.x": "2.x-dev"
                  }
              },
              "autoload": {
***************
*** 7776,7797 ****
              "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
              "support": {
                  "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
!                 "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0"
              },
!             "time": "2024-11-09T15:12:26+00:00"
          },
          {
              "name": "phpstan/phpdoc-parser",
!             "version": "2.3.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/phpstan/phpdoc-parser.git",
!                 "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495",
!                 "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495",
                  "shasum": ""
              },
              "require": {
--- 8640,8661 ----
              "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
              "support": {
                  "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
!                 "source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0"
              },
!             "time": "2026-01-06T21:53:42+00:00"
          },
          {
              "name": "phpstan/phpdoc-parser",
!             "version": "2.3.2",
              "source": {
                  "type": "git",
                  "url": "https://github.com/phpstan/phpdoc-parser.git",
!                 "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a",
!                 "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a",
                  "shasum": ""
              },
              "require": {
***************
*** 7823,7831 ****
              "description": "PHPDoc parser with support for nullable, intersection and generic types",
              "support": {
                  "issues": "https://github.com/phpstan/phpdoc-parser/issues",
!                 "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0"
              },
!             "time": "2025-08-30T15:50:23+00:00"
          },
          {
              "name": "phpunit/php-code-coverage",
--- 8687,8695 ----
              "description": "PHPDoc parser with support for nullable, intersection and generic types",
              "support": {
                  "issues": "https://github.com/phpstan/phpdoc-parser/issues",
!                 "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2"
              },
!             "time": "2026-01-25T14:56:51+00:00"
          },
          {
              "name": "phpunit/php-code-coverage",
***************
*** 8150,8165 ****
          },
          {
              "name": "phpunit/phpunit",
!             "version": "10.5.55",
              "source": {
                  "type": "git",
                  "url": "https://github.com/sebastianbergmann/phpunit.git",
!                 "reference": "4b2d546b336876bd9562f24641b08a25335b06b6"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4b2d546b336876bd9562f24641b08a25335b06b6",
!                 "reference": "4b2d546b336876bd9562f24641b08a25335b06b6",
                  "shasum": ""
              },
              "require": {
--- 9014,9029 ----
          },
          {
              "name": "phpunit/phpunit",
!             "version": "10.5.63",
              "source": {
                  "type": "git",
                  "url": "https://github.com/sebastianbergmann/phpunit.git",
!                 "reference": "33198268dad71e926626b618f3ec3966661e4d90"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90",
!                 "reference": "33198268dad71e926626b618f3ec3966661e4d90",
                  "shasum": ""
              },
              "require": {
***************
*** 8180,8189 ****
                  "phpunit/php-timer": "^6.0.0",
                  "sebastian/cli-parser": "^2.0.1",
                  "sebastian/code-unit": "^2.0.0",
!                 "sebastian/comparator": "^5.0.4",
                  "sebastian/diff": "^5.1.1",
                  "sebastian/environment": "^6.1.0",
!                 "sebastian/exporter": "^5.1.2",
                  "sebastian/global-state": "^6.0.2",
                  "sebastian/object-enumerator": "^5.0.0",
                  "sebastian/recursion-context": "^5.0.1",
--- 9044,9053 ----
                  "phpunit/php-timer": "^6.0.0",
                  "sebastian/cli-parser": "^2.0.1",
                  "sebastian/code-unit": "^2.0.0",
!                 "sebastian/comparator": "^5.0.5",
                  "sebastian/diff": "^5.1.1",
                  "sebastian/environment": "^6.1.0",
!                 "sebastian/exporter": "^5.1.4",
                  "sebastian/global-state": "^6.0.2",
                  "sebastian/object-enumerator": "^5.0.0",
                  "sebastian/recursion-context": "^5.0.1",
***************
*** 8231,8237 ****
              "support": {
                  "issues": "https://github.com/sebastianbergmann/phpunit/issues",
                  "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
!                 "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.55"
              },
              "funding": [
                  {
--- 9095,9101 ----
              "support": {
                  "issues": "https://github.com/sebastianbergmann/phpunit/issues",
                  "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
!                 "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63"
              },
              "funding": [
                  {
***************
*** 8255,8292 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-09-14T06:19:20+00:00"
          },
          {
              "name": "psalm/plugin-symfony",
!             "version": "v5.2.6",
              "source": {
                  "type": "git",
                  "url": "https://github.com/psalm/psalm-plugin-symfony.git",
!                 "reference": "1638f172b4ae5f283ee6b1b58fff3e44aa628742"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/psalm/psalm-plugin-symfony/zipball/1638f172b4ae5f283ee6b1b58fff3e44aa628742",
!                 "reference": "1638f172b4ae5f283ee6b1b58fff3e44aa628742",
                  "shasum": ""
              },
              "require": {
                  "ext-simplexml": "*",
!                 "php": "^8.1",
!                 "symfony/framework-bundle": "^5.0 || ^6.0 || ^7.0",
!                 "vimeo/psalm": "^5.16 || dev-master"
              },
              "require-dev": {
                  "doctrine/annotations": "^1.8|^2",
!                 "doctrine/orm": "^2.9",
                  "phpunit/phpunit": "~7.5 || ~9.5",
!                 "symfony/cache-contracts": "^1.0 || ^2.0",
                  "symfony/console": "*",
!                 "symfony/form": "^5.0 || ^6.0 || ^7.0",
!                 "symfony/messenger": "^5.0 || ^6.0 || ^7.0",
                  "symfony/security-core": "*",
!                 "symfony/serializer": "^5.0 || ^6.0 || ^7.0",
                  "symfony/validator": "*",
                  "twig/twig": "^2.10 || ^3.0",
                  "weirdan/codeception-psalm-module": "dev-master"
--- 9119,9157 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-01-27T05:48:37+00:00"
          },
          {
              "name": "psalm/plugin-symfony",
!             "version": "v5.3.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/psalm/psalm-plugin-symfony.git",
!                 "reference": "df874ff48ae3b1833ea17fba7c21b31a4d682614"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/psalm/psalm-plugin-symfony/zipball/df874ff48ae3b1833ea17fba7c21b31a4d682614",
!                 "reference": "df874ff48ae3b1833ea17fba7c21b31a4d682614",
                  "shasum": ""
              },
              "require": {
                  "ext-simplexml": "*",
!                 "php": ">=8.1",
!                 "symfony/expression-language": "^5.0 || ^6.0 || ^7.0 || ^8.0",
!                 "symfony/framework-bundle": "^5.0 || ^6.0 || ^7.0 || ^8.0",
!                 "vimeo/psalm": "^6 || dev-master"
              },
              "require-dev": {
                  "doctrine/annotations": "^1.8|^2",
!                 "doctrine/orm": "^2.9 || ^3.6",
                  "phpunit/phpunit": "~7.5 || ~9.5",
!                 "symfony/cache-contracts": "^1.0 || ^2.0 || ^3.0",
                  "symfony/console": "*",
!                 "symfony/form": "^5.0 || ^6.0 || ^7.0 || ^8.0",
!                 "symfony/messenger": "^5.0 || ^6.0 || ^7.0 || ^8.0",
                  "symfony/security-core": "*",
!                 "symfony/serializer": "^5.0 || ^6.0 || ^7.0 || ^8.0",
                  "symfony/validator": "*",
                  "twig/twig": "^2.10 || ^3.0",
                  "weirdan/codeception-psalm-module": "dev-master"
***************
*** 8318,8326 ****
              "description": "Psalm Plugin for Symfony",
              "support": {
                  "issues": "https://github.com/psalm/psalm-plugin-symfony/issues",
!                 "source": "https://github.com/psalm/psalm-plugin-symfony/tree/v5.2.6"
              },
!             "time": "2024-12-21T08:46:26+00:00"
          },
          {
              "name": "sebastian/cli-parser",
--- 9183,9263 ----
              "description": "Psalm Plugin for Symfony",
              "support": {
                  "issues": "https://github.com/psalm/psalm-plugin-symfony/issues",
!                 "source": "https://github.com/psalm/psalm-plugin-symfony/tree/v5.3.0"
              },
!             "time": "2026-02-12T11:24:16+00:00"
!         },
!         {
!             "name": "revolt/event-loop",
!             "version": "v1.0.8",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/revoltphp/event-loop.git",
!                 "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/b6fc06dce8e9b523c9946138fa5e62181934f91c",
!                 "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c",
!                 "shasum": ""
!             },
!             "require": {
!                 "php": ">=8.1"
!             },
!             "require-dev": {
!                 "ext-json": "*",
!                 "jetbrains/phpstorm-stubs": "^2019.3",
!                 "phpunit/phpunit": "^9",
!                 "psalm/phar": "^5.15"
!             },
!             "type": "library",
!             "extra": {
!                 "branch-alias": {
!                     "dev-main": "1.x-dev"
!                 }
!             },
!             "autoload": {
!                 "psr-4": {
!                     "Revolt\\": "src"
!                 }
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "MIT"
!             ],
!             "authors": [
!                 {
!                     "name": "Aaron Piotrowski",
!                     "email": "aaron@trowski.com"
!                 },
!                 {
!                     "name": "Cees-Jan Kiewiet",
!                     "email": "ceesjank@gmail.com"
!                 },
!                 {
!                     "name": "Christian Lück",
!                     "email": "christian@clue.engineering"
!                 },
!                 {
!                     "name": "Niklas Keller",
!                     "email": "me@kelunik.com"
!                 }
!             ],
!             "description": "Rock-solid event loop for concurrent PHP applications.",
!             "keywords": [
!                 "async",
!                 "asynchronous",
!                 "concurrency",
!                 "event",
!                 "event-loop",
!                 "non-blocking",
!                 "scheduler"
!             ],
!             "support": {
!                 "issues": "https://github.com/revoltphp/event-loop/issues",
!                 "source": "https://github.com/revoltphp/event-loop/tree/v1.0.8"
!             },
!             "time": "2025-08-27T21:33:23+00:00"
          },
          {
              "name": "sebastian/cli-parser",
***************
*** 8492,8507 ****
          },
          {
              "name": "sebastian/comparator",
!             "version": "5.0.4",
              "source": {
                  "type": "git",
                  "url": "https://github.com/sebastianbergmann/comparator.git",
!                 "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e",
!                 "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e",
                  "shasum": ""
              },
              "require": {
--- 9429,9444 ----
          },
          {
              "name": "sebastian/comparator",
!             "version": "5.0.5",
              "source": {
                  "type": "git",
                  "url": "https://github.com/sebastianbergmann/comparator.git",
!                 "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
!                 "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
                  "shasum": ""
              },
              "require": {
***************
*** 8557,8563 ****
              "support": {
                  "issues": "https://github.com/sebastianbergmann/comparator/issues",
                  "security": "https://github.com/sebastianbergmann/comparator/security/policy",
!                 "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4"
              },
              "funding": [
                  {
--- 9494,9500 ----
              "support": {
                  "issues": "https://github.com/sebastianbergmann/comparator/issues",
                  "security": "https://github.com/sebastianbergmann/comparator/security/policy",
!                 "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5"
              },
              "funding": [
                  {
***************
*** 8577,8583 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-09-07T05:25:07+00:00"
          },
          {
              "name": "sebastian/complexity",
--- 9514,9520 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-01-24T09:25:16+00:00"
          },
          {
              "name": "sebastian/complexity",
***************
*** 8770,8785 ****
          },
          {
              "name": "sebastian/exporter",
!             "version": "5.1.2",
              "source": {
                  "type": "git",
                  "url": "https://github.com/sebastianbergmann/exporter.git",
!                 "reference": "955288482d97c19a372d3f31006ab3f37da47adf"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf",
!                 "reference": "955288482d97c19a372d3f31006ab3f37da47adf",
                  "shasum": ""
              },
              "require": {
--- 9707,9722 ----
          },
          {
              "name": "sebastian/exporter",
!             "version": "5.1.4",
              "source": {
                  "type": "git",
                  "url": "https://github.com/sebastianbergmann/exporter.git",
!                 "reference": "0735b90f4da94969541dac1da743446e276defa6"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6",
!                 "reference": "0735b90f4da94969541dac1da743446e276defa6",
                  "shasum": ""
              },
              "require": {
***************
*** 8788,8794 ****
                  "sebastian/recursion-context": "^5.0"
              },
              "require-dev": {
!                 "phpunit/phpunit": "^10.0"
              },
              "type": "library",
              "extra": {
--- 9725,9731 ----
                  "sebastian/recursion-context": "^5.0"
              },
              "require-dev": {
!                 "phpunit/phpunit": "^10.5"
              },
              "type": "library",
              "extra": {
***************
*** 8836,8850 ****
              "support": {
                  "issues": "https://github.com/sebastianbergmann/exporter/issues",
                  "security": "https://github.com/sebastianbergmann/exporter/security/policy",
!                 "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2"
              },
              "funding": [
                  {
                      "url": "https://github.com/sebastianbergmann",
                      "type": "github"
                  }
              ],
!             "time": "2024-03-02T07:17:12+00:00"
          },
          {
              "name": "sebastian/global-state",
--- 9773,9799 ----
              "support": {
                  "issues": "https://github.com/sebastianbergmann/exporter/issues",
                  "security": "https://github.com/sebastianbergmann/exporter/security/policy",
!                 "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4"
              },
              "funding": [
                  {
                      "url": "https://github.com/sebastianbergmann",
                      "type": "github"
+                 },
+                 {
+                     "url": "https://liberapay.com/sebastianbergmann",
+                     "type": "liberapay"
+                 },
+                 {
+                     "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                     "type": "thanks_dev"
+                 },
+                 {
+                     "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
+                     "type": "tidelift"
                  }
              ],
!             "time": "2025-09-24T06:09:11+00:00"
          },
          {
              "name": "sebastian/global-state",
***************
*** 9265,9280 ****
          },
          {
              "name": "spatie/array-to-xml",
!             "version": "3.4.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/spatie/array-to-xml.git",
!                 "reference": "7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67",
!                 "reference": "7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67",
                  "shasum": ""
              },
              "require": {
--- 10214,10229 ----
          },
          {
              "name": "spatie/array-to-xml",
!             "version": "3.4.4",
              "source": {
                  "type": "git",
                  "url": "https://github.com/spatie/array-to-xml.git",
!                 "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/88b2f3852a922dd73177a68938f8eb2ec70c7224",
!                 "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224",
                  "shasum": ""
              },
              "require": {
***************
*** 9317,9323 ****
                  "xml"
              ],
              "support": {
!                 "source": "https://github.com/spatie/array-to-xml/tree/3.4.0"
              },
              "funding": [
                  {
--- 10266,10272 ----
                  "xml"
              ],
              "support": {
!                 "source": "https://github.com/spatie/array-to-xml/tree/3.4.4"
              },
              "funding": [
                  {
***************
*** 9329,9369 ****
                      "type": "github"
                  }
              ],
!             "time": "2024-12-16T12:45:15+00:00"
          },
          {
              "name": "squizlabs/php_codesniffer",
!             "version": "3.13.4",
              "source": {
                  "type": "git",
                  "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
!                 "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/ad545ea9c1b7d270ce0fc9cbfb884161cd706119",
!                 "reference": "ad545ea9c1b7d270ce0fc9cbfb884161cd706119",
                  "shasum": ""
              },
              "require": {
                  "ext-simplexml": "*",
                  "ext-tokenizer": "*",
                  "ext-xmlwriter": "*",
!                 "php": ">=5.4.0"
              },
              "require-dev": {
!                 "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
              },
              "bin": [
                  "bin/phpcbf",
                  "bin/phpcs"
              ],
              "type": "library",
-             "extra": {
-                 "branch-alias": {
-                     "dev-master": "3.x-dev"
-                 }
-             },
              "notification-url": "https://packagist.org/downloads/",
              "license": [
                  "BSD-3-Clause"
--- 10278,10313 ----
                      "type": "github"
                  }
              ],
!             "time": "2025-12-15T09:00:41+00:00"
          },
          {
              "name": "squizlabs/php_codesniffer",
!             "version": "4.0.1",
              "source": {
                  "type": "git",
                  "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
!                 "reference": "0525c73950de35ded110cffafb9892946d7771b5"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5",
!                 "reference": "0525c73950de35ded110cffafb9892946d7771b5",
                  "shasum": ""
              },
              "require": {
                  "ext-simplexml": "*",
                  "ext-tokenizer": "*",
                  "ext-xmlwriter": "*",
!                 "php": ">=7.2.0"
              },
              "require-dev": {
!                 "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31"
              },
              "bin": [
                  "bin/phpcbf",
                  "bin/phpcs"
              ],
              "type": "library",
              "notification-url": "https://packagist.org/downloads/",
              "license": [
                  "BSD-3-Clause"
***************
*** 9382,9388 ****
                      "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors"
                  }
              ],
!             "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
              "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
              "keywords": [
                  "phpcs",
--- 10326,10332 ----
                      "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors"
                  }
              ],
!             "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.",
              "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
              "keywords": [
                  "phpcs",
***************
*** 9413,9443 ****
                      "type": "thanks_dev"
                  }
              ],
!             "time": "2025-09-05T05:47:09+00:00"
          },
          {
              "name": "symfony/browser-kit",
!             "version": "v6.4.24",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/browser-kit.git",
!                 "reference": "3537d17782f8c20795b194acb6859071b60c6fac"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/browser-kit/zipball/3537d17782f8c20795b194acb6859071b60c6fac",
!                 "reference": "3537d17782f8c20795b194acb6859071b60c6fac",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
!                 "symfony/dom-crawler": "^5.4|^6.0|^7.0"
              },
              "require-dev": {
!                 "symfony/css-selector": "^5.4|^6.0|^7.0",
!                 "symfony/http-client": "^5.4|^6.0|^7.0",
!                 "symfony/mime": "^5.4|^6.0|^7.0",
!                 "symfony/process": "^5.4|^6.0|^7.0"
              },
              "type": "library",
              "autoload": {
--- 10357,10388 ----
                      "type": "thanks_dev"
                  }
              ],
!             "time": "2025-11-10T16:43:36+00:00"
          },
          {
              "name": "symfony/browser-kit",
!             "version": "v7.4.4",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/browser-kit.git",
!                 "reference": "bed167eadaaba641f51fc842c9227aa5e251309e"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/browser-kit/zipball/bed167eadaaba641f51fc842c9227aa5e251309e",
!                 "reference": "bed167eadaaba641f51fc842c9227aa5e251309e",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
!                 "symfony/deprecation-contracts": "^2.5|^3",
!                 "symfony/dom-crawler": "^6.4|^7.0|^8.0"
              },
              "require-dev": {
!                 "symfony/css-selector": "^6.4|^7.0|^8.0",
!                 "symfony/http-client": "^6.4|^7.0|^8.0",
!                 "symfony/mime": "^6.4|^7.0|^8.0",
!                 "symfony/process": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 9465,9471 ****
              "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/browser-kit/tree/v6.4.24"
              },
              "funding": [
                  {
--- 10410,10416 ----
              "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/browser-kit/tree/v7.4.4"
              },
              "funding": [
                  {
***************
*** 9485,9519 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-07-10T08:14:14+00:00"
          },
          {
              "name": "symfony/cache",
!             "version": "v6.4.24",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/cache.git",
!                 "reference": "d038cd3054aeaf1c674022a77048b2ef6376a175"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/cache/zipball/d038cd3054aeaf1c674022a77048b2ef6376a175",
!                 "reference": "d038cd3054aeaf1c674022a77048b2ef6376a175",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1",
                  "psr/cache": "^2.0|^3.0",
                  "psr/log": "^1.1|^2|^3",
!                 "symfony/cache-contracts": "^2.5|^3",
                  "symfony/service-contracts": "^2.5|^3",
!                 "symfony/var-exporter": "^6.3.6|^7.0"
              },
              "conflict": {
!                 "doctrine/dbal": "<2.13.1",
!                 "symfony/dependency-injection": "<5.4",
!                 "symfony/http-kernel": "<5.4",
!                 "symfony/var-dumper": "<5.4"
              },
              "provide": {
                  "psr/cache-implementation": "2.0|3.0",
--- 10430,10467 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-01-13T10:40:19+00:00"
          },
          {
              "name": "symfony/cache",
!             "version": "v7.4.7",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/cache.git",
!                 "reference": "665522ec357540e66c294c08583b40ee576574f0"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/cache/zipball/665522ec357540e66c294c08583b40ee576574f0",
!                 "reference": "665522ec357540e66c294c08583b40ee576574f0",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2",
                  "psr/cache": "^2.0|^3.0",
                  "psr/log": "^1.1|^2|^3",
!                 "symfony/cache-contracts": "^3.6",
!                 "symfony/deprecation-contracts": "^2.5|^3",
                  "symfony/service-contracts": "^2.5|^3",
!                 "symfony/var-exporter": "^6.4|^7.0|^8.0"
              },
              "conflict": {
!                 "doctrine/dbal": "<3.6",
!                 "ext-redis": "<6.1",
!                 "ext-relay": "<0.12.1",
!                 "symfony/dependency-injection": "<6.4",
!                 "symfony/http-kernel": "<6.4",
!                 "symfony/var-dumper": "<6.4"
              },
              "provide": {
                  "psr/cache-implementation": "2.0|3.0",
***************
*** 9522,9536 ****
              },
              "require-dev": {
                  "cache/integration-tests": "dev-master",
!                 "doctrine/dbal": "^2.13.1|^3|^4",
                  "predis/predis": "^1.1|^2.0",
                  "psr/simple-cache": "^1.0|^2.0|^3.0",
!                 "symfony/config": "^5.4|^6.0|^7.0",
!                 "symfony/dependency-injection": "^5.4|^6.0|^7.0",
!                 "symfony/filesystem": "^5.4|^6.0|^7.0",
!                 "symfony/http-kernel": "^5.4|^6.0|^7.0",
!                 "symfony/messenger": "^5.4|^6.0|^7.0",
!                 "symfony/var-dumper": "^5.4|^6.0|^7.0"
              },
              "type": "library",
              "autoload": {
--- 10470,10485 ----
              },
              "require-dev": {
                  "cache/integration-tests": "dev-master",
!                 "doctrine/dbal": "^3.6|^4",
                  "predis/predis": "^1.1|^2.0",
                  "psr/simple-cache": "^1.0|^2.0|^3.0",
!                 "symfony/clock": "^6.4|^7.0|^8.0",
!                 "symfony/config": "^6.4|^7.0|^8.0",
!                 "symfony/dependency-injection": "^6.4|^7.0|^8.0",
!                 "symfony/filesystem": "^6.4|^7.0|^8.0",
!                 "symfony/http-kernel": "^6.4|^7.0|^8.0",
!                 "symfony/messenger": "^6.4|^7.0|^8.0",
!                 "symfony/var-dumper": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 9565,9571 ****
                  "psr6"
              ],
              "support": {
!                 "source": "https://github.com/symfony/cache/tree/v6.4.24"
              },
              "funding": [
                  {
--- 10514,10520 ----
                  "psr6"
              ],
              "support": {
!                 "source": "https://github.com/symfony/cache/tree/v7.4.7"
              },
              "funding": [
                  {
***************
*** 9585,9591 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-07-30T09:32:03+00:00"
          },
          {
              "name": "symfony/cache-contracts",
--- 10534,10540 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-03-06T08:14:57+00:00"
          },
          {
              "name": "symfony/cache-contracts",
***************
*** 9665,9684 ****
          },
          {
              "name": "symfony/css-selector",
!             "version": "v6.4.24",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/css-selector.git",
!                 "reference": "9b784413143701aa3c94ac1869a159a9e53e8761"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/css-selector/zipball/9b784413143701aa3c94ac1869a159a9e53e8761",
!                 "reference": "9b784413143701aa3c94ac1869a159a9e53e8761",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.1"
              },
              "type": "library",
              "autoload": {
--- 10614,10633 ----
          },
          {
              "name": "symfony/css-selector",
!             "version": "v7.4.6",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/css-selector.git",
!                 "reference": "2e7c52c647b406e2107dd867db424a4dbac91864"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/css-selector/zipball/2e7c52c647b406e2107dd867db424a4dbac91864",
!                 "reference": "2e7c52c647b406e2107dd867db424a4dbac91864",
                  "shasum": ""
              },
              "require": {
!                 "php": ">=8.2"
              },
              "type": "library",
              "autoload": {
***************
*** 9710,9716 ****
              "description": "Converts CSS selectors to XPath expressions",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/css-selector/tree/v6.4.24"
              },
              "funding": [
                  {
--- 10659,10665 ----
              "description": "Converts CSS selectors to XPath expressions",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/css-selector/tree/v7.4.6"
              },
              "funding": [
                  {
***************
*** 9730,9759 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-07-10T08:14:14+00:00"
          },
          {
              "name": "symfony/dom-crawler",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/dom-crawler.git",
!                 "reference": "976302990f9f2a6d4c07206836dd4ca77cae9524"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/976302990f9f2a6d4c07206836dd4ca77cae9524",
!                 "reference": "976302990f9f2a6d4c07206836dd4ca77cae9524",
                  "shasum": ""
              },
              "require": {
                  "masterminds/html5": "^2.6",
!                 "php": ">=8.1",
                  "symfony/polyfill-ctype": "~1.8",
                  "symfony/polyfill-mbstring": "~1.0"
              },
              "require-dev": {
!                 "symfony/css-selector": "^5.4|^6.0|^7.0"
              },
              "type": "library",
              "autoload": {
--- 10679,10709 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-02-17T07:53:42+00:00"
          },
          {
              "name": "symfony/dom-crawler",
!             "version": "v7.4.6",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/dom-crawler.git",
!                 "reference": "487ba8fa43da9a8e6503fe939b45ecd96875410e"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/487ba8fa43da9a8e6503fe939b45ecd96875410e",
!                 "reference": "487ba8fa43da9a8e6503fe939b45ecd96875410e",
                  "shasum": ""
              },
              "require": {
                  "masterminds/html5": "^2.6",
!                 "php": ">=8.2",
!                 "symfony/deprecation-contracts": "^2.5|^3",
                  "symfony/polyfill-ctype": "~1.8",
                  "symfony/polyfill-mbstring": "~1.0"
              },
              "require-dev": {
!                 "symfony/css-selector": "^6.4|^7.0|^8.0"
              },
              "type": "library",
              "autoload": {
***************
*** 9781,9787 ****
              "description": "Eases DOM navigation for HTML and XML documents",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/dom-crawler/tree/v6.4.25"
              },
              "funding": [
                  {
--- 10731,10737 ----
              "description": "Eases DOM navigation for HTML and XML documents",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/dom-crawler/tree/v7.4.6"
              },
              "funding": [
                  {
***************
*** 9801,9912 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-05T18:56:08+00:00"
          },
          {
              "name": "symfony/framework-bundle",
!             "version": "v6.4.25",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/framework-bundle.git",
!                 "reference": "1d6a764b58e4f780df00f71c20ba3a61095ea447"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/1d6a764b58e4f780df00f71c20ba3a61095ea447",
!                 "reference": "1d6a764b58e4f780df00f71c20ba3a61095ea447",
                  "shasum": ""
              },
              "require": {
                  "composer-runtime-api": ">=2.1",
                  "ext-xml": "*",
!                 "php": ">=8.1",
!                 "symfony/cache": "^5.4|^6.0|^7.0",
!                 "symfony/config": "^6.1|^7.0",
!                 "symfony/dependency-injection": "^6.4.12|^7.0",
                  "symfony/deprecation-contracts": "^2.5|^3",
!                 "symfony/error-handler": "^6.1|^7.0",
!                 "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
!                 "symfony/filesystem": "^5.4|^6.0|^7.0",
!                 "symfony/finder": "^5.4|^6.0|^7.0",
!                 "symfony/http-foundation": "^6.4|^7.0",
!                 "symfony/http-kernel": "^6.4",
                  "symfony/polyfill-mbstring": "~1.0",
!                 "symfony/routing": "^6.4|^7.0"
              },
              "conflict": {
-                 "doctrine/annotations": "<1.13.1",
                  "doctrine/persistence": "<1.3",
!                 "phpdocumentor/reflection-docblock": "<3.2.2",
!                 "phpdocumentor/type-resolver": "<1.4.0",
!                 "symfony/asset": "<5.4",
                  "symfony/asset-mapper": "<6.4",
!                 "symfony/clock": "<6.3",
!                 "symfony/console": "<5.4|>=7.0",
                  "symfony/dom-crawler": "<6.4",
!                 "symfony/dotenv": "<5.4",
!                 "symfony/form": "<5.4",
!                 "symfony/http-client": "<6.3",
!                 "symfony/lock": "<5.4",
!                 "symfony/mailer": "<5.4",
!                 "symfony/messenger": "<6.3",
                  "symfony/mime": "<6.4",
!                 "symfony/property-access": "<5.4",
!                 "symfony/property-info": "<5.4",
!                 "symfony/runtime": "<5.4.45|>=6.0,<6.4.13|>=7.0,<7.1.6",
                  "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4",
!                 "symfony/security-core": "<5.4",
!                 "symfony/security-csrf": "<5.4",
!                 "symfony/serializer": "<6.4",
!                 "symfony/stopwatch": "<5.4",
!                 "symfony/translation": "<6.4",
!                 "symfony/twig-bridge": "<5.4",
!                 "symfony/twig-bundle": "<5.4",
                  "symfony/validator": "<6.4",
                  "symfony/web-profiler-bundle": "<6.4",
!                 "symfony/workflow": "<6.4"
              },
              "require-dev": {
-                 "doctrine/annotations": "^1.13.1|^2",
                  "doctrine/persistence": "^1.3|^2|^3",
                  "dragonmantank/cron-expression": "^3.1",
!                 "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
                  "seld/jsonlint": "^1.10",
!                 "symfony/asset": "^5.4|^6.0|^7.0",
!                 "symfony/asset-mapper": "^6.4|^7.0",
!                 "symfony/browser-kit": "^5.4|^6.0|^7.0",
!                 "symfony/clock": "^6.2|^7.0",
!                 "symfony/console": "^5.4.9|^6.0.9|^7.0",
!                 "symfony/css-selector": "^5.4|^6.0|^7.0",
!                 "symfony/dom-crawler": "^6.4|^7.0",
!                 "symfony/dotenv": "^5.4|^6.0|^7.0",
!                 "symfony/expression-language": "^5.4|^6.0|^7.0",
!                 "symfony/form": "^5.4|^6.0|^7.0",
!                 "symfony/html-sanitizer": "^6.1|^7.0",
!                 "symfony/http-client": "^6.3|^7.0",
!                 "symfony/lock": "^5.4|^6.0|^7.0",
!                 "symfony/mailer": "^5.4|^6.0|^7.0",
!                 "symfony/messenger": "^6.3|^7.0",
!                 "symfony/mime": "^6.4|^7.0",
!                 "symfony/notifier": "^5.4|^6.0|^7.0",
                  "symfony/polyfill-intl-icu": "~1.0",
!                 "symfony/process": "^5.4|^6.0|^7.0",
!                 "symfony/property-info": "^5.4|^6.0|^7.0",
!                 "symfony/rate-limiter": "^5.4|^6.0|^7.0",
!                 "symfony/scheduler": "^6.4.4|^7.0.4",
!                 "symfony/security-bundle": "^5.4|^6.0|^7.0",
!                 "symfony/semaphore": "^5.4|^6.0|^7.0",
!                 "symfony/serializer": "^6.4|^7.0",
!                 "symfony/stopwatch": "^5.4|^6.0|^7.0",
!                 "symfony/string": "^5.4|^6.0|^7.0",
!                 "symfony/translation": "^6.4|^7.0",
!                 "symfony/twig-bundle": "^5.4|^6.0|^7.0",
!                 "symfony/uid": "^5.4|^6.0|^7.0",
!                 "symfony/validator": "^6.4|^7.0",
!                 "symfony/web-link": "^5.4|^6.0|^7.0",
!                 "symfony/workflow": "^6.4|^7.0",
!                 "symfony/yaml": "^5.4|^6.0|^7.0",
!                 "twig/twig": "^2.10|^3.0.4"
              },
              "type": "symfony-bundle",
              "autoload": {
--- 10751,10935 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-02-17T07:53:42+00:00"
!         },
!         {
!             "name": "symfony/expression-language",
!             "version": "v7.4.4",
!             "source": {
!                 "type": "git",
!                 "url": "https://github.com/symfony/expression-language.git",
!                 "reference": "f3a6497eb6573e185f2ec41cd3b3f0cd68ddf667"
!             },
!             "dist": {
!                 "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/expression-language/zipball/f3a6497eb6573e185f2ec41cd3b3f0cd68ddf667",
!                 "reference": "f3a6497eb6573e185f2ec41cd3b3f0cd68ddf667",
!                 "shasum": ""
!             },
!             "require": {
!                 "php": ">=8.2",
!                 "symfony/cache": "^6.4|^7.0|^8.0",
!                 "symfony/deprecation-contracts": "^2.5|^3",
!                 "symfony/service-contracts": "^2.5|^3"
!             },
!             "type": "library",
!             "autoload": {
!                 "psr-4": {
!                     "Symfony\\Component\\ExpressionLanguage\\": ""
!                 },
!                 "exclude-from-classmap": [
!                     "/Tests/"
!                 ]
!             },
!             "notification-url": "https://packagist.org/downloads/",
!             "license": [
!                 "MIT"
!             ],
!             "authors": [
!                 {
!                     "name": "Fabien Potencier",
!                     "email": "fabien@symfony.com"
!                 },
!                 {
!                     "name": "Symfony Community",
!                     "homepage": "https://symfony.com/contributors"
!                 }
!             ],
!             "description": "Provides an engine that can compile and evaluate expressions",
!             "homepage": "https://symfony.com",
!             "support": {
!                 "source": "https://github.com/symfony/expression-language/tree/v7.4.4"
!             },
!             "funding": [
!                 {
!                     "url": "https://symfony.com/sponsor",
!                     "type": "custom"
!                 },
!                 {
!                     "url": "https://github.com/fabpot",
!                     "type": "github"
!                 },
!                 {
!                     "url": "https://github.com/nicolas-grekas",
!                     "type": "github"
!                 },
!                 {
!                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
!                     "type": "tidelift"
!                 }
!             ],
!             "time": "2026-01-05T08:47:25+00:00"
          },
          {
              "name": "symfony/framework-bundle",
!             "version": "v7.4.7",
              "source": {
                  "type": "git",
                  "url": "https://github.com/symfony/framework-bundle.git",
!                 "reference": "c94bc78c85d76af67918404a95d44940f66a7c2f"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/c94bc78c85d76af67918404a95d44940f66a7c2f",
!                 "reference": "c94bc78c85d76af67918404a95d44940f66a7c2f",
                  "shasum": ""
              },
              "require": {
                  "composer-runtime-api": ">=2.1",
                  "ext-xml": "*",
!                 "php": ">=8.2",
!                 "symfony/cache": "^6.4.12|^7.0|^8.0",
!                 "symfony/config": "^7.4.4|^8.0.4",
!                 "symfony/dependency-injection": "^7.4.4|^8.0.4",
                  "symfony/deprecation-contracts": "^2.5|^3",
!                 "symfony/error-handler": "^7.3|^8.0",
!                 "symfony/event-dispatcher": "^6.4|^7.0|^8.0",
!                 "symfony/filesystem": "^7.1|^8.0",
!                 "symfony/finder": "^6.4|^7.0|^8.0",
!                 "symfony/http-foundation": "^7.4|^8.0",
!                 "symfony/http-kernel": "^7.4|^8.0",
                  "symfony/polyfill-mbstring": "~1.0",
!                 "symfony/polyfill-php85": "^1.32",
!                 "symfony/routing": "^7.4|^8.0"
              },
              "conflict": {
                  "doctrine/persistence": "<1.3",
!                 "phpdocumentor/reflection-docblock": "<5.2|>=7",
!                 "phpdocumentor/type-resolver": "<1.5.1",
!                 "symfony/asset": "<6.4",
                  "symfony/asset-mapper": "<6.4",
!                 "symfony/clock": "<6.4",
!                 "symfony/console": "<6.4",
                  "symfony/dom-crawler": "<6.4",
!                 "symfony/dotenv": "<6.4",
!                 "symfony/form": "<7.4",
!                 "symfony/http-client": "<6.4",
!                 "symfony/lock": "<6.4",
!                 "symfony/mailer": "<6.4",
!                 "symfony/messenger": "<7.4",
                  "symfony/mime": "<6.4",
!                 "symfony/property-access": "<6.4",
!                 "symfony/property-info": "<6.4",
!                 "symfony/runtime": "<6.4.13|>=7.0,<7.1.6",
                  "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4",
!                 "symfony/security-core": "<6.4",
!                 "symfony/security-csrf": "<7.2",
!                 "symfony/serializer": "<7.2.5",
!                 "symfony/stopwatch": "<6.4",
!                 "symfony/translation": "<7.3",
!                 "symfony/twig-bridge": "<6.4",
!                 "symfony/twig-bundle": "<6.4",
                  "symfony/validator": "<6.4",
                  "symfony/web-profiler-bundle": "<6.4",
!                 "symfony/webhook": "<7.2",
!                 "symfony/workflow": "<7.4"
              },
              "require-dev": {
                  "doctrine/persistence": "^1.3|^2|^3",
                  "dragonmantank/cron-expression": "^3.1",
!                 "phpdocumentor/reflection-docblock": "^5.2|^6.0",
                  "seld/jsonlint": "^1.10",
!                 "symfony/asset": "^6.4|^7.0|^8.0",
!                 "symfony/asset-mapper": "^6.4|^7.0|^8.0",
!                 "symfony/browser-kit": "^6.4|^7.0|^8.0",
!                 "symfony/clock": "^6.4|^7.0|^8.0",
!                 "symfony/console": "^6.4|^7.0|^8.0",
!                 "symfony/css-selector": "^6.4|^7.0|^8.0",
!                 "symfony/dom-crawler": "^6.4|^7.0|^8.0",
!                 "symfony/dotenv": "^6.4|^7.0|^8.0",
!                 "symfony/expression-language": "^6.4|^7.0|^8.0",
!                 "symfony/form": "^7.4|^8.0",
!                 "symfony/html-sanitizer": "^6.4|^7.0|^8.0",
!                 "symfony/http-client": "^6.4|^7.0|^8.0",
!                 "symfony/json-streamer": "^7.3|^8.0",
!                 "symfony/lock": "^6.4|^7.0|^8.0",
!                 "symfony/mailer": "^6.4|^7.0|^8.0",
!                 "symfony/messenger": "^7.4|^8.0",
!                 "symfony/mime": "^6.4|^7.0|^8.0",
!                 "symfony/notifier": "^6.4|^7.0|^8.0",
!                 "symfony/object-mapper": "^7.3|^8.0",
                  "symfony/polyfill-intl-icu": "~1.0",
!                 "symfony/process": "^6.4|^7.0|^8.0",
!                 "symfony/property-info": "^6.4|^7.0|^8.0",
!                 "symfony/rate-limiter": "^6.4|^7.0|^8.0",
!                 "symfony/runtime": "^6.4.13|^7.1.6|^8.0",
!                 "symfony/scheduler": "^6.4.4|^7.0.4|^8.0",
!                 "symfony/security-bundle": "^6.4|^7.0|^8.0",
!                 "symfony/semaphore": "^6.4|^7.0|^8.0",
!                 "symfony/serializer": "^7.2.5|^8.0",
!                 "symfony/stopwatch": "^6.4|^7.0|^8.0",
!                 "symfony/string": "^6.4|^7.0|^8.0",
!                 "symfony/translation": "^7.3|^8.0",
!                 "symfony/twig-bundle": "^6.4|^7.0|^8.0",
!                 "symfony/type-info": "^7.1.8|^8.0",
!                 "symfony/uid": "^6.4|^7.0|^8.0",
!                 "symfony/validator": "^7.4|^8.0",
!                 "symfony/web-link": "^6.4|^7.0|^8.0",
!                 "symfony/webhook": "^7.2|^8.0",
!                 "symfony/workflow": "^7.4|^8.0",
!                 "symfony/yaml": "^7.3|^8.0",
!                 "twig/twig": "^3.12"
              },
              "type": "symfony-bundle",
              "autoload": {
***************
*** 9934,9940 ****
              "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/framework-bundle/tree/v6.4.25"
              },
              "funding": [
                  {
--- 10957,10963 ----
              "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework",
              "homepage": "https://symfony.com",
              "support": {
!                 "source": "https://github.com/symfony/framework-bundle/tree/v7.4.7"
              },
              "funding": [
                  {
***************
*** 9954,9973 ****
                      "type": "tidelift"
                  }
              ],
!             "time": "2025-08-26T10:44:20+00:00"
          },
          {
              "name": "theseer/tokenizer",
!             "version": "1.2.3",
              "source": {
                  "type": "git",
                  "url": "https://github.com/theseer/tokenizer.git",
!                 "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
!                 "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
                  "shasum": ""
              },
              "require": {
--- 10977,10996 ----
                      "type": "tidelift"
                  }
              ],
!             "time": "2026-03-06T15:39:55+00:00"
          },
          {
              "name": "theseer/tokenizer",
!             "version": "1.3.1",
              "source": {
                  "type": "git",
                  "url": "https://github.com/theseer/tokenizer.git",
!                 "reference": "b7489ce515e168639d17feec34b8847c326b0b3c"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c",
!                 "reference": "b7489ce515e168639d17feec34b8847c326b0b3c",
                  "shasum": ""
              },
              "require": {
***************
*** 9996,10002 ****
              "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
              "support": {
                  "issues": "https://github.com/theseer/tokenizer/issues",
!                 "source": "https://github.com/theseer/tokenizer/tree/1.2.3"
              },
              "funding": [
                  {
--- 11019,11025 ----
              "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
              "support": {
                  "issues": "https://github.com/theseer/tokenizer/issues",
!                 "source": "https://github.com/theseer/tokenizer/tree/1.3.1"
              },
              "funding": [
                  {
***************
*** 10004,10031 ****
                      "type": "github"
                  }
              ],
!             "time": "2024-03-03T12:36:25+00:00"
          },
          {
              "name": "vimeo/psalm",
!             "version": "5.26.1",
              "source": {
                  "type": "git",
                  "url": "https://github.com/vimeo/psalm.git",
!                 "reference": "d747f6500b38ac4f7dfc5edbcae6e4b637d7add0"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/vimeo/psalm/zipball/d747f6500b38ac4f7dfc5edbcae6e4b637d7add0",
!                 "reference": "d747f6500b38ac4f7dfc5edbcae6e4b637d7add0",
                  "shasum": ""
              },
              "require": {
!                 "amphp/amp": "^2.4.2",
!                 "amphp/byte-stream": "^1.5",
                  "composer-runtime-api": "^2",
                  "composer/semver": "^1.4 || ^2.0 || ^3.0",
                  "composer/xdebug-handler": "^2.0 || ^3.0",
                  "dnoegel/php-xdg-base-dir": "^0.1.1",
                  "ext-ctype": "*",
                  "ext-dom": "*",
--- 11027,11056 ----
                      "type": "github"
                  }
              ],
!             "time": "2025-11-17T20:03:58+00:00"
          },
          {
              "name": "vimeo/psalm",
!             "version": "6.16.1",
              "source": {
                  "type": "git",
                  "url": "https://github.com/vimeo/psalm.git",
!                 "reference": "f1f5de594dc76faf8784e02d3dc4716c91c6f6ac"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/vimeo/psalm/zipball/f1f5de594dc76faf8784e02d3dc4716c91c6f6ac",
!                 "reference": "f1f5de594dc76faf8784e02d3dc4716c91c6f6ac",
                  "shasum": ""
              },
              "require": {
!                 "amphp/amp": "^3",
!                 "amphp/byte-stream": "^2",
!                 "amphp/parallel": "^2.3",
                  "composer-runtime-api": "^2",
                  "composer/semver": "^1.4 || ^2.0 || ^3.0",
                  "composer/xdebug-handler": "^2.0 || ^3.0",
+                 "danog/advanced-json-rpc": "^3.1",
                  "dnoegel/php-xdg-base-dir": "^0.1.1",
                  "ext-ctype": "*",
                  "ext-dom": "*",
***************
*** 10034,10060 ****
                  "ext-mbstring": "*",
                  "ext-simplexml": "*",
                  "ext-tokenizer": "*",
!                 "felixfbecker/advanced-json-rpc": "^3.1",
!                 "felixfbecker/language-server-protocol": "^1.5.2",
                  "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0",
!                 "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
!                 "nikic/php-parser": "^4.17",
!                 "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0",
!                 "sebastian/diff": "^4.0 || ^5.0 || ^6.0",
                  "spatie/array-to-xml": "^2.17.0 || ^3.0",
!                 "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0",
!                 "symfony/filesystem": "^5.4 || ^6.0 || ^7.0"
!             },
!             "conflict": {
!                 "nikic/php-parser": "4.17.0"
              },
              "provide": {
                  "psalm/psalm": "self.version"
              },
              "require-dev": {
!                 "amphp/phpunit-util": "^2.0",
                  "bamarni/composer-bin-plugin": "^1.4",
                  "brianium/paratest": "^6.9",
                  "ext-curl": "*",
                  "mockery/mockery": "^1.5",
                  "nunomaduro/mock-final-classes": "^1.1",
--- 11059,11084 ----
                  "ext-mbstring": "*",
                  "ext-simplexml": "*",
                  "ext-tokenizer": "*",
!                 "felixfbecker/language-server-protocol": "^1.5.3",
                  "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0",
!                 "netresearch/jsonmapper": "^5.0",
!                 "nikic/php-parser": "^5.0.0",
!                 "php": "~8.1.31 || ~8.2.27 || ~8.3.16 || ~8.4.3 || ~8.5.0",
!                 "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0",
                  "spatie/array-to-xml": "^2.17.0 || ^3.0",
!                 "symfony/console": "^6.0 || ^7.0 || ^8.0",
!                 "symfony/filesystem": "~6.3.12 || ~6.4.3 || ^7.0.3 || ^8.0",
!                 "symfony/polyfill-php84": "^1.31.0"
              },
              "provide": {
                  "psalm/psalm": "self.version"
              },
              "require-dev": {
!                 "amphp/phpunit-util": "^3",
                  "bamarni/composer-bin-plugin": "^1.4",
                  "brianium/paratest": "^6.9",
+                 "danog/class-finder": "^0.4.8",
+                 "dg/bypass-finals": "^1.5",
                  "ext-curl": "*",
                  "mockery/mockery": "^1.5",
                  "nunomaduro/mock-final-classes": "^1.1",
***************
*** 10062,10071 ****
                  "phpstan/phpdoc-parser": "^1.6",
                  "phpunit/phpunit": "^9.6",
                  "psalm/plugin-mockery": "^1.1",
!                 "psalm/plugin-phpunit": "^0.18",
                  "slevomat/coding-standard": "^8.4",
                  "squizlabs/php_codesniffer": "^3.6",
!                 "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0"
              },
              "suggest": {
                  "ext-curl": "In order to send data to shepherd",
--- 11086,11095 ----
                  "phpstan/phpdoc-parser": "^1.6",
                  "phpunit/phpunit": "^9.6",
                  "psalm/plugin-mockery": "^1.1",
!                 "psalm/plugin-phpunit": "^0.19",
                  "slevomat/coding-standard": "^8.4",
                  "squizlabs/php_codesniffer": "^3.6",
!                 "symfony/process": "^6.0 || ^7.0 || ^8.0"
              },
              "suggest": {
                  "ext-curl": "In order to send data to shepherd",
***************
*** 10076,10081 ****
--- 11100,11106 ----
                  "psalm-language-server",
                  "psalm-plugin",
                  "psalm-refactor",
+                 "psalm-review",
                  "psalter"
              ],
              "type": "project",
***************
*** 10085,10091 ****
                      "dev-2.x": "2.x-dev",
                      "dev-3.x": "3.x-dev",
                      "dev-4.x": "4.x-dev",
!                     "dev-master": "5.x-dev"
                  }
              },
              "autoload": {
--- 11110,11118 ----
                      "dev-2.x": "2.x-dev",
                      "dev-3.x": "3.x-dev",
                      "dev-4.x": "4.x-dev",
!                     "dev-5.x": "5.x-dev",
!                     "dev-6.x": "6.x-dev",
!                     "dev-master": "7.x-dev"
                  }
              },
              "autoload": {
***************
*** 10100,10105 ****
--- 11127,11136 ----
              "authors": [
                  {
                      "name": "Matthew Brown"
+                 },
+                 {
+                     "name": "Daniil Gentili",
+                     "email": "daniil@daniil.it"
                  }
              ],
              "description": "A static analysis tool for finding errors in PHP applications",
***************
*** 10114,10150 ****
                  "issues": "https://github.com/vimeo/psalm/issues",
                  "source": "https://github.com/vimeo/psalm"
              },
!             "time": "2024-09-08T18:53:08+00:00"
          },
          {
              "name": "webmozart/assert",
!             "version": "1.11.0",
              "source": {
                  "type": "git",
                  "url": "https://github.com/webmozarts/assert.git",
!                 "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991",
!                 "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991",
                  "shasum": ""
              },
              "require": {
                  "ext-ctype": "*",
!                 "php": "^7.2 || ^8.0"
!             },
!             "conflict": {
!                 "phpstan/phpstan": "<0.12.20",
!                 "vimeo/psalm": "<4.6.1 || 4.6.2"
              },
!             "require-dev": {
!                 "phpunit/phpunit": "^8.5.13"
              },
              "type": "library",
              "extra": {
                  "branch-alias": {
!                     "dev-master": "1.10-dev"
                  }
              },
              "autoload": {
--- 11145,11181 ----
                  "issues": "https://github.com/vimeo/psalm/issues",
                  "source": "https://github.com/vimeo/psalm"
              },
!             "time": "2026-03-19T10:56:09+00:00"
          },
          {
              "name": "webmozart/assert",
!             "version": "2.1.6",
              "source": {
                  "type": "git",
                  "url": "https://github.com/webmozarts/assert.git",
!                 "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8"
              },
              "dist": {
                  "type": "zip",
!                 "url": "https://api.github.com/repos/webmozarts/assert/zipball/ff31ad6efc62e66e518fbab1cde3453d389bcdc8",
!                 "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8",
                  "shasum": ""
              },
              "require": {
                  "ext-ctype": "*",
!                 "ext-date": "*",
!                 "ext-filter": "*",
!                 "php": "^8.2"
              },
!             "suggest": {
!                 "ext-intl": "",
!                 "ext-simplexml": "",
!                 "ext-spl": ""
              },
              "type": "library",
              "extra": {
                  "branch-alias": {
!                     "dev-feature/2-0": "2.0-dev"
                  }
              },
              "autoload": {
***************
*** 10160,10165 ****
--- 11191,11200 ----
                  {
                      "name": "Bernhard Schussek",
                      "email": "bschussek@gmail.com"
+                 },
+                 {
+                     "name": "Woody Gilk",
+                     "email": "woody.gilk@gmail.com"
                  }
              ],
              "description": "Assertions to validate method input/output with nice error messages.",
***************
*** 10170,10178 ****
              ],
              "support": {
                  "issues": "https://github.com/webmozarts/assert/issues",
!                 "source": "https://github.com/webmozarts/assert/tree/1.11.0"
              },
!             "time": "2022-06-03T18:03:27+00:00"
          }
      ],
      "aliases": [],
--- 11205,11213 ----
              ],
              "support": {
                  "issues": "https://github.com/webmozarts/assert/issues",
!                 "source": "https://github.com/webmozarts/assert/tree/2.1.6"
              },
!             "time": "2026-02-27T10:28:38+00:00"
          }
      ],
      "aliases": [],
***************
*** 10181,10187 ****
      "prefer-stable": false,
      "prefer-lowest": false,
      "platform": {
!         "php": "^8.1",
          "ext-pdo": "*",
          "ext-zip": "*",
          "ext-zlib": "*",
--- 11216,11222 ----
      "prefer-stable": false,
      "prefer-lowest": false,
      "platform": {
!         "php": "^8.2",
          "ext-pdo": "*",
          "ext-zip": "*",
          "ext-zlib": "*",
***************
*** 10189,10195 ****
      },
      "platform-dev": {},
      "platform-overrides": {
!         "php": "8.1.0"
      },
!     "plugin-api-version": "2.6.0"
  }
--- 11224,11230 ----
      },
      "platform-dev": {},
      "platform-overrides": {
!         "php": "8.2.27"
      },
!     "plugin-api-version": "2.9.0"
  }
diff -x images -crNEB release-4.0.0-a1/config/default/container/services.yml release-4.0.0-a2-dev/config/default/container/services.yml
*** release-4.0.0-a1/config/default/container/services.yml	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/config/default/container/services.yml	Wed Apr  1 17:47:36 2026
***************
*** 15,23 ****
--- 15,25 ----
      - { resource: services_files.yml }
      - { resource: services_filesystem.yml }
      - { resource: services_finder.yml }
+     - { resource: services_forum.yml }
      - { resource: services_help.yml }
      - { resource: services_http.yml }
      - { resource: services_language.yml }
+     - { resource: services_members.yml }
      - { resource: services_mention.yml }
      - { resource: services_messenger.yml }
      - { resource: services_migrator.yml }
***************
*** 140,150 ****
--- 142,154 ----
          arguments:
              - '@auth'
              - '@avatar.helper'
+             - '@dbal.conn'
              - '@cache'
              - '@config'
              - '@language'
              - '@event_dispatcher'
              - '@path_helper'
+             - '@template'
              - '@user'
  
      log:
***************
*** 163,169 ****
          class: phpbb\manifest
          arguments:
              - '@config'
-             - '@path_helper'
              - '@event_dispatcher'
              - '@user'
  
--- 167,172 ----
diff -x images -crNEB release-4.0.0-a1/config/default/container/services_content.yml release-4.0.0-a2-dev/config/default/container/services_content.yml
*** release-4.0.0-a1/config/default/container/services_content.yml	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/config/default/container/services_content.yml	Wed Apr  1 17:47:36 2026
***************
*** 32,37 ****
--- 32,38 ----
              - '@config'
              - '@config_text'
              - '@dbal.conn'
+             - '@controller.helper'
              - '@user'
              - '@event_dispatcher'
              - '%core.root_path%'
***************
*** 43,48 ****
--- 44,50 ----
              - '@auth'
              - '@config'
              - '@dbal.conn'
+             - '@controller.helper'
              - '@user'
              - '%core.root_path%'
              - '%core.php_ext%'
***************
*** 53,58 ****
--- 55,61 ----
              - '@auth'
              - '@config'
              - '@dbal.conn'
+             - '@controller.helper'
              - '@user'
              - '%core.root_path%'
              - '%core.php_ext%'
diff -x images -crNEB release-4.0.0-a1/config/default/container/services_feed.yml release-4.0.0-a2-dev/config/default/container/services_feed.yml
*** release-4.0.0-a1/config/default/container/services_feed.yml	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/config/default/container/services_feed.yml	Wed Apr  1 17:47:36 2026
***************
*** 15,20 ****
--- 15,31 ----
              - '@language'
              - '%core.php_ext%'
  
+     feed.http_auth_subscriber:
+         class: phpbb\feed\event\http_auth_subscriber
+         arguments:
+             - '@auth'
+             - '@config'
+             - '@language'
+             - '@request'
+             - '@user'
+         tags:
+             - { name: kernel.event_subscriber }
+ 
      feed.helper:
          class: phpbb\feed\helper
          arguments:
diff -x images -crNEB release-4.0.0-a1/config/default/container/services_forum.yml release-4.0.0-a2-dev/config/default/container/services_forum.yml
*** release-4.0.0-a1/config/default/container/services_forum.yml	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/config/default/container/services_forum.yml	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,27 ----
+ services:
+ 
+     phpbb.birthday.helper:
+         class: phpbb\forum\birthday_helper
+         arguments:
+             - '@auth'
+             - '@config'
+             - '@dbal.conn'
+             - '@dispatcher'
+             - '@language'
+             - '@template'
+             - '@user'
+ 
+     forum.controller.index:
+         class: phpbb\forum\controller\index
+         arguments:
+             - '@auth'
+             - '@phpbb.birthday.helper'
+             - '@config'
+             - '@controller.helper'
+             - '@dispatcher'
+             - '@group_helper'
+             - '@language'
+             - '@user'
+             - '@template'
+             - '%core.root_path%'
+             - '%core.php_ext%'
diff -x images -crNEB release-4.0.0-a1/config/default/container/services_members.yml release-4.0.0-a2-dev/config/default/container/services_members.yml
*** release-4.0.0-a1/config/default/container/services_members.yml	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/config/default/container/services_members.yml	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,72 ----
+ services:
+ 
+ # Controllers
+     members.controller.online:
+         class: phpbb\members\controller\online
+         arguments:
+             - '@auth'
+             - '@config'
+             - '@dispatcher'
+             - '@group_helper'
+             - '@viewonline_helper'
+             - '@controller.helper'
+             - '@language'
+             - '@pagination'
+             - '@request'
+             - '@template'
+             - '@user'
+             - '%core.root_path%'
+             - '%core.php_ext%'
+ 
+     members.controller.online_whois:
+         class: phpbb\members\controller\online_whois
+         arguments:
+             - '@auth'
+             - '@dbal.conn'
+             - '@controller.helper'
+             - '@language'
+             - '@template'
+             - '@user'
+             - '%tables.users%'
+             - '%tables.sessions%'
+             - '%core.root_path%'
+             - '%core.php_ext%'
+ 
+     members.controller.team:
+         class: phpbb\members\controller\team
+         arguments:
+             - '@auth'
+             - '@config'
+             - '@dbal.conn'
+             - '@dispatcher'
+             - '@group_helper'
+             - '@controller.helper'
+             - '@language'
+             - '@template'
+             - '@user'
+             - '%core.root_path%'
+             - '%core.php_ext%'
+             - '%tables.forums%'
+             - '%tables.groups%'
+             - '%tables.teampage%'
+             - '%tables.user_group%'
+             - '%tables.users%'
+ 
+ # Helpers
+     viewonline_helper:
+         class: phpbb\members\viewonline_helper
+         arguments:
+             - '@dbal.conn'
+             - '@config'
+             - '@event_dispatcher'
+             - '@router'
+             - '@controller.helper'
+             - '@language'
+             - '@auth'
+             - '%core.root_path%'
+             - '%core.php_ext%'
+             - '%core.adm_relative_path%'
+             - '%tables.users%'
+             - '%tables.sessions%'
+             - '%tables.topics%'
+             - '%tables.forums%'
diff -x images -crNEB release-4.0.0-a1/config/default/container/services_mention.yml release-4.0.0-a2-dev/config/default/container/services_mention.yml
*** release-4.0.0-a1/config/default/container/services_mention.yml	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/config/default/container/services_mention.yml	Wed Apr  1 17:47:36 2026
***************
*** 5,10 ****
--- 5,11 ----
          arguments:
              - '@mention.source_collection'
              - '@request'
+             - '@controller.helper'
              - '%core.root_path%'
              - '%core.php_ext%'
  
diff -x images -crNEB release-4.0.0-a1/config/default/container/services_notification.yml release-4.0.0-a2-dev/config/default/container/services_notification.yml
*** release-4.0.0-a1/config/default/container/services_notification.yml	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/config/default/container/services_notification.yml	Wed Apr  1 17:47:36 2026
***************
*** 26,31 ****
--- 26,33 ----
      notification.type.base:
          abstract: true
          arguments:
+             - '@avatar.helper'
+             - '@controller.helper'
              - '@dbal.conn'
              - '@language'
              - '@user'
***************
*** 248,250 ****
--- 250,263 ----
              - '%tables.push_subscriptions%'
          tags:
              - { name: notification.method }
+ 
+ # ----- Notification's controllers -----
+     notification.controller.mark_read:
+         class: phpbb\notification\controller\mark_read
+         arguments:
+             - '@event_dispatcher'
+             - '@language'
+             - '@notification_manager'
+             - '@request'
+             - '@user'
+             - '%core.root_path%'
diff -x images -crNEB release-4.0.0-a1/config/default/container/services_ucp.yml release-4.0.0-a2-dev/config/default/container/services_ucp.yml
*** release-4.0.0-a1/config/default/container/services_ucp.yml	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/config/default/container/services_ucp.yml	Wed Apr  1 17:47:36 2026
***************
*** 4,9 ****
--- 4,10 ----
          arguments:
              - '@config'
              - '@dispatcher'
+             - '@controller.helper'
              - '@language'
              - '@request'
              - '@user'
diff -x images -crNEB release-4.0.0-a1/config/default/routing/members.yml release-4.0.0-a2-dev/config/default/routing/members.yml
*** release-4.0.0-a1/config/default/routing/members.yml	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/config/default/routing/members.yml	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,14 ----
+ phpbb_members_online:
+     path: /online
+     defaults:
+         _controller: members.controller.online:handle
+ 
+ phpbb_members_online_whois:
+     path: /online/whois/{session_id}
+     defaults:
+         _controller: members.controller.online_whois:handle
+ 
+ phpbb_members_team:
+     path:             /team
+     defaults:
+         _controller:  members.controller.team:handle
diff -x images -crNEB release-4.0.0-a1/config/default/routing/notifications.yml release-4.0.0-a2-dev/config/default/routing/notifications.yml
*** release-4.0.0-a1/config/default/routing/notifications.yml	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/config/default/routing/notifications.yml	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,6 ----
+ phpbb_notifications_mark_read:
+     path:             /mark-read/{id}
+     defaults:
+         _controller:  notification.controller.mark_read:handle
+     requirements:
+         id:         \d+
diff -x images -crNEB release-4.0.0-a1/config/default/routing/routing.yml release-4.0.0-a2-dev/config/default/routing/routing.yml
*** release-4.0.0-a1/config/default/routing/routing.yml	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/config/default/routing/routing.yml	Wed Apr  1 17:47:36 2026
***************
*** 4,13 ****
  #     path: /foo
  #     defaults: { _controller: foo_sevice:method }
  #
! # The above will be accessed via app.php?controller=foo and it will
  # instantiate the 'foo_service' service and call the 'method' method.
  #
  
  phpbb_cron_routing:
      resource: cron.yml
      prefix: /cron
--- 4,17 ----
  #     path: /foo
  #     defaults: { _controller: foo_sevice:method }
  #
! # The above will be accessed via index.php?controller=foo and it will
  # instantiate the 'foo_service' service and call the 'method' method.
  #
  
+ phpbb_index_controller:
+     path: /
+     defaults: { _controller: forum.controller.index:handle }
+ 
  phpbb_cron_routing:
      resource: cron.yml
      prefix: /cron
***************
*** 28,38 ****
--- 32,50 ----
      path: /manifest
      defaults: { _controller: manifest.controller:handle }
  
+ phpbb_members_routing:
+     resource: members.yml
+     prefix: /members
+ 
  phpbb_mention_controller:
      path: /mention
      methods: [GET, POST]
      defaults: { _controller: mention.controller:handle }
  
+ phpbb_notifications:
+     resource: notifications.yml
+     prefix: /notifications
+ 
  phpbb_report_routing:
      resource: report.yml
  
diff -x images -crNEB release-4.0.0-a1/includes/acp/acp_board.php release-4.0.0-a2-dev/includes/acp/acp_board.php
*** release-4.0.0-a1/includes/acp/acp_board.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/acp/acp_board.php	Wed Apr  1 17:47:36 2026
***************
*** 1385,1395 ****
  	}
  
  	/**
! 	* Option to enable/disable removal of 'app.php' from URLs
  	*
! 	* Note that if mod_rewrite is on, URLs without app.php will still work,
  	* but any paths generated by the controller helper url() method will not
! 	* contain app.php.
  	*
  	* @param int $value The current config value
  	* @param string $key The config key
--- 1385,1395 ----
  	}
  
  	/**
! 	* Option to enable/disable removal of 'index.php' from URLs
  	*
! 	* Note that if mod_rewrite is on, URLs without index.php will still work,
  	* but any paths generated by the controller helper url() method will not
! 	* contain index.php.
  	*
  	* @param int $value The current config value
  	* @param string $key The config key
diff -x images -crNEB release-4.0.0-a1/includes/acp/acp_main.php release-4.0.0-a2-dev/includes/acp/acp_main.php
*** release-4.0.0-a1/includes/acp/acp_main.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/acp/acp_main.php	Wed Apr  1 17:47:36 2026
***************
*** 458,463 ****
--- 458,464 ----
  
  		if ($auth->acl_get('a_board'))
  		{
+ 			/** @var \phpbb\version_helper $version_helper */
  			$version_helper = $phpbb_container->get('version_helper');
  			try
  			{
diff -x images -crNEB release-4.0.0-a1/includes/acp/acp_update.php release-4.0.0-a2-dev/includes/acp/acp_update.php
*** release-4.0.0-a1/includes/acp/acp_update.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/acp/acp_update.php	Wed Apr  1 17:47:36 2026
***************
*** 77,83 ****
  			$template->assign_block_vars('updates_available', $updates_available);
  		}
  
! 		$update_link = $phpbb_root_path . 'install/app.' . $phpEx;
  
  		$template_ary = [
  			'S_UP_TO_DATE'				=> empty($updates_available),
--- 77,83 ----
  			$template->assign_block_vars('updates_available', $updates_available);
  		}
  
! 		$update_link = $phpbb_root_path . 'install/index.' . $phpEx;
  
  		$template_ary = [
  			'S_UP_TO_DATE'				=> empty($updates_available),
***************
*** 96,102 ****
  		// Incomplete update?
  		if (phpbb_version_compare($config['version'], PHPBB_VERSION, '<'))
  		{
! 			$database_update_link = $phpbb_root_path . 'install/app.php/update';
  
  			$template->assign_vars(array(
  				'S_UPDATE_INCOMPLETE'		=> true,
--- 96,102 ----
  		// Incomplete update?
  		if (phpbb_version_compare($config['version'], PHPBB_VERSION, '<'))
  		{
! 			$database_update_link = $phpbb_root_path . 'install/index.php/update';
  
  			$template->assign_vars(array(
  				'S_UPDATE_INCOMPLETE'		=> true,
diff -x images -crNEB release-4.0.0-a1/includes/acp/acp_users.php release-4.0.0-a2-dev/includes/acp/acp_users.php
*** release-4.0.0-a1/includes/acp/acp_users.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/acp/acp_users.php	Wed Apr  1 17:47:36 2026
***************
*** 16,21 ****
--- 16,22 ----
  */
  
  use phpbb\controller\helper;
+ use phpbb\language\language;
  use phpbb\messenger\method\messenger_interface;
  
  if (!defined('IN_PHPBB'))
***************
*** 43,48 ****
--- 44,52 ----
  		/** @var helper $controller_helper */
  		$controller_helper = $phpbb_container->get('controller.helper');
  
+ 		/** @var language $language Language object */
+ 		$language = $phpbb_container->get('language');
+ 
  		$user->add_lang(array('posting', 'ucp', 'acp/users'));
  		$this->tpl_name = 'acp_users';
  
***************
*** 1889,1897 ****
  									$sql = 'UPDATE ' . USERS_TABLE . '
  										SET ' . $db->sql_build_array('UPDATE', $result) . '
  										WHERE user_id = ' . (int) $user_id;
- 
  									$db->sql_query($sql);
! 									trigger_error($user->lang['USER_AVATAR_UPDATED'] . adm_back_link($this->u_action . '&amp;u=' . $user_id));
  								}
  							}
  						}
--- 1893,1919 ----
  									$sql = 'UPDATE ' . USERS_TABLE . '
  										SET ' . $db->sql_build_array('UPDATE', $result) . '
  										WHERE user_id = ' . (int) $user_id;
  									$db->sql_query($sql);
! 
! 									if ($request->is_ajax())
! 									{
! 										$json_response = new \phpbb\json_response;
! 										$json_response->send([
! 											'success' => true,
! 
! 											'MESSAGE_TITLE'	=> $language->lang('INFORMATION'),
! 											'MESSAGE_TEXT'	=> $language->lang('USER_AVATAR_UPDATED'),
! 											'REFRESH_DATA'	=> [
! 												'time'	=> 3,
! 												'url'		=> $this->u_action . '&amp;u=' . $user_id,
! 												'text'		=> $language->lang('BACK_TO_PREV'),
! 											]
! 										]);
! 									}
! 									else
! 									{
! 										trigger_error($user->lang['USER_AVATAR_UPDATED'] . adm_back_link($this->u_action . '&amp;u=' . $user_id));
! 									}
  								}
  							}
  						}
diff -x images -crNEB release-4.0.0-a1/includes/constants.php release-4.0.0-a2-dev/includes/constants.php
*** release-4.0.0-a1/includes/constants.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/constants.php	Wed Apr  1 17:47:36 2026
***************
*** 28,34 ****
  */
  
  // phpBB Version
! @define('PHPBB_VERSION', '4.0.0-a1');
  
  // QA-related
  // define('PHPBB_QA', 1);
--- 28,34 ----
  */
  
  // phpBB Version
! @define('PHPBB_VERSION', '4.0.0-a2-dev');
  
  // QA-related
  // define('PHPBB_QA', 1);
diff -x images -crNEB release-4.0.0-a1/includes/diff/diff.php release-4.0.0-a2-dev/includes/diff/diff.php
*** release-4.0.0-a1/includes/diff/diff.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/diff/diff.php	Wed Apr  1 17:47:36 2026
***************
*** 768,779 ****
  	/**
  	 * @var array|mixed
  	 */
! 	protected $final1;
  
  	/**
  	 * @var array|mixed
  	 */
! 	protected $final2;
  
  	/**
  	 * @var false
--- 768,779 ----
  	/**
  	 * @var array|mixed
  	 */
! 	public $final1;
  
  	/**
  	 * @var array|mixed
  	 */
! 	public $final2;
  
  	/**
  	 * @var false
diff -x images -crNEB release-4.0.0-a1/includes/diff/engine.php release-4.0.0-a2-dev/includes/diff/engine.php
*** release-4.0.0-a1/includes/diff/engine.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/diff/engine.php	Wed Apr  1 17:47:36 2026
***************
*** 1,68 ****
  <?php
  /**
! *
! * This file is part of the phpBB Forum Software package.
! *
! * @copyright (c) phpBB Limited <https://www.phpbb.com>
! * @license GNU General Public License, version 2 (GPL-2.0)
! *
! * For full copyright and license information, please see
! * the docs/CREDITS.txt file.
! *
! */
  
  /**
! * @ignore
! */
  if (!defined('IN_PHPBB'))
  {
  	exit;
  }
  
  /**
! * Code from pear.php.net, Text_Diff-1.1.0 package
! * http://pear.php.net/package/Text_Diff/ (native engine)
! *
! * Modified by phpBB Limited to meet our coding standards
! * and being able to integrate into phpBB
! *
! * Class used internally by Text_Diff to actually compute the diffs. This
! * class is implemented using native PHP code.
! *
! * The algorithm used here is mostly lifted from the perl module
! * Algorithm::Diff (version 1.06) by Ned Konz, which is available at:
! * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip
! *
! * More ideas are taken from: http://www.ics.uci.edu/~eppstein/161/960229.html
! *
! * Some ideas (and a bit of code) are taken from analyze.c, of GNU
! * diffutils-2.7, which can be found at:
! * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
! *
! * Some ideas (subdivision by NCHUNKS > 2, and some optimizations) are from
! * Geoffrey T. Dairiki <dairiki@dairiki.org>. The original PHP version of this
! * code was written by him, and is used/adapted with his permission.
! *
! * Copyright 2004-2008 The Horde Project (http://www.horde.org/)
! *
! * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
! * @package diff
! *
! * @access private
! */
  class diff_engine
  {
  	/**
! 	* If set to true we trim all lines before we compare them. This ensures that sole space/tab changes do not trigger diffs.
! 	*/
! 	var $skip_whitespace_changes = true;
  
  	function diff(&$from_lines, &$to_lines, $preserve_cr = true)
  	{
! 		// Remove empty lines...
! 		// If preserve_cr is true, we basically only change \r\n and bare \r to \n to get the same carriage returns for both files
! 		// If it is false, we try to only use \n once per line and ommit all empty lines to be able to get a proper data diff
! 
  		if (is_array($from_lines))
  		{
  			$from_lines = implode("\n", $from_lines);
--- 1,98 ----
  <?php
  /**
!  *
!  * This file is part of the phpBB Forum Software package.
!  *
!  * @copyright (c) phpBB Limited <https://www.phpbb.com>
!  * @license GNU General Public License, version 2 (GPL-2.0)
!  *
!  * For full copyright and license information, please see
!  * the docs/CREDITS.txt file.
!  *
!  */
  
  /**
!  * @ignore
!  */
  if (!defined('IN_PHPBB'))
  {
  	exit;
  }
  
  /**
!  * Code from pear.php.net, Text_Diff-1.1.0 package
!  * http://pear.php.net/package/Text_Diff/ (native engine)
!  *
!  * Modified by phpBB Limited to meet our coding standards
!  * and being able to integrate into phpBB
!  *
!  * Class used internally by Text_Diff to actually compute the diffs. This
!  * class is implemented using native PHP code.
!  *
!  * The algorithm used here is mostly lifted from the perl module
!  * Algorithm::Diff (version 1.06) by Ned Konz, which is available at:
!  * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip
!  *
!  * More ideas are taken from: http://www.ics.uci.edu/~eppstein/161/960229.html
!  *
!  * Some ideas (and a bit of code) are taken from analyze.c, of GNU
!  * diffutils-2.7, which can be found at:
!  * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
!  *
!  * Some ideas (subdivision by NCHUNKS > 2, and some optimizations) are from
!  * Geoffrey T. Dairiki <dairiki@dairiki.org>. The original PHP version of this
!  * code was written by him, and is used/adapted with his permission.
!  *
!  * Copyright 2004-2008 The Horde Project (http://www.horde.org/)
!  *
!  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
!  * @package diff
!  *
!  * @access private
!  */
  class diff_engine
  {
+ 	/** var array */
+ 	protected $in_seq;
+ 
+ 	/** var int */
+ 	protected $lcs;
+ 
+ 	/** var array */
+ 	protected $seq;
+ 
  	/**
! 	 * bool
! 	 *
! 	 * If set to true we trim all lines before we compare them. This ensures that sole space/tab changes do not trigger diffs.
! 	 */
! 	protected $skip_whitespace_changes = true;
! 
! 	/** var array */
! 	protected $xchanged;
! 
! 	/** var array */
! 	protected $xind;
! 
! 	/** var array */
! 	protected $xv;
! 
! 	/** var array */
! 	protected $ychanged;
! 
! 	/** var array */
! 	protected $yind;
! 
! 	/** var array */
! 	protected $yv;
  
  	function diff(&$from_lines, &$to_lines, $preserve_cr = true)
  	{
! 		/*
! 		 * Remove empty lines...
! 		 * If preserve_cr is true, we basically only change \r\n and bare \r to \n to get the same carriage returns for both files
! 		 * If it is false, we try to only use \n once per line and ommit all empty lines to be able to get a proper data diff
! 		 */
  		if (is_array($from_lines))
  		{
  			$from_lines = implode("\n", $from_lines);
***************
*** 87,93 ****
  		$n_from = count($from_lines);
  		$n_to = count($to_lines);
  
! 		$this->xchanged = $this->ychanged = $this->xv = $this->yv = $this->xind = $this->yind = array();
  		unset($this->seq, $this->in_seq, $this->lcs);
  
  		// Skip leading common lines.
--- 117,123 ----
  		$n_from = count($from_lines);
  		$n_to = count($to_lines);
  
! 		$this->xchanged = $this->ychanged = $this->xv = $this->yv = $this->xind = $this->yind = [];
  		unset($this->seq, $this->in_seq, $this->lcs);
  
  		// Skip leading common lines.
diff -x images -crNEB release-4.0.0-a1/includes/functions.php release-4.0.0-a2-dev/includes/functions.php
*** release-4.0.0-a1/includes/functions.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/functions.php	Wed Apr  1 17:47:36 2026
***************
*** 1825,1831 ****
  	$script_name = substr($script_name, -1) === '/' ? $script_name . '.' : $script_name;
  
  	// $phpbb_root_path accounts for redirects from e.g. /adm
! 	$script_path = trim(dirname($script_name)) . '/' . $phpbb_root_path . 'install/app.' . $phpEx;
  	// Replace any number of consecutive backslashes and/or slashes with a single slash
  	// (could happen on some proxy setups and/or Windows servers)
  	return preg_replace('#[\\\\/]{2,}#', '/', $script_path);
--- 1825,1831 ----
  	$script_name = substr($script_name, -1) === '/' ? $script_name . '.' : $script_name;
  
  	// $phpbb_root_path accounts for redirects from e.g. /adm
! 	$script_path = trim(dirname($script_name)) . '/' . $phpbb_root_path . 'install/index.' . $phpEx;
  	// Replace any number of consecutive backslashes and/or slashes with a single slash
  	// (could happen on some proxy setups and/or Windows servers)
  	return preg_replace('#[\\\\/]{2,}#', '/', $script_path);
***************
*** 3849,3857 ****
  		'U_PRIVATEMSGS'			=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
  		'U_RETURN_INBOX'		=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
  		'U_MEMBERLIST'			=> append_sid("{$phpbb_root_path}memberlist.$phpEx"),
! 		'U_VIEWONLINE'			=> ($auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) ? append_sid("{$phpbb_root_path}viewonline.$phpEx") : '',
  		'U_LOGIN_LOGOUT'		=> $u_login_logout,
! 		'U_INDEX'				=> append_sid("{$phpbb_root_path}index.$phpEx"),
  		'U_SEARCH'				=> append_sid("{$phpbb_root_path}search.$phpEx"),
  		'U_SITE_HOME'			=> $config['site_home_url'],
  		'U_REGISTER'			=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'),
--- 3849,3857 ----
  		'U_PRIVATEMSGS'			=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
  		'U_RETURN_INBOX'		=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
  		'U_MEMBERLIST'			=> append_sid("{$phpbb_root_path}memberlist.$phpEx"),
! 		'U_VIEWONLINE'			=> $auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel') ? $controller_helper->route('phpbb_members_online') : '',
  		'U_LOGIN_LOGOUT'		=> $u_login_logout,
! 		'U_INDEX'				=> $controller_helper->route('phpbb_index_controller'),
  		'U_SEARCH'				=> append_sid("{$phpbb_root_path}search.$phpEx"),
  		'U_SITE_HOME'			=> $config['site_home_url'],
  		'U_REGISTER'			=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'),
***************
*** 3866,3872 ****
  		'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'),
  		'U_DELETE_COOKIES'		=> $controller_helper->route('phpbb_ucp_delete_cookies_controller'),
  		'U_CONTACT_US'			=> ($config['contact_admin_form_enable'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') : '',
! 		'U_TEAM'				=> (!$auth->acl_get('u_viewprofile')) ? '' : append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=team'),
  		'U_TERMS_USE'			=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
  		'U_PRIVACY'				=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
  		'UA_PRIVACY'			=> addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')),
--- 3866,3872 ----
  		'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'),
  		'U_DELETE_COOKIES'		=> $controller_helper->route('phpbb_ucp_delete_cookies_controller'),
  		'U_CONTACT_US'			=> ($config['contact_admin_form_enable'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') : '',
! 		'U_TEAM'				=> (!$auth->acl_get('u_viewprofile')) ? '' : $controller_helper->route('phpbb_members_team'),
  		'U_TERMS_USE'			=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
  		'U_PRIVACY'				=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
  		'UA_PRIVACY'			=> addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')),
diff -x images -crNEB release-4.0.0-a1/includes/functions_acp.php release-4.0.0-a2-dev/includes/functions_acp.php
*** release-4.0.0-a1/includes/functions_acp.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/functions_acp.php	Wed Apr  1 17:47:36 2026
***************
*** 81,87 ****
  		'PHPBB_VERSION'			=> PHPBB_VERSION,
  		'PHPBB_MAJOR'			=> $phpbb_major,
  
! 		'U_LOGOUT'				=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=logout'),
  		'U_ADM_LOGOUT'			=> append_sid("{$phpbb_admin_path}index.$phpEx", 'action=admlogout&amp;hash=' . generate_link_hash('acp_logout')),
  		'U_ADM_INDEX'			=> append_sid("{$phpbb_admin_path}index.$phpEx"),
  		'U_INDEX'				=> append_sid("{$phpbb_root_path}index.$phpEx"),
--- 81,87 ----
  		'PHPBB_VERSION'			=> PHPBB_VERSION,
  		'PHPBB_MAJOR'			=> $phpbb_major,
  
! 		'U_LOGOUT'				=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=logout&amp;hash=' . generate_link_hash('ucp_logout')),
  		'U_ADM_LOGOUT'			=> append_sid("{$phpbb_admin_path}index.$phpEx", 'action=admlogout&amp;hash=' . generate_link_hash('acp_logout')),
  		'U_ADM_INDEX'			=> append_sid("{$phpbb_admin_path}index.$phpEx"),
  		'U_INDEX'				=> append_sid("{$phpbb_root_path}index.$phpEx"),
diff -x images -crNEB release-4.0.0-a1/includes/functions_convert.php release-4.0.0-a2-dev/includes/functions_convert.php
*** release-4.0.0-a1/includes/functions_convert.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/functions_convert.php	Wed Apr  1 17:47:36 2026
***************
*** 1022,1028 ****
  /**
  * Retrieves configuration information from the source forum and caches it as an array
  * Both database and file driven configuration formats can be handled
! * (the type used is specified in $config_schema, see convert_phpbb20.php for more details)
  */
  function get_config()
  {
--- 1022,1028 ----
  /**
  * Retrieves configuration information from the source forum and caches it as an array
  * Both database and file driven configuration formats can be handled
! * (the type used is specified in $config_schema)
  */
  function get_config()
  {
***************
*** 1110,1116 ****
  
  /**
  * Transfers the relevant configuration information from the source forum
! * The mapping of fields is specified in $config_schema, see convert_phpbb20.php for more details
  */
  function restore_config($schema)
  {
--- 1110,1116 ----
  
  /**
  * Transfers the relevant configuration information from the source forum
! * The mapping of fields is specified in $config_schema
  */
  function restore_config($schema)
  {
***************
*** 1124,1132 ****
  		{
  			$var = (empty($m[2]) || empty($convert_config[$m[2]])) ? "''" : "'" . addslashes($convert_config[$m[2]]) . "'";
  			$exec = '$config_value = ' . $m[1] . '(' . $var . ');';
! 			// @codingStandardsIgnoreStart
  			eval($exec);
! 			// @codingStandardsIgnoreEnd
  		}
  		else
  		{
--- 1124,1132 ----
  		{
  			$var = (empty($m[2]) || empty($convert_config[$m[2]])) ? "''" : "'" . addslashes($convert_config[$m[2]]) . "'";
  			$exec = '$config_value = ' . $m[1] . '(' . $var . ');';
! 			// phpcs:disable Squiz.PHP.Eval
  			eval($exec);
! 			// phpcs:enable Squiz.PHP.Eval
  		}
  		else
  		{
diff -x images -crNEB release-4.0.0-a1/includes/functions_module.php release-4.0.0-a2-dev/includes/functions_module.php
*** release-4.0.0-a1/includes/functions_module.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/functions_module.php	Wed Apr  1 17:47:36 2026
***************
*** 468,476 ****
  		);
  
  		$is_auth = false;
! 		// @codingStandardsIgnoreStart
  		eval('$is_auth = (int) (' .	$module_auth . ');');
! 		// @codingStandardsIgnoreEnd
  
  		return $is_auth;
  	}
--- 468,476 ----
  		);
  
  		$is_auth = false;
! 		// phpcs:disable Squiz.PHP.Eval
  		eval('$is_auth = (int) (' .	$module_auth . ');');
! 		// phpcs:enable Squiz.PHP.Eval
  
  		return $is_auth;
  	}
diff -x images -crNEB release-4.0.0-a1/includes/functions_user.php release-4.0.0-a2-dev/includes/functions_user.php
*** release-4.0.0-a1/includes/functions_user.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/functions_user.php	Wed Apr  1 17:47:36 2026
***************
*** 1026,1032 ****
  	$match = array();
  
  	// Test for referrals from $whois_host to other whois databases, roll on rwhois
! 	if (preg_match('#ReferralServer:[\x20]*whois://(.+)#im', $ipwhois, $match))
  	{
  		if (strpos($match[1], ':') !== false)
  		{
--- 1026,1033 ----
  	$match = array();
  
  	// Test for referrals from $whois_host to other whois databases, roll on rwhois
! 	// Search for referral servers with or without the "whois://" prefix
! 	if (preg_match('#ReferralServer:[\x20]*whois://(.+)#im', $ipwhois, $match) || preg_match('#ReferralServer:[\x20]*([^/]+)$#im', $ipwhois, $match))
  	{
  		if (strpos($match[1], ':') !== false)
  		{
diff -x images -crNEB release-4.0.0-a1/includes/mcp/mcp_notes.php release-4.0.0-a2-dev/includes/mcp/mcp_notes.php
*** release-4.0.0-a1/includes/mcp/mcp_notes.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/mcp/mcp_notes.php	Wed Apr  1 17:47:36 2026
***************
*** 234,240 ****
  		$avatar_helper = $phpbb_container->get('avatar.helper');
  
  		$avatar = $avatar_helper->get_user_avatar($userrow);
! 		$template->assign_vars($avatar_helper->get_template_vars($avatar));
  
  		$template->assign_vars(array(
  			'U_POST_ACTION'			=> $this->u_action,
--- 234,240 ----
  		$avatar_helper = $phpbb_container->get('avatar.helper');
  
  		$avatar = $avatar_helper->get_user_avatar($userrow);
! 		$template->assign_vars($avatar_helper->get_template_vars($avatar, 'USER_'));
  
  		$template->assign_vars(array(
  			'U_POST_ACTION'			=> $this->u_action,
***************
*** 255,260 ****
--- 255,261 ----
  			'USERNAME_FULL'		=> get_username_string('full', $userrow['user_id'], $userrow['username'], $userrow['user_colour']),
  			'USERNAME_COLOUR'	=> get_username_string('colour', $userrow['user_id'], $userrow['username'], $userrow['user_colour']),
  			'USERNAME'			=> get_username_string('username', $userrow['user_id'], $userrow['username'], $userrow['user_colour']),
+ 			'USER_ID'			=> $userrow['user_id'],
  			'U_PROFILE'			=> get_username_string('profile', $userrow['user_id'], $userrow['username'], $userrow['user_colour']),
  
  			'RANK_IMG'			=> $rank_data['img'],
diff -x images -crNEB release-4.0.0-a1/includes/mcp/mcp_warn.php release-4.0.0-a2-dev/includes/mcp/mcp_warn.php
*** release-4.0.0-a1/includes/mcp/mcp_warn.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/mcp/mcp_warn.php	Wed Apr  1 17:47:36 2026
***************
*** 347,353 ****
  		$avatar_helper = $phpbb_container->get('avatar.helper');
  
  		$avatar = $avatar_helper->get_user_avatar($user_row);
! 		$template->assign_vars($avatar_helper->get_template_vars($avatar));
  
  		$template->assign_vars(array(
  			'U_POST_ACTION'		=> $this->u_action,
--- 347,353 ----
  		$avatar_helper = $phpbb_container->get('avatar.helper');
  
  		$avatar = $avatar_helper->get_user_avatar($user_row);
! 		$template->assign_vars($avatar_helper->get_template_vars($avatar, 'USER_'));
  
  		$template->assign_vars(array(
  			'U_POST_ACTION'		=> $this->u_action,
***************
*** 500,506 ****
  		$avatar_helper = $phpbb_container->get('avatar.helper');
  
  		$avatar = $avatar_helper->get_user_avatar($user_row);
! 		$template->assign_vars($avatar_helper->get_template_vars($avatar));
  
  		// OK, they didn't submit a warning so lets build the page for them to do so
  		$template->assign_vars(array(
--- 500,506 ----
  		$avatar_helper = $phpbb_container->get('avatar.helper');
  
  		$avatar = $avatar_helper->get_user_avatar($user_row);
! 		$template->assign_vars($avatar_helper->get_template_vars($avatar, 'USER_'));
  
  		// OK, they didn't submit a warning so lets build the page for them to do so
  		$template->assign_vars(array(
diff -x images -crNEB release-4.0.0-a1/includes/startup.php release-4.0.0-a2-dev/includes/startup.php
*** release-4.0.0-a1/includes/startup.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/startup.php	Wed Apr  1 17:47:36 2026
***************
*** 23,33 ****
  error_reporting($level);
  
  /**
! * Minimum Requirement: PHP 8.1.0
  */
! if (version_compare(PHP_VERSION, '8.1.0', '<'))
  {
! 	die('You are running an unsupported PHP version (' . PHP_VERSION . '). Please upgrade to PHP 8.1.0 or higher before trying to install or update to phpBB 4.0');
  }
  
  // In PHP 5.3.0 the error level has been raised to E_WARNING which causes problems
--- 23,33 ----
  error_reporting($level);
  
  /**
! * Minimum Requirement: PHP 8.2.0
  */
! if (version_compare(PHP_VERSION, '8.2.0', '<'))
  {
! 	die('You are running an unsupported PHP version (' . PHP_VERSION . '). Please upgrade to PHP 8.2.0 or higher before trying to install or update to phpBB 4.0');
  }
  
  // In PHP 5.3.0 the error level has been raised to E_WARNING which causes problems
diff -x images -crNEB release-4.0.0-a1/includes/ucp/ucp_activate.php release-4.0.0-a2-dev/includes/ucp/ucp_activate.php
*** release-4.0.0-a1/includes/ucp/ucp_activate.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/ucp/ucp_activate.php	Wed Apr  1 17:47:36 2026
***************
*** 32,37 ****
--- 32,40 ----
  		global $config, $phpbb_root_path, $phpEx, $request;
  		global $db, $user, $auth, $phpbb_container, $phpbb_log, $phpbb_dispatcher;
  
+ 		/** @var \phpbb\controller\helper $controller_helper */
+ 		$controller_helper = $phpbb_container->get('controller.helper');
+ 
  		$user_id = $request->variable('u', 0);
  		$key = $request->variable('k', '');
  
***************
*** 49,55 ****
  
  		if ($user_row['user_type'] <> USER_INACTIVE && !$user_row['user_newpasswd'])
  		{
! 			meta_refresh(3, append_sid("{$phpbb_root_path}index.$phpEx"));
  			trigger_error('ALREADY_ACTIVATED');
  		}
  
--- 52,58 ----
  
  		if ($user_row['user_type'] <> USER_INACTIVE && !$user_row['user_newpasswd'])
  		{
! 			meta_refresh(3, $controller_helper->route('phpbb_index_controller'));
  			trigger_error('ALREADY_ACTIVATED');
  		}
  
***************
*** 171,177 ****
  		$vars = array('user_row', 'message');
  		extract($phpbb_dispatcher->trigger_event('core.ucp_activate_after', compact($vars)));
  
! 		meta_refresh(3, append_sid("{$phpbb_root_path}index.$phpEx"));
  		trigger_error($user->lang[$message]);
  	}
  }
--- 174,180 ----
  		$vars = array('user_row', 'message');
  		extract($phpbb_dispatcher->trigger_event('core.ucp_activate_after', compact($vars)));
  
! 		meta_refresh(3, $controller_helper->route('phpbb_index_controller'));
  		trigger_error($user->lang[$message]);
  	}
  }
diff -x images -crNEB release-4.0.0-a1/includes/ucp/ucp_login_link.php release-4.0.0-a2-dev/includes/ucp/ucp_login_link.php
*** release-4.0.0-a1/includes/ucp/ucp_login_link.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/ucp/ucp_login_link.php	Wed Apr  1 17:47:36 2026
***************
*** 257,264 ****
  	*/
  	protected function perform_redirect()
  	{
! 		global $phpbb_root_path, $phpEx;
! 		$url = append_sid($phpbb_root_path . 'index.' . $phpEx);
  		redirect($url);
  	}
  }
--- 257,266 ----
  	*/
  	protected function perform_redirect()
  	{
! 		global $phpbb_container;
! 		/** @var \phpbb\controller\helper $controller_helper */
! 		$controller_helper = $phpbb_container->get('controller.helper');
! 		$url = $controller_helper->route('phpbb_index_controller');
  		redirect($url);
  	}
  }
diff -x images -crNEB release-4.0.0-a1/includes/ucp/ucp_pm_viewfolder.php release-4.0.0-a2-dev/includes/ucp/ucp_pm_viewfolder.php
*** release-4.0.0-a1/includes/ucp/ucp_pm_viewfolder.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/ucp/ucp_pm_viewfolder.php	Wed Apr  1 17:47:36 2026
***************
*** 314,320 ****
  						$newline = "\n";
  					}
  
! 					$string = '';
  					foreach ($data as $value)
  					{
  						$recipients = $value['to'];
--- 314,320 ----
  						$newline = "\n";
  					}
  
! 					$string = $export_type == 'CSV_EXCEL' ? "\xEF\xBB\xBF" : ''; // Add UTF-8 BOM mark for Excel
  					foreach ($data as $value)
  					{
  						$recipients = $value['to'];
diff -x images -crNEB release-4.0.0-a1/includes/ucp/ucp_pm_viewmessage.php release-4.0.0-a2-dev/includes/ucp/ucp_pm_viewmessage.php
*** release-4.0.0-a1/includes/ucp/ucp_pm_viewmessage.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/ucp/ucp_pm_viewmessage.php	Wed Apr  1 17:47:36 2026
***************
*** 214,219 ****
--- 214,220 ----
  		'MESSAGE_AUTHOR_FULL'		=> get_username_string('full', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']),
  		'MESSAGE_AUTHOR_COLOUR'		=> get_username_string('colour', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']),
  		'MESSAGE_AUTHOR'			=> get_username_string('username', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']),
+ 		'MESSAGE_AUTHOR_ID'			=> $author_id,
  		'U_MESSAGE_AUTHOR'			=> get_username_string('profile', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']),
  
  		'RANK_TITLE'		=> $user_info['rank_title'],
diff -x images -crNEB release-4.0.0-a1/includes/ucp/ucp_profile.php release-4.0.0-a2-dev/includes/ucp/ucp_profile.php
*** release-4.0.0-a1/includes/ucp/ucp_profile.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/ucp/ucp_profile.php	Wed Apr  1 17:47:36 2026
***************
*** 36,41 ****
--- 36,44 ----
  		global $config, $db, $user, $auth, $template, $phpbb_root_path, $phpEx;
  		global $request, $phpbb_container, $phpbb_log, $phpbb_dispatcher, $language;
  
+ 		/** @var \phpbb\controller\helper $controller_helper */
+ 		$controller_helper = $phpbb_container->get('controller.helper');
+ 
  		$user->add_lang('posting');
  
  		$submit		= $request->variable('submit', false, false, \phpbb\request\request_interface::POST);
***************
*** 233,240 ****
  						// Now, we can remove the user completely (kill the session) - NOT BEFORE!!!
  						if (!empty($sql_ary['user_actkey']))
  						{
! 							meta_refresh(5, append_sid($phpbb_root_path . 'index.' . $phpEx));
! 							$message = $user->lang[$message] . '<br /><br />' . sprintf($user->lang['RETURN_INDEX'], '<a href="' . append_sid($phpbb_root_path . 'index.' . $phpEx) . '">', '</a>');
  
  							// Because the user gets deactivated we log him out too, killing his session
  							$user->session_kill();
--- 236,244 ----
  						// Now, we can remove the user completely (kill the session) - NOT BEFORE!!!
  						if (!empty($sql_ary['user_actkey']))
  						{
! 							$index_route = $controller_helper->route('phpbb_index_controller');
! 							meta_refresh(5, $index_route);
! 							$message = $language->lang($message) . '<br /><br />' . sprintf($language->lang('RETURN_INDEX'), '<a href="' . $index_route . '">', '</a>');
  
  							// Because the user gets deactivated we log him out too, killing his session
  							$user->session_kill();
***************
*** 242,248 ****
  						else
  						{
  							meta_refresh(3, $this->u_action);
! 							$message = $user->lang[$message] . '<br /><br />' . sprintf($user->lang['RETURN_UCP'], '<a href="' . $this->u_action . '">', '</a>');
  						}
  
  						trigger_error($message);
--- 246,252 ----
  						else
  						{
  							meta_refresh(3, $this->u_action);
! 							$message = $language->lang($message) . '<br /><br />' . sprintf($language->lang('RETURN_UCP'), '<a href="' . $this->u_action . '">', '</a>');
  						}
  
  						trigger_error($message);
***************
*** 389,395 ****
  						$cp->update_profile_field_data($user->data['user_id'], $cp_data);
  
  						meta_refresh(3, $this->u_action);
! 						$message = $user->lang['PROFILE_UPDATED'] . '<br /><br />' . sprintf($user->lang['RETURN_UCP'], '<a href="' . $this->u_action . '">', '</a>');
  						trigger_error($message);
  					}
  
--- 393,399 ----
  						$cp->update_profile_field_data($user->data['user_id'], $cp_data);
  
  						meta_refresh(3, $this->u_action);
! 						$message = $language->lang('PROFILE_UPDATED') . '<br /><br />' . sprintf($language->lang('RETURN_UCP'), '<a href="' . $this->u_action . '">', '</a>');
  						trigger_error($message);
  					}
  
***************
*** 563,569 ****
  							WHERE user_id = ' . $user->data['user_id'];
  						$db->sql_query($sql);
  
! 						$message = $user->lang['PROFILE_UPDATED'] . '<br /><br />' . sprintf($user->lang['RETURN_UCP'], '<a href="' . $this->u_action . '">', '</a>');
  						trigger_error($message);
  					}
  				}
--- 567,573 ----
  							WHERE user_id = ' . $user->data['user_id'];
  						$db->sql_query($sql);
  
! 						$message = $language->lang('PROFILE_UPDATED') . '<br /><br />' . sprintf($language->lang('RETURN_UCP'), '<a href="' . $this->u_action . '">', '</a>');
  						trigger_error($message);
  					}
  				}
***************
*** 589,597 ****
  					'S_MAGIC_URL_CHECKED' 	=> (!$enable_urls) ? ' checked="checked"' : '',
  
  					'BBCODE_STATUS'			=> $user->lang(($config['allow_sig_bbcode'] ? 'BBCODE_IS_ON' : 'BBCODE_IS_OFF'), '<a href="' . $controller_helper->route('phpbb_help_bbcode_controller') . '">', '</a>'),
! 					'SMILIES_STATUS'		=> ($config['allow_sig_smilies']) ? $user->lang['SMILIES_ARE_ON'] : $user->lang['SMILIES_ARE_OFF'],
! 					'IMG_STATUS'			=> ($config['allow_sig_img']) ? $user->lang['IMAGES_ARE_ON'] : $user->lang['IMAGES_ARE_OFF'],
! 					'URL_STATUS'			=> ($config['allow_sig_links']) ? $user->lang['URL_IS_ON'] : $user->lang['URL_IS_OFF'],
  					'MAX_FONT_SIZE'			=> (int) $config['max_sig_font_size'],
  
  					'L_SIGNATURE_EXPLAIN'	=> $user->lang('SIGNATURE_EXPLAIN', (int) $config['max_sig_chars']),
--- 593,601 ----
  					'S_MAGIC_URL_CHECKED' 	=> (!$enable_urls) ? ' checked="checked"' : '',
  
  					'BBCODE_STATUS'			=> $user->lang(($config['allow_sig_bbcode'] ? 'BBCODE_IS_ON' : 'BBCODE_IS_OFF'), '<a href="' . $controller_helper->route('phpbb_help_bbcode_controller') . '">', '</a>'),
! 					'SMILIES_STATUS'		=> ($config['allow_sig_smilies']) ? $language->lang('SMILIES_ARE_ON') : $language->lang('SMILIES_ARE_OFF'),
! 					'IMG_STATUS'			=> ($config['allow_sig_img']) ? $language->lang('IMAGES_ARE_ON') : $language->lang('IMAGES_ARE_OFF'),
! 					'URL_STATUS'			=> ($config['allow_sig_links']) ? $language->lang('URL_IS_ON') : $language->lang('URL_IS_OFF'),
  					'MAX_FONT_SIZE'			=> (int) $config['max_sig_font_size'],
  
  					'L_SIGNATURE_EXPLAIN'	=> $user->lang('SIGNATURE_EXPLAIN', (int) $config['max_sig_chars']),
***************
*** 665,682 ****
  
  									if ($request->is_ajax())
  									{
- 										/** @var \phpbb\avatar\helper $avatar_helper */
- 										$avatar_helper = $phpbb_container->get('avatar.helper');
- 
- 										$avatar = $avatar_helper->get_user_avatar($user->data, 'USER_AVATAR', true);
- 
  										$json_response = new \phpbb\json_response;
  										$json_response->send(array(
  											'success' => true,
  
  											'MESSAGE_TITLE'	=> $language->lang('INFORMATION'),
  											'MESSAGE_TEXT'	=> $language->lang('PROFILE_UPDATED'),
- 											'AVATAR'		=> $avatar_helper->get_template_vars($avatar),
  											'REFRESH_DATA'	=> [
  												'time'	=> 3,
  												'url'		=> $this->u_action,
--- 669,680 ----
***************
*** 728,734 ****
  							$phpbb_avatar_manager->handle_avatar_delete($db, $user, $avatar_data, USERS_TABLE, 'user_');
  
  							meta_refresh(3, $this->u_action);
! 							$message = $user->lang['PROFILE_UPDATED'] . '<br /><br />' . sprintf($user->lang['RETURN_UCP'], '<a href="' . $this->u_action . '">', '</a>');
  							trigger_error($message);
  						}
  					}
--- 726,732 ----
  							$phpbb_avatar_manager->handle_avatar_delete($db, $user, $avatar_data, USERS_TABLE, 'user_');
  
  							meta_refresh(3, $this->u_action);
! 							$message = $language->lang('PROFILE_UPDATED') . '<br /><br />' . sprintf($language->lang('RETURN_UCP'), '<a href="' . $this->u_action . '">', '</a>');
  							trigger_error($message);
  						}
  					}
***************
*** 818,824 ****
  							$db->sql_query($sql);
  
  							meta_refresh(3, $this->u_action);
! 							$message = $user->lang['AUTOLOGIN_SESSION_KEYS_DELETED'] . '<br /><br />' . sprintf($user->lang['RETURN_UCP'], '<a href="' . $this->u_action . '">', '</a>');
  							trigger_error($message);
  						}
  					}
--- 816,822 ----
  							$db->sql_query($sql);
  
  							meta_refresh(3, $this->u_action);
! 							$message = $language->lang('AUTOLOGIN_SESSION_KEYS_DELETED') . '<br /><br />' . sprintf($language->lang('RETURN_UCP'), '<a href="' . $this->u_action . '">', '</a>');
  							trigger_error($message);
  						}
  					}
***************
*** 878,884 ****
  		$template->assign_vars(array(
  			'ERROR'		=> (count($error)) ? implode('<br />', $error) : '',
  
! 			'L_TITLE'	=> $user->lang['UCP_PROFILE_' . strtoupper($mode)],
  
  			'S_HIDDEN_FIELDS'	=> $s_hidden_fields,
  			'S_UCP_ACTION'		=> $this->u_action)
--- 876,882 ----
  		$template->assign_vars(array(
  			'ERROR'		=> (count($error)) ? implode('<br />', $error) : '',
  
! 			'L_TITLE'	=> $language->lang('UCP_PROFILE_' . strtoupper($mode)),
  
  			'S_HIDDEN_FIELDS'	=> $s_hidden_fields,
  			'S_UCP_ACTION'		=> $this->u_action)
diff -x images -crNEB release-4.0.0-a1/includes/ucp/ucp_register.php release-4.0.0-a2-dev/includes/ucp/ucp_register.php
*** release-4.0.0-a1/includes/ucp/ucp_register.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/ucp/ucp_register.php	Wed Apr  1 17:47:36 2026
***************
*** 32,37 ****
--- 32,40 ----
  		global $config, $db, $user, $template, $phpbb_root_path, $phpEx;
  		global $request, $phpbb_container, $phpbb_dispatcher;
  
+ 		/** @var \phpbb\controller\helper $controller_helper */
+ 		$controller_helper = $phpbb_container->get('controller.helper');
+ 
  		//
  		if ($config['require_activation'] == USER_ACTIVATION_DISABLE ||
  			(in_array($config['require_activation'], array(USER_ACTIVATION_SELF, USER_ACTIVATION_ADMIN)) && !$config['email_enable']))
***************
*** 558,564 ****
  				);
  				extract($phpbb_dispatcher->trigger_event('core.ucp_register_register_after', compact($vars)));
  
! 				$message = $message . '<br /><br />' . sprintf($user->lang['RETURN_INDEX'], '<a href="' . append_sid("{$phpbb_root_path}index.$phpEx") . '">', '</a>');
  				trigger_error($message);
  			}
  		}
--- 561,567 ----
  				);
  				extract($phpbb_dispatcher->trigger_event('core.ucp_register_register_after', compact($vars)));
  
! 				$message = $message . '<br /><br />' . sprintf($user->lang['RETURN_INDEX'], '<a href="' . $controller_helper->route('phpbb_index_controller') . '">', '</a>');
  				trigger_error($message);
  			}
  		}
diff -x images -crNEB release-4.0.0-a1/includes/ucp/ucp_resend.php release-4.0.0-a2-dev/includes/ucp/ucp_resend.php
*** release-4.0.0-a1/includes/ucp/ucp_resend.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/includes/ucp/ucp_resend.php	Wed Apr  1 17:47:36 2026
***************
*** 32,37 ****
--- 32,40 ----
  		global $config, $phpbb_root_path, $phpEx;
  		global $db, $user, $auth, $template, $request, $phpbb_container;
  
+ 		/** @var \phpbb\controller\helper $controller_helper */
+ 		$controller_helper = $phpbb_container->get('controller.helper');
+ 
  		$username	= $request->variable('username', '', true);
  		$email		= strtolower($request->variable('email', ''));
  		$submit		= (isset($_POST['submit'])) ? true : false;
***************
*** 157,166 ****
  
  			$this->update_activation_expiration();
  
! 			meta_refresh(3, append_sid("{$phpbb_root_path}index.$phpEx"));
  
  			$message = ($config['require_activation'] == USER_ACTIVATION_ADMIN) ? $user->lang['ACTIVATION_EMAIL_SENT_ADMIN'] : $user->lang['ACTIVATION_EMAIL_SENT'];
! 			$message .= '<br /><br />' . sprintf($user->lang['RETURN_INDEX'], '<a href="' . append_sid("{$phpbb_root_path}index.$phpEx") . '">', '</a>');
  			trigger_error($message);
  		}
  
--- 160,169 ----
  
  			$this->update_activation_expiration();
  
! 			meta_refresh(3, $controller_helper->route('phpbb_index_controller'));
  
  			$message = ($config['require_activation'] == USER_ACTIVATION_ADMIN) ? $user->lang['ACTIVATION_EMAIL_SENT_ADMIN'] : $user->lang['ACTIVATION_EMAIL_SENT'];
! 			$message .= '<br /><br />' . sprintf($user->lang['RETURN_INDEX'], '<a href="' . $controller_helper->route('phpbb_index_controller') . '">', '</a>');
  			trigger_error($message);
  		}
  
diff -x images -crNEB release-4.0.0-a1/index.php release-4.0.0-a2-dev/index.php
*** release-4.0.0-a1/index.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/index.php	Wed Apr  1 17:47:36 2026
***************
*** 12,211 ****
  */
  
  /**
- */
- 
- /**
  * @ignore
  */
  define('IN_PHPBB', true);
  $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './';
  $phpEx = substr(strrchr(__FILE__, '.'), 1);
  include($phpbb_root_path . 'common.' . $phpEx);
- include($phpbb_root_path . 'includes/functions_display.' . $phpEx);
  
  // Start session management
  $user->session_begin();
  $auth->acl($user->data);
! $user->setup('viewforum');
! 
! // Mark notifications read
! if (($mark_notification = $request->variable('mark_notification', 0)))
! {
! 	if ($user->data['user_id'] == ANONYMOUS)
! 	{
! 		if ($request->is_ajax())
! 		{
! 			trigger_error('LOGIN_REQUIRED');
! 		}
! 		login_box('', $user->lang['LOGIN_REQUIRED']);
! 	}
! 
! 	if (check_link_hash($request->variable('hash', ''), 'mark_notification_read'))
! 	{
! 		/* @var $phpbb_notifications \phpbb\notification\manager */
! 		$phpbb_notifications = $phpbb_container->get('notification_manager');
! 
! 		$notification = $phpbb_notifications->load_notifications('notification.method.board', array(
! 			'notification_id'	=> $mark_notification,
! 		));
! 
! 		if (isset($notification['notifications'][$mark_notification]))
! 		{
! 			$notification = $notification['notifications'][$mark_notification];
! 
! 			$notification->mark_read();
! 
! 			/**
! 			* You can use this event to perform additional tasks or redirect user elsewhere.
! 			*
! 			* @event core.index_mark_notification_after
! 			* @var	int										mark_notification	Notification ID
! 			* @var	\phpbb\notification\type\type_interface	notification		Notification instance
! 			* @since 3.2.6-RC1
! 			*/
! 			$vars = array('mark_notification', 'notification');
! 			extract($phpbb_dispatcher->trigger_event('core.index_mark_notification_after', compact($vars)));
! 
! 			if ($request->is_ajax())
! 			{
! 				$json_response = new \phpbb\json_response();
! 				$json_response->send(array(
! 					'success'	=> true,
! 				));
! 			}
! 
! 			if (($redirect = $request->variable('redirect', '')))
! 			{
! 				redirect(append_sid($phpbb_root_path . $redirect));
! 			}
! 
! 			redirect($notification->get_redirect_url());
! 		}
! 	}
! }
! 
! display_forums('', $config['load_moderators']);
! 
! /** @var \phpbb\group\helper $group_helper */
! $group_helper = $phpbb_container->get('group_helper');
! 
! $group_helper->display_legend($db, $template);
! 
! // Generate birthday list if required ...
! $show_birthdays = ($config['load_birthdays'] && $config['allow_birthdays'] && $auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel'));
! 
! $birthdays = $birthday_list = array();
! if ($show_birthdays)
! {
! 	$time = $user->create_datetime();
! 	$now = phpbb_gmgetdate($time->getTimestamp() + $time->getOffset());
! 
! 	// Display birthdays of 29th february on 28th february in non-leap-years
! 	$leap_year_birthdays = '';
! 	if ($now['mday'] == 28 && $now['mon'] == 2 && !$time->format('L'))
! 	{
! 		$leap_year_birthdays = " OR u.user_birthday LIKE '" . $db->sql_escape(sprintf('%2d-%2d-', 29, 2)) . "%'";
! 	}
! 
! 	$sql_ary = array(
! 		'SELECT' => 'u.user_id, u.username, u.user_colour, u.user_birthday',
! 		'FROM' => array(
! 			USERS_TABLE => 'u',
! 		),
! 		'LEFT_JOIN' => array(
! 			array(
! 				'FROM' => array(BANS_TABLE => 'b'),
! 				'ON' => 'u.user_id = b.ban_userid',
! 			),
! 		),
! 		'WHERE' => 'b.ban_id IS NULL
! 			AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ")
! 			AND (u.user_birthday LIKE '" . $db->sql_escape(sprintf('%2d-%2d-', $now['mday'], $now['mon'])) . "%' $leap_year_birthdays)",
! 	);
! 
! 	/**
! 	* Event to modify the SQL query to get birthdays data
! 	*
! 	* @event core.index_modify_birthdays_sql
! 	* @var	array	now			The assoc array with the 'now' local timestamp data
! 	* @var	array	sql_ary		The SQL array to get the birthdays data
! 	* @var	object	time		The user related Datetime object
! 	* @since 3.1.7-RC1
! 	*/
! 	$vars = array('now', 'sql_ary', 'time');
! 	extract($phpbb_dispatcher->trigger_event('core.index_modify_birthdays_sql', compact($vars)));
! 
! 	$sql = $db->sql_build_query('SELECT', $sql_ary);
! 	$result = $db->sql_query($sql);
! 	$rows = $db->sql_fetchrowset($result);
! 	$db->sql_freeresult($result);
! 
! 	foreach ($rows as $row)
! 	{
! 		$birthday_username	= get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']);
! 		$birthday_year		= (int) substr($row['user_birthday'], -4);
! 		$birthday_age		= ($birthday_year) ? max(0, $now['year'] - $birthday_year) : '';
! 
! 		$birthdays[] = array(
! 			'USERNAME'	=> $birthday_username,
! 			'AGE'		=> $birthday_age,
! 		);
! 
! 		// For 3.0 compatibility
! 		$birthday_list[] = $birthday_username . (($birthday_age) ? " ({$birthday_age})" : '');
! 	}
! 
! 	/**
! 	* Event to modify the birthdays list
! 	*
! 	* @event core.index_modify_birthdays_list
! 	* @var	array	birthdays		Array with the users birthdays data
! 	* @var	array	rows			Array with the birthdays SQL query result
! 	* @since 3.1.7-RC1
! 	*/
! 	$vars = array('birthdays', 'rows');
! 	extract($phpbb_dispatcher->trigger_event('core.index_modify_birthdays_list', compact($vars)));
! 
! 	$template->assign_block_vars_array('birthdays', $birthdays);
! }
! 
! $controller_helper = $phpbb_container->get('controller.helper');
! // Assign index specific vars
! $template->assign_vars(array(
! 	'TOTAL_POSTS'	=> $user->lang('TOTAL_POSTS_COUNT', (int) $config['num_posts']),
! 	'TOTAL_TOPICS'	=> $user->lang('TOTAL_TOPICS', (int) $config['num_topics']),
! 	'TOTAL_USERS'	=> $user->lang('TOTAL_USERS', (int) $config['num_users']),
! 	'NEWEST_USER'	=> $user->lang('NEWEST_USER', get_username_string('full', $config['newest_user_id'], $config['newest_username'], $config['newest_user_colour'])),
! 
! 	'BIRTHDAY_LIST'	=> (empty($birthday_list)) ? '' : implode($user->lang['COMMA_SEPARATOR'], $birthday_list),
! 
! 	'S_LOGIN_ACTION'			=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login'),
! 	'U_SEND_PASSWORD'           => ($config['email_enable'] && $config['allow_password_reset']) ? $controller_helper->route('phpbb_ucp_forgot_password_controller') : '',
! 	'S_DISPLAY_BIRTHDAY_LIST'	=> $show_birthdays,
! 	'S_INDEX'					=> true,
! 
! 	'U_CANONICAL'		=> generate_board_url() . '/',
! 	'U_MARK_FORUMS'		=> ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}index.$phpEx", 'hash=' . generate_link_hash('global') . '&amp;mark=forums&amp;mark_time=' . time()) : '',
! 	'U_MCP'				=> ($auth->acl_get('m_') || $auth->acl_getf_global('m_')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=main&amp;mode=front') : '')
! );
! 
! $page_title = ($config['board_index_text'] !== '') ? $config['board_index_text'] : $user->lang['INDEX'];
! 
! /**
! * You can use this event to modify the page title and load data for the index
! *
! * @event core.index_modify_page_title
! * @var	string	page_title		Title of the index page
! * @since 3.1.0-a1
! */
! $vars = array('page_title');
! extract($phpbb_dispatcher->trigger_event('core.index_modify_page_title', compact($vars)));
! 
! // Output page
! page_header($page_title, true);
  
! $template->set_filenames(array(
! 	'body' => 'index_body.html')
! );
  
! page_footer();
--- 12,34 ----
  */
  
  /**
  * @ignore
  */
  define('IN_PHPBB', true);
  $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './';
  $phpEx = substr(strrchr(__FILE__, '.'), 1);
  include($phpbb_root_path . 'common.' . $phpEx);
  
  // Start session management
  $user->session_begin();
  $auth->acl($user->data);
! $user->setup('app');
  
! /* @var $http_kernel \Symfony\Component\HttpKernel\HttpKernel */
! $http_kernel = $phpbb_container->get('http_kernel');
  
! /* @var $symfony_request \phpbb\symfony_request */
! $symfony_request = $phpbb_container->get('symfony_request');
! $response = $http_kernel->handle($symfony_request);
! $response->send();
! $http_kernel->terminate($symfony_request, $response);
diff -x images -crNEB release-4.0.0-a1/language/en/acp/attachments.php release-4.0.0-a2-dev/language/en/acp/attachments.php
*** release-4.0.0-a1/language/en/acp/attachments.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/language/en/acp/attachments.php	Wed Apr  1 17:47:36 2026
***************
*** 120,126 ****
  	'MAX_ATTACHMENTS_PM'			=> 'Maximum number of attachments per private message',
  	'MAX_EXTGROUP_FILESIZE'			=> 'Maximum file size',
  	'MAX_IMAGE_SIZE'				=> 'Maximum image dimensions',
! 	'MAX_IMAGE_SIZE_EXPLAIN'		=> 'Maximum size of image attachments. Set both values to 0px by 0px to disable dimension checking.',
  	'MAX_THUMB_WIDTH'				=> 'Maximum thumbnail width/height in pixel',
  	'MAX_THUMB_WIDTH_EXPLAIN'		=> 'A generated thumbnail will not exceed the width set here.',
  	'MIN_THUMB_FILESIZE'			=> 'Minimum thumbnail file size',
--- 120,126 ----
  	'MAX_ATTACHMENTS_PM'			=> 'Maximum number of attachments per private message',
  	'MAX_EXTGROUP_FILESIZE'			=> 'Maximum file size',
  	'MAX_IMAGE_SIZE'				=> 'Maximum image dimensions',
! 	'MAX_IMAGE_SIZE_EXPLAIN'		=> 'Maximum dimensions of image attachments. phpBB may try to automatically resize images before upload to these maximum image dimensions. Set both values to 0px by 0px to disable dimension checking and automatic resizing.',
  	'MAX_THUMB_WIDTH'				=> 'Maximum thumbnail width/height in pixel',
  	'MAX_THUMB_WIDTH_EXPLAIN'		=> 'A generated thumbnail will not exceed the width set here.',
  	'MIN_THUMB_FILESIZE'			=> 'Minimum thumbnail file size',
diff -x images -crNEB release-4.0.0-a1/language/en/acp/board.php release-4.0.0-a2-dev/language/en/acp/board.php
*** release-4.0.0-a1/language/en/acp/board.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/language/en/acp/board.php	Wed Apr  1 17:47:36 2026
***************
*** 293,299 ****
  	'ACP_FEED_OVERALL_FORUMS_EXPLAIN'	=> 'Enables the “All forums” feed, which displays a list of forums.',
  
  	'ACP_FEED_HTTP_AUTH'				=> 'Allow HTTP Authentication',
! 	'ACP_FEED_HTTP_AUTH_EXPLAIN'		=> 'Enables HTTP authentication, which allows users to receive content that is hidden to guest users by adding the <samp>auth=http</samp> parameter to the feed URL. Please note that some PHP setups require additional changes to the .htaccess file. Instructions can be found in that file.',
  	'ACP_FEED_ITEM_STATISTICS'			=> 'Item statistics',
  	'ACP_FEED_ITEM_STATISTICS_EXPLAIN'	=> 'Display individual statistics underneath feed items<br>(e.g. posted by, date and time, replies, views)',
  	'ACP_FEED_EXCLUDE_ID'				=> 'Exclude these forums',
--- 293,299 ----
  	'ACP_FEED_OVERALL_FORUMS_EXPLAIN'	=> 'Enables the “All forums” feed, which displays a list of forums.',
  
  	'ACP_FEED_HTTP_AUTH'				=> 'Allow HTTP Authentication',
! 	'ACP_FEED_HTTP_AUTH_EXPLAIN'		=> 'Enables HTTP authentication, allowing users to access content hidden from guests by adding the <samp>auth=http</samp> parameter to the feed URL. Please note that some PHP configurations may require additional changes to the .htaccess file; refer to that file for guidance. HTTP authentication is only supported over encrypted (https) connections.',
  	'ACP_FEED_ITEM_STATISTICS'			=> 'Item statistics',
  	'ACP_FEED_ITEM_STATISTICS_EXPLAIN'	=> 'Display individual statistics underneath feed items<br>(e.g. posted by, date and time, replies, views)',
  	'ACP_FEED_EXCLUDE_ID'				=> 'Exclude these forums',
***************
*** 451,457 ****
  	'ICONS_PATH'				=> 'Post icons storage path',
  	'ICONS_PATH_EXPLAIN'		=> 'Path under your phpBB root directory, e.g. <samp>images/icons</samp>.',
  	'MOD_REWRITE_ENABLE'		=> 'Enable URL Rewriting',
! 	'MOD_REWRITE_ENABLE_EXPLAIN' => 'When enabled, URLs containing ’app.php’ will be rewritten to remove the filename (i.e. app.php/foo will become /foo). <strong>Apache server’s mod_rewrite module is required for this functionality to work; if this option is enabled without mod_rewrite support, URLs on your board may be broken.</strong>',
  	'MOD_REWRITE_DISABLED'		=> 'The <strong>mod_rewrite</strong> module on your Apache web server is disabled. Enable the module or contact your web hosting provider if you wish to enable this feature.',
  	'MOD_REWRITE_INFORMATION_UNAVAILABLE' => 'We are unable to determine whether or not this server supports URL rewriting. This setting may be enabled but if URL rewriting is not available, paths generated by this board (such as for use in links) may be broken. Contact your web hosting provider if you are unsure whether or not you can safely enable this feature.',
  	'PATH_SETTINGS'				=> 'Path settings',
--- 451,457 ----
  	'ICONS_PATH'				=> 'Post icons storage path',
  	'ICONS_PATH_EXPLAIN'		=> 'Path under your phpBB root directory, e.g. <samp>images/icons</samp>.',
  	'MOD_REWRITE_ENABLE'		=> 'Enable URL Rewriting',
! 	'MOD_REWRITE_ENABLE_EXPLAIN' => 'When enabled, URLs containing ’index.php’ will be rewritten to remove the filename (i.e. index.php/foo will become /foo). <strong>Apache server’s mod_rewrite module is required for this functionality to work; if this option is enabled without mod_rewrite support, URLs on your board may be broken.</strong>',
  	'MOD_REWRITE_DISABLED'		=> 'The <strong>mod_rewrite</strong> module on your Apache web server is disabled. Enable the module or contact your web hosting provider if you wish to enable this feature.',
  	'MOD_REWRITE_INFORMATION_UNAVAILABLE' => 'We are unable to determine whether or not this server supports URL rewriting. This setting may be enabled but if URL rewriting is not available, paths generated by this board (such as for use in links) may be broken. Contact your web hosting provider if you are unsure whether or not you can safely enable this feature.',
  	'PATH_SETTINGS'				=> 'Path settings',
diff -x images -crNEB release-4.0.0-a1/language/en/acp/extensions.php release-4.0.0-a2-dev/language/en/acp/extensions.php
*** release-4.0.0-a1/language/en/acp/extensions.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/language/en/acp/extensions.php	Wed Apr  1 17:47:36 2026
***************
*** 90,114 ****
  	'EXTENSION_NAME'			=> 'Extension Name',
  	'EXTENSION_ACTIONS'			=> 'Actions',
  	'EXTENSION_OPTIONS'			=> 'Options',
! 	'EXTENSION_INSTALLING_HEADLINE'	=> 'Installing an extension',
! 	'EXTENSION_INSTALLING_EXPLAIN'	=> [
! 		0 => 'Download an extension from phpBB’s extensions database',
! 		1 => 'Unzip the extension and upload it to the <samp>ext/</samp> directory of your phpBB board',
! 		2 => 'Enable the extension, here in the Extensions manager',
! 	],
! 	'EXTENSION_REMOVING_HEADLINE'	=> 'Deleting an extension from your board',
! 	'EXTENSION_REMOVING_EXPLAIN'	=> [
! 		0 => 'Disable the extension',
! 		1 => 'Delete the extension’s data',
! 		2 => 'Delete the extension‘s files from the filesystem',
! 	],
! 	'EXTENSION_UPDATING_HEADLINE'	=> 'Updating an extension',
! 	'EXTENSION_UPDATING_EXPLAIN'	=> [
! 		0 => 'Disable the extension',
! 		1 => 'Delete the extension’s files from the filesystem',
! 		2 => 'Upload the new files',
! 		3 => 'Enable the extension',
! 	],
  
  	'EXTENSION_DELETE_DATA_CONFIRM'	=> 'Are you sure that you wish to delete the data associated with “%s”?<br /><br />This removes all of its data and settings and cannot be undone!',
  	'EXTENSION_DISABLE_CONFIRM'		=> 'Are you sure that you wish to disable the “%s” extension?',
--- 90,112 ----
  	'EXTENSION_NAME'			=> 'Extension Name',
  	'EXTENSION_ACTIONS'			=> 'Actions',
  	'EXTENSION_OPTIONS'			=> 'Options',
! 
! 	'EXTENSION_INSTALLING_HEADLINE'			=> 'Installing an extension',
! 	'EXTENSION_INSTALLING_EXPLAIN_STEP1'	=> 'Download an extension from phpBB’s extensions database',
! 	'EXTENSION_INSTALLING_EXPLAIN_STEP2'	=> 'Unzip the extension and upload it to the <samp>ext/</samp> directory of your phpBB board',
! 	'EXTENSION_INSTALLING_EXPLAIN_STEP3'	=> 'Enable the extension, here in the Extensions manager',
! 
! 	'EXTENSION_REMOVING_HEADLINE'			=> 'Deleting an extension from your board',
! 	'EXTENSION_REMOVING_EXPLAIN_STEP1'		=> 'Disable the extension',
! 	'EXTENSION_REMOVING_EXPLAIN_STEP2'		=> 'Delete the extension’s data',
! 	'EXTENSION_REMOVING_EXPLAIN_STEP3'		=> 'Delete the extension‘s files from the filesystem',
! 
! 	'EXTENSION_UPDATING_HEADLINE'			=> 'Updating an extension',
! 	'EXTENSION_UPDATING_EXPLAIN_STEP1'		=> 'Disable the extension',
! 	'EXTENSION_UPDATING_EXPLAIN_STEP2'		=> 'Delete the extension’s files from the filesystem',
! 	'EXTENSION_UPDATING_EXPLAIN_STEP3'		=> 'Upload the new files',
! 	'EXTENSION_UPDATING_EXPLAIN_STEP4'		=> 'Enable the extension',
! 
  
  	'EXTENSION_DELETE_DATA_CONFIRM'	=> 'Are you sure that you wish to delete the data associated with “%s”?<br /><br />This removes all of its data and settings and cannot be undone!',
  	'EXTENSION_DISABLE_CONFIRM'		=> 'Are you sure that you wish to disable the “%s” extension?',
diff -x images -crNEB release-4.0.0-a1/language/en/install.php release-4.0.0-a2-dev/language/en/install.php
*** release-4.0.0-a1/language/en/install.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/language/en/install.php	Wed Apr  1 17:47:36 2026
***************
*** 84,89 ****
--- 84,90 ----
  
  	'INSTALL_PHPBB_INSTALLED'		=> 'phpBB is already installed.',
  	'INSTALL_PHPBB_NOT_INSTALLED'	=> 'phpBB is not installed yet.',
+ 	'INSTALL_RESTART'				=> 'Restart installation',
  ));
  
  // Requirements translation
***************
*** 105,111 ****
  
  	// Server requirements
  	'PHP_VERSION_REQD'					=> 'PHP version',
! 	'PHP_VERSION_REQD_EXPLAIN'			=> 'phpBB requires PHP version 7.2.0 or higher.',
  	'PHP_GETIMAGESIZE_SUPPORT'			=> 'PHP getimagesize() function is required',
  	'PHP_GETIMAGESIZE_SUPPORT_EXPLAIN'	=> 'In order for phpBB to function correctly, the getimagesize function needs to be available.',
  	'PCRE_UTF_SUPPORT'					=> 'PCRE UTF-8 support',
--- 106,112 ----
  
  	// Server requirements
  	'PHP_VERSION_REQD'					=> 'PHP version',
! 	'PHP_VERSION_REQD_EXPLAIN'			=> 'phpBB requires PHP version 8.2.0 or higher.',
  	'PHP_GETIMAGESIZE_SUPPORT'			=> 'PHP getimagesize() function is required',
  	'PHP_GETIMAGESIZE_SUPPORT_EXPLAIN'	=> 'In order for phpBB to function correctly, the getimagesize function needs to be available.',
  	'PCRE_UTF_SUPPORT'					=> 'PCRE UTF-8 support',
diff -x images -crNEB release-4.0.0-a1/language/en/posting.php release-4.0.0-a2-dev/language/en/posting.php
*** release-4.0.0-a1/language/en/posting.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/language/en/posting.php	Wed Apr  1 17:47:36 2026
***************
*** 158,163 ****
--- 158,164 ----
  		2	=> 'Your images may only be up to %d pixels wide.',
  	),
  
+ 	'MENTION_NO_MATCH_FOUND'	=> 'No match found',
  	'MESSAGE_BODY_EXPLAIN'		=> array(
  		0	=> '', // zero means no limit, so we don't view a message here.
  		1	=> 'Enter your message here, it may contain no more than <strong>%d</strong> character.',
diff -x images -crNEB release-4.0.0-a1/mcp.php release-4.0.0-a2-dev/mcp.php
*** release-4.0.0-a1/mcp.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/mcp.php	Wed Apr  1 17:47:36 2026
***************
*** 43,49 ****
  {
  	if ($user->data['is_bot'])
  	{
! 		redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
  	}
  
  	login_box('', $user->lang['LOGIN_EXPLAIN_MCP']);
--- 43,51 ----
  {
  	if ($user->data['is_bot'])
  	{
! 		/** @var \phpbb\controller\helper $controller_helper */
! 		$controller_helper = $phpbb_container->get('controller.helper');
! 		redirect($controller_helper->route('phpbb_index_controller'));
  	}
  
  	login_box('', $user->lang['LOGIN_EXPLAIN_MCP']);
diff -x images -crNEB release-4.0.0-a1/memberlist.php release-4.0.0-a2-dev/memberlist.php
*** release-4.0.0-a1/memberlist.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/memberlist.php	Wed Apr  1 17:47:36 2026
***************
*** 43,53 ****
  $group_id	= $request->variable('g', 0);
  $topic_id	= $request->variable('t', 0);
  
  // Redirect when old mode is used
  if ($mode == 'leaders')
  {
  	send_status_line(301, 'Moved Permanently');
! 	redirect(append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=team'));
  }
  
  // Check our mode...
--- 43,56 ----
  $group_id	= $request->variable('g', 0);
  $topic_id	= $request->variable('t', 0);
  
+ /** @var \phpbb\controller\helper $controller_helper */
+ $controller_helper = $phpbb_container->get('controller.helper');
+ 
  // Redirect when old mode is used
  if ($mode == 'leaders')
  {
  	send_status_line(301, 'Moved Permanently');
! 	redirect($controller_helper->route('phpbb_members_team', [], false));
  }
  
  // Check our mode...
***************
*** 62,67 ****
--- 65,75 ----
  	case 'contactadmin':
  	break;
  
+ 	case 'team':
+ 		send_status_line(301, 'Moved Permanently');
+ 		redirect($controller_helper->route('phpbb_members_team', [], false));
+ 	break;
+ 
  	case 'livesearch':
  		if (!$config['allow_live_searches'])
  		{
***************
*** 102,378 ****
  // What do you want to do today? ... oops, I think that line is taken ...
  switch ($mode)
  {
- 	case 'team':
- 		// Display a listing of board admins, moderators
- 		if (!function_exists('user_get_id_name'))
- 		{
- 			include($phpbb_root_path . 'includes/functions_user.' . $phpEx);
- 		}
- 
- 		$page_title = $user->lang['THE_TEAM'];
- 		$template_html = 'memberlist_team.html';
- 
- 		$sql = 'SELECT *
- 			FROM ' . TEAMPAGE_TABLE . '
- 			ORDER BY teampage_position ASC';
- 		$result = $db->sql_query($sql, 3600);
- 		$teampage_data = $db->sql_fetchrowset($result);
- 		$db->sql_freeresult($result);
- 
- 		$sql_ary = array(
- 			'SELECT'	=> 'g.group_id, g.group_name, g.group_colour, g.group_type, ug.user_id as ug_user_id, t.teampage_id',
- 
- 			'FROM'		=> array(GROUPS_TABLE => 'g'),
- 
- 			'LEFT_JOIN'	=> array(
- 				array(
- 					'FROM'	=> array(TEAMPAGE_TABLE => 't'),
- 					'ON'	=> 't.group_id = g.group_id',
- 				),
- 				array(
- 					'FROM'	=> array(USER_GROUP_TABLE => 'ug'),
- 					'ON'	=> 'ug.group_id = g.group_id AND ug.user_pending = 0 AND ug.user_id = ' . (int) $user->data['user_id'],
- 				),
- 			),
- 		);
- 
- 		$result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary));
- 
- 		$group_ids = $groups_ary = array();
- 		while ($row = $db->sql_fetchrow($result))
- 		{
- 			if ($row['group_type'] == GROUP_HIDDEN && !$auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel') && $row['ug_user_id'] != $user->data['user_id'])
- 			{
- 				$row['group_name'] = $user->lang['GROUP_UNDISCLOSED'];
- 				$row['u_group'] = '';
- 			}
- 			else
- 			{
- 				$row['group_name'] = $group_helper->get_name($row['group_name']);
- 				$row['u_group'] = append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=group&amp;g=' . $row['group_id']);
- 			}
- 
- 			if ($row['teampage_id'])
- 			{
- 				// Only put groups into the array we want to display.
- 				// We are fetching all groups, to ensure we got all data for default groups.
- 				$group_ids[] = (int) $row['group_id'];
- 			}
- 			$groups_ary[(int) $row['group_id']] = $row;
- 		}
- 		$db->sql_freeresult($result);
- 
- 		$sql_ary = array(
- 			'SELECT'	=> 'u.user_id, u.group_id as default_group, u.username, u.username_clean, u.user_colour, u.user_type, u.user_rank, u.user_posts, u.user_allow_pm, g.group_id',
- 
- 			'FROM'		=> array(
- 				USER_GROUP_TABLE => 'ug',
- 			),
- 
- 			'LEFT_JOIN'	=> array(
- 				array(
- 					'FROM'	=> array(USERS_TABLE => 'u'),
- 					'ON'	=> 'ug.user_id = u.user_id',
- 				),
- 				array(
- 					'FROM'	=> array(GROUPS_TABLE => 'g'),
- 					'ON'	=> 'ug.group_id = g.group_id',
- 				),
- 			),
- 
- 			'WHERE'		=> $db->sql_in_set('g.group_id', $group_ids, false, true) . ' AND ug.user_pending = 0',
- 
- 			'ORDER_BY'	=> 'u.username_clean ASC',
- 		);
- 
- 		/**
- 		 * Modify the query used to get the users for the team page
- 		 *
- 		 * @event core.memberlist_team_modify_query
- 		 * @var array	sql_ary			Array containing the query
- 		 * @var array	group_ids		Array of group ids
- 		 * @var array	teampage_data	The teampage data
- 		 * @since 3.1.3-RC1
- 		 */
- 		$vars = array(
- 			'sql_ary',
- 			'group_ids',
- 			'teampage_data',
- 		);
- 		extract($phpbb_dispatcher->trigger_event('core.memberlist_team_modify_query', compact($vars)));
- 
- 		$result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary));
- 
- 		$user_ary = $user_ids = $group_users = array();
- 		while ($row = $db->sql_fetchrow($result))
- 		{
- 			$row['forums'] = '';
- 			$row['forums_ary'] = array();
- 			$user_ary[(int) $row['user_id']] = $row;
- 			$user_ids[] = (int) $row['user_id'];
- 			$group_users[(int) $row['group_id']][] = (int) $row['user_id'];
- 		}
- 		$db->sql_freeresult($result);
- 
- 		$user_ids = array_unique($user_ids);
- 
- 		if (!empty($user_ids) && $config['teampage_forums'])
- 		{
- 			$template->assign_var('S_DISPLAY_MODERATOR_FORUMS', true);
- 			// Get all moderators
- 			$perm_ary = $auth->acl_get_list($user_ids, array('m_'), false);
- 
- 			foreach ($perm_ary as $forum_id => $forum_ary)
- 			{
- 				foreach ($forum_ary as $auth_option => $id_ary)
- 				{
- 					foreach ($id_ary as $id)
- 					{
- 						if (!$forum_id)
- 						{
- 							$user_ary[$id]['forums'] = $user->lang['ALL_FORUMS'];
- 						}
- 						else
- 						{
- 							$user_ary[$id]['forums_ary'][] = $forum_id;
- 						}
- 					}
- 				}
- 			}
- 
- 			$sql = 'SELECT forum_id, forum_name
- 				FROM ' . FORUMS_TABLE;
- 			$result = $db->sql_query($sql);
- 
- 			$forums = array();
- 			while ($row = $db->sql_fetchrow($result))
- 			{
- 				$forums[$row['forum_id']] = $row['forum_name'];
- 			}
- 			$db->sql_freeresult($result);
- 
- 			foreach ($user_ary as $user_id => $user_data)
- 			{
- 				if (!$user_data['forums'])
- 				{
- 					foreach ($user_data['forums_ary'] as $forum_id)
- 					{
- 						$user_ary[$user_id]['forums_options'] = true;
- 						if (isset($forums[$forum_id]))
- 						{
- 							if ($auth->acl_get('f_list', $forum_id))
- 							{
- 								$user_ary[$user_id]['forums'] .= '<option value="">' . $forums[$forum_id] . '</option>';
- 							}
- 						}
- 					}
- 				}
- 			}
- 		}
- 
- 		$parent_team = 0;
- 		foreach ($teampage_data as $team_data)
- 		{
- 			// If this team entry has no group, it's a category
- 			if (!$team_data['group_id'])
- 			{
- 				$template->assign_block_vars('group', array(
- 					'GROUP_NAME'  => $team_data['teampage_name'],
- 				));
- 
- 				$parent_team = (int) $team_data['teampage_id'];
- 				continue;
- 			}
- 
- 			$group_data = $groups_ary[(int) $team_data['group_id']];
- 			$group_id = (int) $team_data['group_id'];
- 
- 			if (!$team_data['teampage_parent'])
- 			{
- 				// If the group does not have a parent category, we display the groupname as category
- 				$template->assign_block_vars('group', array(
- 					'GROUP_NAME'	=> $group_data['group_name'],
- 					'GROUP_COLOR'	=> $group_data['group_colour'],
- 					'U_GROUP'		=> $group_data['u_group'],
- 				));
- 			}
- 
- 			// Display group members.
- 			if (!empty($group_users[$group_id]))
- 			{
- 				foreach ($group_users[$group_id] as $user_id)
- 				{
- 					if (isset($user_ary[$user_id]))
- 					{
- 						$row = $user_ary[$user_id];
- 						if ($config['teampage_memberships'] == 1 && ($group_id != $groups_ary[$row['default_group']]['group_id']) && $groups_ary[$row['default_group']]['teampage_id'])
- 						{
- 							// Display users in their primary group, instead of the first group, when it is displayed on the teampage.
- 							continue;
- 						}
- 
- 						$user_rank_data = phpbb_get_user_rank($row, (($row['user_id'] == ANONYMOUS) ? false : $row['user_posts']));
- 
- 						$template_vars = array(
- 							'USER_ID'		=> $row['user_id'],
- 							'FORUMS'		=> $row['forums'],
- 							'FORUM_OPTIONS'	=> (isset($row['forums_options'])) ? true : false,
- 							'RANK_TITLE'	=> $user_rank_data['title'],
- 
- 							'GROUP_NAME'	=> $groups_ary[$row['default_group']]['group_name'],
- 							'GROUP_COLOR'	=> $groups_ary[$row['default_group']]['group_colour'],
- 							'U_GROUP'		=> $groups_ary[$row['default_group']]['u_group'],
- 
- 							'RANK_IMG'		=> $user_rank_data['img'],
- 							'RANK_IMG_SRC'	=> $user_rank_data['img_src'],
- 
- 							'S_INACTIVE'	=> $row['user_type'] == USER_INACTIVE,
- 
- 							'U_PM'			=> ($config['allow_privmsg'] && $auth->acl_get('u_sendpm') && ($row['user_allow_pm'] || $auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_'))) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;mode=compose&amp;u=' . $row['user_id']) : '',
- 
- 							'USERNAME_FULL'		=> get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']),
- 							'USERNAME'			=> get_username_string('username', $row['user_id'], $row['username'], $row['user_colour']),
- 							'USER_COLOR'		=> get_username_string('colour', $row['user_id'], $row['username'], $row['user_colour']),
- 							'U_VIEW_PROFILE'	=> get_username_string('profile', $row['user_id'], $row['username'], $row['user_colour']),
- 						);
- 
- 						/**
- 						 * Modify the template vars for displaying the user in the groups on the teampage
- 						 *
- 						 * @event core.memberlist_team_modify_template_vars
- 						 * @var array	template_vars		Array containing the query
- 						 * @var array	row					Array containing the action user row
- 						 * @var array	groups_ary			Array of groups with all users that should be displayed
- 						 * @since 3.1.3-RC1
- 						 */
- 						$vars = array(
- 							'template_vars',
- 							'row',
- 							'groups_ary',
- 						);
- 						extract($phpbb_dispatcher->trigger_event('core.memberlist_team_modify_template_vars', compact($vars)));
- 
- 						$template->assign_block_vars('group.user', $template_vars);
- 
- 						if ($config['teampage_memberships'] != 2)
- 						{
- 							unset($user_ary[$user_id]);
- 						}
- 					}
- 				}
- 			}
- 		}
- 
- 		$template->assign_block_vars('navlinks', array(
- 			'BREADCRUMB_NAME'	=> $page_title,
- 			'U_BREADCRUMB'		=> append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=team"),
- 		));
- 
- 		$template->assign_vars(array(
- 			'PM_IMG'		=> $user->img('icon_contact_pm', $user->lang['SEND_PRIVATE_MESSAGE']))
- 		);
- 	break;
- 
  	case 'viewprofile':
  		// Display a profile
  		if ($user_id == ANONYMOUS && !$username)
--- 110,115 ----
***************
*** 691,696 ****
--- 428,434 ----
  			'U_REMOVE_FOE'				=> ($foe && $foes_enabled) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=zebra&amp;remove=1&amp;mode=foes&amp;usernames[]=' . $user_id) : '',
  
  			'U_CANONICAL'				=> generate_board_url() . '/' . append_sid("memberlist.$phpEx", 'mode=viewprofile&amp;u=' . $user_id, true, ''),
+ 			'USER_ID'					=> $user_id,
  		);
  
  		/**
diff -x images -crNEB release-4.0.0-a1/phpbb/auth/provider/oauth/token_storage.php release-4.0.0-a2-dev/phpbb/auth/provider/oauth/token_storage.php
*** release-4.0.0-a1/phpbb/auth/provider/oauth/token_storage.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/auth/provider/oauth/token_storage.php	Wed Apr  1 17:47:36 2026
***************
*** 537,545 ****
  	 * A helper function that JSON encodes a TokenInterface's data.
  	 *
  	 * @param TokenInterface	$token
! 	 * @return string					The json encoded TokenInterface's data
  	 */
! 	public function json_encode_token(TokenInterface $token)
  	{
  		$members = [
  			'accessToken'	=> $token->getAccessToken(),
--- 537,545 ----
  	 * A helper function that JSON encodes a TokenInterface's data.
  	 *
  	 * @param TokenInterface	$token
! 	 * @return false|string		The json encoded TokenInterface's data
  	 */
! 	public function json_encode_token(TokenInterface $token): false|string
  	{
  		$members = [
  			'accessToken'	=> $token->getAccessToken(),
diff -x images -crNEB release-4.0.0-a1/phpbb/avatar/driver/driver.php release-4.0.0-a2-dev/phpbb/avatar/driver/driver.php
*** release-4.0.0-a1/phpbb/avatar/driver/driver.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/avatar/driver/driver.php	Wed Apr  1 17:47:36 2026
***************
*** 129,135 ****
  	*/
  	public function get_config_name()
  	{
! 		return preg_replace('#^phpbb\\\\avatar\\\\driver\\\\#', '', get_class($this));
  	}
  
  	/**
--- 129,135 ----
  	*/
  	public function get_config_name()
  	{
! 		return preg_replace('#^phpbb\\\\avatar\\\\driver\\\\#', '', get_class($this)) ?: '';
  	}
  
  	/**
diff -x images -crNEB release-4.0.0-a1/phpbb/avatar/helper.php release-4.0.0-a2-dev/phpbb/avatar/helper.php
*** release-4.0.0-a1/phpbb/avatar/helper.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/avatar/helper.php	Wed Apr  1 17:47:36 2026
***************
*** 81,87 ****
  	 */
  	public function get_template_vars(array $avatar, string $prefix = ''): array
  	{
! 		$prefix = $prefix && substr($prefix, -1) !== '_' ? "{$prefix}_" : $prefix;
  
  		return [
  			"{$prefix}AVATAR"			=> $avatar,
--- 81,87 ----
  	 */
  	public function get_template_vars(array $avatar, string $prefix = ''): array
  	{
! 		$prefix = $prefix && !str_ends_with($prefix, '_') ? "{$prefix}_" : $prefix;
  
  		return [
  			"{$prefix}AVATAR"			=> $avatar,
***************
*** 94,100 ****
  			"{$prefix}AVATAR_HEIGHT"	=> $avatar['height'],
  
  			"{$prefix}AVATAR_LAZY"		=> $avatar['lazy'],
- 			"{$prefix}AVATAR_HTML"		=> $avatar['html'],
  		];
  	}
  
--- 94,99 ----
***************
*** 144,150 ****
  		if (!$this->config['allow_avatar'] && !$ignore_config)
  		{
  			return [
! 				'html'		=> '',
  				'lazy'		=> false,
  				'src'		=> '',
  				'title'		=> '',
--- 143,150 ----
  		if (!$this->config['allow_avatar'] && !$ignore_config)
  		{
  			return [
! 				'id'		=> 0,
! 				'username'	=> '',
  				'lazy'		=> false,
  				'src'		=> '',
  				'title'		=> '',
***************
*** 155,160 ****
--- 155,162 ----
  		}
  
  		$data = [
+ 			'id'		=> $row['id'] ?? 0,
+ 			'username'	=> $row['username'] ?? '',
  			'src'		=> $row['avatar'],
  			'width'		=> $row['avatar_width'],
  			'height'	=> $row['avatar_height'],
diff -x images -crNEB release-4.0.0-a1/phpbb/avatar/manager.php release-4.0.0-a2-dev/phpbb/avatar/manager.php
*** release-4.0.0-a1/phpbb/avatar/manager.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/avatar/manager.php	Wed Apr  1 17:47:36 2026
***************
*** 202,210 ****
--- 202,216 ----
  			return self::$default_row;
  		}
  
+ 		$required_keys = ['user_id', 'group_id', 'username'];
  		$output = [];
  		foreach ($row as $key => $value)
  		{
+ 			if (!str_starts_with($key, $prefix ? "{$prefix}_avatar": 'avatar') && !in_array($key, $required_keys))
+ 			{
+ 				continue;
+ 			}
+ 
  			$key = preg_replace("#^(?:{$prefix}_)#", '', (string) $key);
  			$output[$key] = $value;
  		}
diff -x images -crNEB release-4.0.0-a1/phpbb/captcha/plugins/captcha_abstract.php release-4.0.0-a2-dev/phpbb/captcha/plugins/captcha_abstract.php
*** release-4.0.0-a1/phpbb/captcha/plugins/captcha_abstract.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/captcha/plugins/captcha_abstract.php	Wed Apr  1 17:47:36 2026
***************
*** 158,164 ****
  			LEFT JOIN ' . SESSIONS_TABLE . ' s ON (c.session_id = s.session_id)
  			WHERE s.session_id IS NULL' .
  				((empty($type)) ? '' : ' AND c.confirm_type = ' . (int) $type);
! 		$result = $db->sql_query($sql);
  
  		if ($row = $db->sql_fetchrow($result))
  		{
--- 158,164 ----
  			LEFT JOIN ' . SESSIONS_TABLE . ' s ON (c.session_id = s.session_id)
  			WHERE s.session_id IS NULL' .
  				((empty($type)) ? '' : ' AND c.confirm_type = ' . (int) $type);
! 		$result = $db->sql_query_limit($sql, 100000); // Prevent possible timeouts
  
  		if ($row = $db->sql_fetchrow($result))
  		{
diff -x images -crNEB release-4.0.0-a1/phpbb/captcha/plugins/qa.php release-4.0.0-a2-dev/phpbb/captcha/plugins/qa.php
*** release-4.0.0-a1/phpbb/captcha/plugins/qa.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/captcha/plugins/qa.php	Wed Apr  1 17:47:36 2026
***************
*** 511,516 ****
--- 511,517 ----
  		global $db, $request;
  
  		$answer = ($this->question_strict) ? $request->variable('qa_answer', '', true) : utf8_clean_string($request->variable('qa_answer', '', true));
+ 		$confirm_id = $request->variable('qa_confirm_id', '');
  
  		$sql = 'SELECT answer_text
  			FROM ' . $this->table_captcha_answers . '
***************
*** 521,527 ****
  		{
  			$solution = ($this->question_strict) ? $row['answer_text'] : utf8_clean_string($row['answer_text']);
  
! 			if ($solution === $answer)
  			{
  				$this->solved = true;
  
--- 522,528 ----
  		{
  			$solution = ($this->question_strict) ? $row['answer_text'] : utf8_clean_string($row['answer_text']);
  
! 			if ($solution === $answer && $this->confirm_id === $confirm_id)
  			{
  				$this->solved = true;
  
diff -x images -crNEB release-4.0.0-a1/phpbb/composer/installer.php release-4.0.0-a2-dev/phpbb/composer/installer.php
*** release-4.0.0-a1/phpbb/composer/installer.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/composer/installer.php	Wed Apr  1 17:47:36 2026
***************
*** 390,411 ****
  				}
  			}
  
! 			foreach ($compatible_packages as $name => $versions)
  			{
  				// Determine the highest version of the package
  				/** @var CompletePackage|CompleteAliasPackage $highest_version */
  				$highest_version = null;
  
  				// Sort the versions array in descending order
! 				usort($versions, function ($a, $b)
  				{
  					return version_compare($b->getVersion(), $a->getVersion());
  				});
  
  				// The first element in the sorted array is the highest version
! 				if (!empty($versions))
  				{
! 					$highest_version = $versions[0];
  
  					// If highest version is a non-numeric dev branch, it's an instance of CompleteAliasPackage,
  					// so we need to get the package being aliased in order to show the true non-numeric version.
--- 390,411 ----
  				}
  			}
  
! 			foreach ($compatible_packages as $package_name => $package_versions)
  			{
  				// Determine the highest version of the package
  				/** @var CompletePackage|CompleteAliasPackage $highest_version */
  				$highest_version = null;
  
  				// Sort the versions array in descending order
! 				usort($package_versions, function ($a, $b)
  				{
  					return version_compare($b->getVersion(), $a->getVersion());
  				});
  
  				// The first element in the sorted array is the highest version
! 				if (!empty($package_versions))
  				{
! 					$highest_version = $package_versions[0];
  
  					// If highest version is a non-numeric dev branch, it's an instance of CompleteAliasPackage,
  					// so we need to get the package being aliased in order to show the true non-numeric version.
***************
*** 416,438 ****
  				}
  
  				// Generates the entry
! 				$available[$name] = [];
! 				$available[$name]['name'] = $highest_version->getPrettyName();
! 				$available[$name]['display_name'] = $highest_version->getExtra()['display-name'];
! 				$available[$name]['composer_name'] = $highest_version->getName();
! 				$available[$name]['version'] = $highest_version->getPrettyVersion();
  
  				if ($highest_version instanceof CompletePackage)
  				{
! 					$available[$name]['description'] = $highest_version->getDescription();
! 					$available[$name]['url'] = $highest_version->getHomepage();
! 					$available[$name]['authors'] = $highest_version->getAuthors();
  				}
  				else
  				{
! 					$available[$name]['description'] = '';
! 					$available[$name]['url'] = '';
! 					$available[$name]['authors'] = [];
  				}
  			}
  
--- 416,438 ----
  				}
  
  				// Generates the entry
! 				$available[$package_name] = [];
! 				$available[$package_name]['name'] = $highest_version->getPrettyName();
! 				$available[$package_name]['display_name'] = $highest_version->getExtra()['display-name'];
! 				$available[$package_name]['composer_name'] = $highest_version->getName();
! 				$available[$package_name]['version'] = $highest_version->getPrettyVersion();
  
  				if ($highest_version instanceof CompletePackage)
  				{
! 					$available[$package_name]['description'] = $highest_version->getDescription();
! 					$available[$package_name]['url'] = $highest_version->getHomepage();
! 					$available[$package_name]['authors'] = $highest_version->getAuthors();
  				}
  				else
  				{
! 					$available[$package_name]['description'] = '';
! 					$available[$package_name]['url'] = '';
! 					$available[$package_name]['authors'] = [];
  				}
  			}
  
***************
*** 525,530 ****
--- 525,545 ----
  					}
  				}
  
+ 				// Check for composer/installers requirement - must support version 2.0 or later
+ 				if (isset($requires['composer/installers']))
+ 				{
+ 					$installers_constraint = $requires['composer/installers']->getConstraint();
+ 					$min_version_constraint = $version_parser->parseConstraints('>=2.0');
+ 					if (!$min_version_constraint->matches($installers_constraint))
+ 					{
+ 						continue;
+ 					}
+ 				}
+ 				else
+ 				{
+ 					continue;
+ 				}
+ 
  				$compatible_packages[$package_name][] = $version;
  			}
  			catch (\Exception $e)
***************
*** 545,552 ****
  	 */
  	protected function generate_ext_json_file(array $packages)
  	{
- 		$io = new NullIO();
- 
  		$composer = $this->get_composer(null);
  
  		$core_packages = $this->get_core_packages($composer);
--- 560,565 ----
***************
*** 587,594 ****
--- 600,747 ----
  			$lockFile->write([]);
  		}
  
+ 		// First pass write: base file with requested packages as provided
  		$json_file->write($ext_json_data);
  		$this->ext_json_file_backup = $ext_json_file_backup;
+ 
+ 		// Second pass: resolve and pin the highest compatible versions for unconstrained requested packages
+ 		try
+ 		{
+ 			// Build a list of requested packages without explicit constraints
+ 			$unconstrained = [];
+ 			foreach ($packages as $name => $constraint)
+ 			{
+ 				// The $packages array can be either ['vendor/package' => '^1.2'] or ['vendor/package'] (numeric keys).
+ 				if (is_int($name))
+ 				{
+ 					// Numeric key means just a name
+ 					$package_name = $constraint;
+ 					$unconstrained[$package_name] = true;
+ 				}
+ 				else
+ 				{
+ 					// If constraint is empty or '*' treat as unconstrained
+ 					if ($constraint === '' || $constraint === '*' || $constraint === null)
+ 					{
+ 						$unconstrained[$name] = true;
+ 					}
+ 				}
+ 			}
+ 
+ 			if (!empty($unconstrained))
+ 			{
+ 				// Load composer on the just-written file so repositories and core constraints are available
+ 				$ext_composer = $this->get_composer($this->get_composer_ext_json_filename());
+ 
+ 				/** @var ConstraintInterface $core_constraint */
+ 				$core_constraint = $ext_composer->getPackage()->getRequires()['phpbb/phpbb']->getConstraint();
+ 				$core_stability = $ext_composer->getPackage()->getMinimumStability();
+ 
+ 				// Resolve highest compatible versions for each unconstrained package
+ 				$pins = $this->resolve_highest_versions(array_keys($unconstrained), $ext_composer, $core_constraint, $core_stability);
+ 
+ 				if (!empty($pins))
+ 				{
+ 					// Merge pins into require section, overwriting unconstrained entries
+ 					foreach ($pins as $pkg => $version)
+ 					{
+ 						$ext_json_data['require'][$pkg] = $version;
+ 					}
+ 
+ 					// Rewrite composer-ext.json with pinned versions
+ 					$json_file->write($ext_json_data);
+ 				}
+ 			}
+ 		}
+ 		catch (\Exception $e)
+ 		{
+ 			// If resolution fails for any reason, keep the first-pass file intact (Composer will still resolve).
+ 			// Intentionally swallow to avoid breaking installation flow.
+ 		}
+ 	}
+ 
+ 	/**
+ 	 * Resolve the highest compatible versions for the given package names
+ 	 * based on repositories and phpBB/PHP constraints from the provided Composer instance.
+ 	 *
+ 	 * @param array $package_names list of package names to resolve
+ 	 * @param Composer|PartialComposer $composer Composer instance configured with repositories
+ 	 * @param ConstraintInterface $core_constraint phpBB version constraint
+ 	 * @param string $core_stability minimum stability
+ 	 * @return array [packageName => prettyVersion]
+ 	 */
+ 	protected function resolve_highest_versions(array $package_names, $composer, ConstraintInterface $core_constraint, $core_stability): array
+ 	{
+ 		$compatible_packages = [];
+ 		$repositories = $composer->getRepositoryManager()->getRepositories();
+ 
+ 		foreach ($repositories as $repository)
+ 		{
+ 			try
+ 			{
+ 				if ($repository instanceof ComposerRepository)
+ 				{
+ 					foreach ($package_names as $name)
+ 					{
+ 						$versions = $repository->findPackages($name);
+ 						if (!empty($versions))
+ 						{
+ 							$compatible_packages = $this->get_compatible_versions($compatible_packages, $core_constraint, $core_stability, $name, $versions);
+ 						}
+ 					}
+ 				}
+ 				else
+ 				{
+ 					// Preload and filter by name for non-composer repositories
+ 					$package_name = [];
+ 					foreach ($repository->getPackages() as $package)
+ 					{
+ 						$name = $package->getName();
+ 						if (in_array($name, $package_names, true))
+ 						{
+ 							$package_name[$name][] = $package;
+ 						}
+ 					}
+ 
+ 					foreach ($package_name as $name => $versions)
+ 					{
+ 						$compatible_packages = $this->get_compatible_versions($compatible_packages, $core_constraint, $core_stability, $name, $versions);
+ 					}
+ 				}
+ 			}
+ 			catch (\Exception $e)
+ 			{
+ 				// If a repo fails, just skip it.
+ 				continue;
+ 			}
+ 		}
+ 
+ 		$pins = [];
+ 		foreach ($package_names as $name)
+ 		{
+ 			if (empty($compatible_packages[$name]))
+ 			{
+ 				continue;
+ 			}
+ 
+ 			$package_versions = $compatible_packages[$name];
+ 
+ 			// Sort descending by normalized version
+ 			usort($package_versions, function ($a, $b) {
+ 				return version_compare($b->getVersion(), $a->getVersion());
+ 			});
+ 
+ 			$highest = $package_versions[0];
+ 			if ($highest instanceof CompleteAliasPackage)
+ 			{
+ 				$highest = $highest->getAliasOf();
+ 			}
+ 
+ 			// Pin to the resolved highest compatible version using its pretty version
+ 			$pins[$name] = $highest->getPrettyVersion();
+ 		}
+ 
+ 		return $pins;
  	}
  
  	/**
diff -x images -crNEB release-4.0.0-a1/phpbb/composer.json release-4.0.0-a2-dev/phpbb/composer.json
*** release-4.0.0-a1/phpbb/composer.json	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/composer.json	Wed Apr  1 17:47:36 2026
***************
*** 23,29 ****
  		"classmap": [""]
  	},
  	"require": {
! 		"php": "^8.1"
  	},
  	"extra": {
  		"branch-alias": {
--- 23,29 ----
  		"classmap": [""]
  	},
  	"require": {
! 		"php": "^8.2"
  	},
  	"extra": {
  		"branch-alias": {
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/cache/purge.php release-4.0.0-a2-dev/phpbb/console/command/cache/purge.php
*** release-4.0.0-a1/phpbb/console/command/cache/purge.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/cache/purge.php	Wed Apr  1 17:47:36 2026
***************
*** 81,87 ****
  	*
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$this->config->increment('assets_version', 1);
  		$this->cache->purge();
--- 81,87 ----
  	*
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$this->config->increment('assets_version', 1);
  		$this->cache->purge();
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/config/delete.php release-4.0.0-a2-dev/phpbb/console/command/config/delete.php
*** release-4.0.0-a1/phpbb/console/command/config/delete.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/config/delete.php	Wed Apr  1 17:47:36 2026
***************
*** 47,53 ****
  	 * @return int
  	 * @see \phpbb\config\config::delete()
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 47,53 ----
  	 * @return int
  	 * @see \phpbb\config\config::delete()
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/config/get.php release-4.0.0-a2-dev/phpbb/console/command/config/get.php
*** release-4.0.0-a1/phpbb/console/command/config/get.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/config/get.php	Wed Apr  1 17:47:36 2026
***************
*** 54,60 ****
  	* @return int
  	* @see \phpbb\config\config::offsetGet()
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 54,60 ----
  	* @return int
  	* @see \phpbb\config\config::offsetGet()
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/config/increment.php release-4.0.0-a2-dev/phpbb/console/command/config/increment.php
*** release-4.0.0-a1/phpbb/console/command/config/increment.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/config/increment.php	Wed Apr  1 17:47:36 2026
***************
*** 59,65 ****
  	* @return int
  	* @see \phpbb\config\config::increment()
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 59,65 ----
  	* @return int
  	* @see \phpbb\config\config::increment()
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/config/set.php release-4.0.0-a2-dev/phpbb/console/command/config/set.php
*** release-4.0.0-a1/phpbb/console/command/config/set.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/config/set.php	Wed Apr  1 17:47:36 2026
***************
*** 59,65 ****
  	* @return int
  	* @see \phpbb\config\config::set()
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 59,65 ----
  	* @return int
  	* @see \phpbb\config\config::set()
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/config/set_atomic.php release-4.0.0-a2-dev/phpbb/console/command/config/set_atomic.php
*** release-4.0.0-a1/phpbb/console/command/config/set_atomic.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/config/set_atomic.php	Wed Apr  1 17:47:36 2026
***************
*** 65,71 ****
  	* @return int 0 if the value was changed, 1 otherwise.
  	* @see \phpbb\config\config::set_atomic()
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 65,71 ----
  	* @return int 0 if the value was changed, 1 otherwise.
  	* @see \phpbb\config\config::set_atomic()
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/cron/cron_list.php release-4.0.0-a2-dev/phpbb/console/command/cron/cron_list.php
*** release-4.0.0-a1/phpbb/console/command/cron/cron_list.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/cron/cron_list.php	Wed Apr  1 17:47:36 2026
***************
*** 55,61 ****
  	*
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 55,61 ----
  	*
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/cron/run.php release-4.0.0-a2-dev/phpbb/console/command/cron/run.php
*** release-4.0.0-a1/phpbb/console/command/cron/run.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/cron/run.php	Wed Apr  1 17:47:36 2026
***************
*** 76,82 ****
  	*
  	* @return int 0 if all is ok, 1 if a lock error occurred and 2 if no task matching the argument was found.
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		if ($this->lock_db->acquire())
  		{
--- 76,82 ----
  	*
  	* @return int 0 if all is ok, 1 if a lock error occurred and 2 if no task matching the argument was found.
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		if ($this->lock_db->acquire())
  		{
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/db/list_command.php release-4.0.0-a2-dev/phpbb/console/command/db/list_command.php
*** release-4.0.0-a1/phpbb/console/command/db/list_command.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/db/list_command.php	Wed Apr  1 17:47:36 2026
***************
*** 47,53 ****
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 47,53 ----
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/db/migrate.php release-4.0.0-a2-dev/phpbb/console/command/db/migrate.php
*** release-4.0.0-a1/phpbb/console/command/db/migrate.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/db/migrate.php	Wed Apr  1 17:47:36 2026
***************
*** 63,69 ****
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 63,69 ----
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/db/revert.php release-4.0.0-a2-dev/phpbb/console/command/db/revert.php
*** release-4.0.0-a1/phpbb/console/command/db/revert.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/db/revert.php	Wed Apr  1 17:47:36 2026
***************
*** 47,53 ****
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 47,53 ----
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/dev/migration_tips.php release-4.0.0-a2-dev/phpbb/console/command/dev/migration_tips.php
*** release-4.0.0-a1/phpbb/console/command/dev/migration_tips.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/dev/migration_tips.php	Wed Apr  1 17:47:36 2026
***************
*** 48,54 ****
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$migrations = $this->extension_manager->get_finder()
  			->set_extensions(array())
--- 48,54 ----
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$migrations = $this->extension_manager->get_finder()
  			->set_extensions(array())
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/extension/disable.php release-4.0.0-a2-dev/phpbb/console/command/extension/disable.php
*** release-4.0.0-a1/phpbb/console/command/extension/disable.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/extension/disable.php	Wed Apr  1 17:47:36 2026
***************
*** 46,52 ****
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 46,52 ----
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/extension/enable.php release-4.0.0-a2-dev/phpbb/console/command/extension/enable.php
*** release-4.0.0-a1/phpbb/console/command/extension/enable.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/extension/enable.php	Wed Apr  1 17:47:36 2026
***************
*** 46,52 ****
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 46,52 ----
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/extension/install.php release-4.0.0-a2-dev/phpbb/console/command/extension/install.php
*** release-4.0.0-a1/phpbb/console/command/extension/install.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/extension/install.php	Wed Apr  1 17:47:36 2026
***************
*** 75,81 ****
  	* @param OutputInterface $output
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$output->getFormatter()->setStyle('warning', new OutputFormatterStyle('black', 'yellow'));
  
--- 75,81 ----
  	* @param OutputInterface $output
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$output->getFormatter()->setStyle('warning', new OutputFormatterStyle('black', 'yellow'));
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/extension/list_available.php release-4.0.0-a2-dev/phpbb/console/command/extension/list_available.php
*** release-4.0.0-a1/phpbb/console/command/extension/list_available.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/extension/list_available.php	Wed Apr  1 17:47:36 2026
***************
*** 55,61 ****
  	* @param OutputInterface $output
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 55,61 ----
  	* @param OutputInterface $output
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/extension/manage.php release-4.0.0-a2-dev/phpbb/console/command/extension/manage.php
*** release-4.0.0-a1/phpbb/console/command/extension/manage.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/extension/manage.php	Wed Apr  1 17:47:36 2026
***************
*** 69,75 ****
  	* @param OutputInterface $output
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 69,75 ----
  	* @param OutputInterface $output
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/extension/purge.php release-4.0.0-a2-dev/phpbb/console/command/extension/purge.php
*** release-4.0.0-a1/phpbb/console/command/extension/purge.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/extension/purge.php	Wed Apr  1 17:47:36 2026
***************
*** 46,52 ****
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 46,52 ----
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/extension/remove.php release-4.0.0-a2-dev/phpbb/console/command/extension/remove.php
*** release-4.0.0-a1/phpbb/console/command/extension/remove.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/extension/remove.php	Wed Apr  1 17:47:36 2026
***************
*** 75,81 ****
  	* @param OutputInterface $output
  	* @return integer
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$output->getFormatter()->setStyle('warning', new OutputFormatterStyle('black', 'yellow'));
  
--- 75,81 ----
  	* @param OutputInterface $output
  	* @return integer
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$output->getFormatter()->setStyle('warning', new OutputFormatterStyle('black', 'yellow'));
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/extension/show.php release-4.0.0-a2-dev/phpbb/console/command/extension/show.php
*** release-4.0.0-a1/phpbb/console/command/extension/show.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/extension/show.php	Wed Apr  1 17:47:36 2026
***************
*** 40,46 ****
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 40,46 ----
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/extension/update.php release-4.0.0-a2-dev/phpbb/console/command/extension/update.php
*** release-4.0.0-a1/phpbb/console/command/extension/update.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/extension/update.php	Wed Apr  1 17:47:36 2026
***************
*** 67,73 ****
  	* @param OutputInterface $output
  	* @return integer
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$output->getFormatter()->setStyle('warning', new OutputFormatterStyle('black', 'yellow'));
  
--- 67,73 ----
  	* @param OutputInterface $output
  	* @return integer
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$output->getFormatter()->setStyle('warning', new OutputFormatterStyle('black', 'yellow'));
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/fixup/fix_left_right_ids.php release-4.0.0-a2-dev/phpbb/console/command/fixup/fix_left_right_ids.php
*** release-4.0.0-a1/phpbb/console/command/fixup/fix_left_right_ids.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/fixup/fix_left_right_ids.php	Wed Apr  1 17:47:36 2026
***************
*** 67,73 ****
  	*
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 67,73 ----
  	*
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/fixup/update_hashes.php release-4.0.0-a2-dev/phpbb/console/command/fixup/update_hashes.php
*** release-4.0.0-a1/phpbb/console/command/fixup/update_hashes.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/fixup/update_hashes.php	Wed Apr  1 17:47:36 2026
***************
*** 77,83 ****
  	/**
  	 * {@inheritdoc}
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		// Get count to be able to display progress
  		$sql = 'SELECT COUNT(user_id) AS count
--- 77,83 ----
  	/**
  	 * {@inheritdoc}
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		// Get count to be able to display progress
  		$sql = 'SELECT COUNT(user_id) AS count
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/reparser/list_all.php release-4.0.0-a2-dev/phpbb/console/command/reparser/list_all.php
*** release-4.0.0-a1/phpbb/console/command/reparser/list_all.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/reparser/list_all.php	Wed Apr  1 17:47:36 2026
***************
*** 60,66 ****
  	* @param OutputInterface $output
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  		$io->section($this->user->lang('CLI_DESCRIPTION_REPARSER_AVAILABLE'));
--- 60,66 ----
  	* @param OutputInterface $output
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  		$io->section($this->user->lang('CLI_DESCRIPTION_REPARSER_AVAILABLE'));
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/reparser/reparse.php release-4.0.0-a2-dev/phpbb/console/command/reparser/reparse.php
*** release-4.0.0-a1/phpbb/console/command/reparser/reparse.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/reparser/reparse.php	Wed Apr  1 17:47:36 2026
***************
*** 135,141 ****
  	* @param OutputInterface $output
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$this->input = $input;
  		$this->output = $output;
--- 135,141 ----
  	* @param OutputInterface $output
  	* @return int
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$this->input = $input;
  		$this->output = $output;
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/thumbnail/delete.php release-4.0.0-a2-dev/phpbb/console/command/thumbnail/delete.php
*** release-4.0.0-a1/phpbb/console/command/thumbnail/delete.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/thumbnail/delete.php	Wed Apr  1 17:47:36 2026
***************
*** 85,91 ****
  	*
  	* @return int 0 if all is ok, 1 if a thumbnail couldn't be deleted.
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 85,91 ----
  	*
  	* @return int 0 if all is ok, 1 if a thumbnail couldn't be deleted.
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/thumbnail/generate.php release-4.0.0-a2-dev/phpbb/console/command/thumbnail/generate.php
*** release-4.0.0-a1/phpbb/console/command/thumbnail/generate.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/thumbnail/generate.php	Wed Apr  1 17:47:36 2026
***************
*** 121,127 ****
  	*
  	* @return int 0.
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 121,127 ----
  	*
  	* @return int 0.
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/thumbnail/recreate.php release-4.0.0-a2-dev/phpbb/console/command/thumbnail/recreate.php
*** release-4.0.0-a1/phpbb/console/command/thumbnail/recreate.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/thumbnail/recreate.php	Wed Apr  1 17:47:36 2026
***************
*** 62,68 ****
  	*
  	* @return int 0 if all is ok, 1 if a thumbnail couldn't be deleted.
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$command = $this->getApplication()->find('thumbnail:delete');
  
--- 62,68 ----
  	*
  	* @return int 0 if all is ok, 1 if a thumbnail couldn't be deleted.
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$command = $this->getApplication()->find('thumbnail:delete');
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/update/check.php release-4.0.0-a2-dev/phpbb/console/command/update/check.php
*** release-4.0.0-a1/phpbb/console/command/update/check.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/update/check.php	Wed Apr  1 17:47:36 2026
***************
*** 82,88 ****
  	* @return int 0 if the board is up to date, 1 if it is not and 2 if an error occurred.
  	* @throws \RuntimeException
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 82,88 ----
  	* @return int 0 if the board is up to date, 1 if it is not and 2 if an error occurred.
  	* @throws \RuntimeException
  	*/
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/user/activate.php release-4.0.0-a2-dev/phpbb/console/command/user/activate.php
*** release-4.0.0-a1/phpbb/console/command/user/activate.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/user/activate.php	Wed Apr  1 17:47:36 2026
***************
*** 131,137 ****
  	 *
  	 * @return int 0 if all is well, 1 if any errors occurred
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 131,137 ----
  	 *
  	 * @return int 0 if all is well, 1 if any errors occurred
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/user/add.php release-4.0.0-a2-dev/phpbb/console/command/user/add.php
*** release-4.0.0-a1/phpbb/console/command/user/add.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/user/add.php	Wed Apr  1 17:47:36 2026
***************
*** 138,144 ****
  	 *
  	 * @return int 0 if all is well, 1 if any errors occurred
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 138,144 ----
  	 *
  	 * @return int 0 if all is well, 1 if any errors occurred
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/user/delete.php release-4.0.0-a2-dev/phpbb/console/command/user/delete.php
*** release-4.0.0-a1/phpbb/console/command/user/delete.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/user/delete.php	Wed Apr  1 17:47:36 2026
***************
*** 109,115 ****
  	 *
  	 * @return int 0 if all is well, 1 if any errors occurred
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$name = $input->getArgument('username');
  		$mode = ($input->getOption('delete-posts')) ? 'remove' : 'retain';
--- 109,115 ----
  	 *
  	 * @return int 0 if all is well, 1 if any errors occurred
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$name = $input->getArgument('username');
  		$mode = ($input->getOption('delete-posts')) ? 'remove' : 'retain';
diff -x images -crNEB release-4.0.0-a1/phpbb/console/command/user/reclean.php release-4.0.0-a2-dev/phpbb/console/command/user/reclean.php
*** release-4.0.0-a1/phpbb/console/command/user/reclean.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/console/command/user/reclean.php	Wed Apr  1 17:47:36 2026
***************
*** 76,82 ****
  	 *
  	 * @return int 0 if all is well, 1 if any errors occurred
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$io = new SymfonyStyle($input, $output);
  
--- 76,82 ----
  	 *
  	 * @return int 0 if all is well, 1 if any errors occurred
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$io = new SymfonyStyle($input, $output);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/db/driver/oracle.php release-4.0.0-a2-dev/phpbb/db/driver/oracle.php
*** release-4.0.0-a1/phpbb/db/driver/oracle.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/db/driver/oracle.php	Wed Apr  1 17:47:36 2026
***************
*** 105,111 ****
  */
  		$this->sql_server_version = @oci_server_version($this->db_connect_id);
  
! 		return $this->sql_server_version;
  	}
  
  	/**
--- 105,111 ----
  */
  		$this->sql_server_version = @oci_server_version($this->db_connect_id);
  
! 		return $this->sql_server_version ?: '';
  	}
  
  	/**
diff -x images -crNEB release-4.0.0-a1/phpbb/db/migration/container_aware_migration.php release-4.0.0-a2-dev/phpbb/db/migration/container_aware_migration.php
*** release-4.0.0-a1/phpbb/db/migration/container_aware_migration.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/db/migration/container_aware_migration.php	Wed Apr  1 17:47:36 2026
***************
*** 13,25 ****
  
  namespace phpbb\db\migration;
  
- use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  use Symfony\Component\DependencyInjection\ContainerInterface;
  
  /**
  * Abstract base class for container aware database migrations.
  */
! abstract class container_aware_migration extends migration implements ContainerAwareInterface
  {
  	/**
  	 * @var ContainerInterface
--- 13,24 ----
  
  namespace phpbb\db\migration;
  
  use Symfony\Component\DependencyInjection\ContainerInterface;
  
  /**
  * Abstract base class for container aware database migrations.
  */
! abstract class container_aware_migration extends migration
  {
  	/**
  	 * @var ContainerInterface
diff -x images -crNEB release-4.0.0-a1/phpbb/db/migration/data/v33x/profilefields_update.php release-4.0.0-a2-dev/phpbb/db/migration/data/v33x/profilefields_update.php
*** release-4.0.0-a1/phpbb/db/migration/data/v33x/profilefields_update.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/db/migration/data/v33x/profilefields_update.php	Wed Apr  1 17:47:36 2026
***************
*** 202,207 ****
  
  	protected function get_youtube_url_part(string $profile_field_string): string
  	{
! 		return preg_replace('#^https://(?:www\.)?youtube\.com/(.+)$#iu', '$1', $profile_field_string);
  	}
  }
--- 202,207 ----
  
  	protected function get_youtube_url_part(string $profile_field_string): string
  	{
! 		return preg_replace('#^https://(?:www\.)?youtube\.com/(.+)$#iu', '$1', $profile_field_string) ?: '';
  	}
  }
diff -x images -crNEB release-4.0.0-a1/phpbb/db/migration/data/v33x/v3316rc1.php release-4.0.0-a2-dev/phpbb/db/migration/data/v33x/v3316rc1.php
*** release-4.0.0-a1/phpbb/db/migration/data/v33x/v3316rc1.php	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/phpbb/db/migration/data/v33x/v3316rc1.php	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,36 ----
+ <?php
+ /**
+  *
+  * This file is part of the phpBB Forum Software package.
+  *
+  * @copyright (c) phpBB Limited <https://www.phpbb.com>
+  * @license GNU General Public License, version 2 (GPL-2.0)
+  *
+  * For full copyright and license information, please see
+  * the docs/CREDITS.txt file.
+  *
+  */
+ 
+ namespace phpbb\db\migration\data\v33x;
+ 
+ class v3316rc1 extends \phpbb\db\migration\migration
+ {
+ 	public function effectively_installed()
+ 	{
+ 		return version_compare($this->config['version'], '3.3.16-RC1', '>=');
+ 	}
+ 
+ 	public static function depends_on()
+ 	{
+ 		return [
+ 			'\phpbb\db\migration\data\v33x\v3315',
+ 		];
+ 	}
+ 
+ 	public function update_data()
+ 	{
+ 		return [
+ 			['config.update', ['version', '3.3.16-RC1']],
+ 		];
+ 	}
+ }
diff -x images -crNEB release-4.0.0-a1/phpbb/db/migration/data/v400/disable_extensions.php release-4.0.0-a2-dev/phpbb/db/migration/data/v400/disable_extensions.php
*** release-4.0.0-a1/phpbb/db/migration/data/v400/disable_extensions.php	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/phpbb/db/migration/data/v400/disable_extensions.php	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,57 ----
+ <?php
+ /**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+ 
+ namespace phpbb\db\migration\data\v400;
+ 
+ use phpbb\db\migration\container_aware_migration;
+ 
+ class disable_extensions extends container_aware_migration
+ {
+ 	/**
+ 	 * @var array List of extensions included with phpBB
+ 	 */
+ 	public static array $default_extensions = [
+ 		'phpbb/viglink',
+ 	];
+ 
+ 	public static function depends_on(): array
+ 	{
+ 		return ['\phpbb\db\migration\data\v400\v400a1'];
+ 	}
+ 
+ 	public function update_data(): array
+ 	{
+ 		return [
+ 			['custom', [[$this, 'disable_enabled_extensions']]],
+ 		];
+ 	}
+ 
+ 	/**
+ 	 * Disable all enabled extensions except those included with phpBB.
+ 	 * This is a safety measure to prevent possible PHP fatal errors
+ 	 * caused by extensions that are not compatible with the changes
+ 	 * introduced with phpBB 4.0.0-a2.
+ 	 */
+ 	public function disable_enabled_extensions(): void
+ 	{
+ 		$ext_manager = $this->container->get('ext.manager');
+ 
+ 		$enabled_extensions = array_keys($ext_manager->all_enabled());
+ 		$enabled_extensions = array_diff($enabled_extensions, self::$default_extensions);
+ 
+ 		foreach ($enabled_extensions as $extension)
+ 		{
+ 			$ext_manager->disable($extension);
+ 		}
+ 	}
+ }
diff -x images -crNEB release-4.0.0-a1/phpbb/db/migration/data/v400/extensions_composer_2.php release-4.0.0-a2-dev/phpbb/db/migration/data/v400/extensions_composer_2.php
*** release-4.0.0-a1/phpbb/db/migration/data/v400/extensions_composer_2.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/db/migration/data/v400/extensions_composer_2.php	Wed Apr  1 17:47:36 2026
***************
*** 24,30 ****
  
  	public function update_data()
  	{
! 		$repositories = json_decode($this->config['exts_composer_repositories'], true);
  		$repositories[] = 'https://satis.phpbb.com';
  		$repositories = array_unique($repositories);
  
--- 24,30 ----
  
  	public function update_data()
  	{
! 		$repositories = json_decode($this->config['exts_composer_repositories'], true) ?: [];
  		$repositories[] = 'https://satis.phpbb.com';
  		$repositories = array_unique($repositories);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/db/migration/data/v400/extensions_composer_4.php release-4.0.0-a2-dev/phpbb/db/migration/data/v400/extensions_composer_4.php
*** release-4.0.0-a1/phpbb/db/migration/data/v400/extensions_composer_4.php	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/phpbb/db/migration/data/v400/extensions_composer_4.php	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,47 ----
+ <?php
+ /**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+ 
+ namespace phpbb\db\migration\data\v400;
+ 
+ use phpbb\db\migration\migration;
+ 
+ class extensions_composer_4 extends migration
+ {
+ 	public function effectively_installed()
+ 	{
+ 		return str_contains($this->config['exts_composer_repositories'], 'https://www.phpbb.com/customise/db/composer/40/');
+ 	}
+ 
+ 	public function update_data()
+ 	{
+ 		$repositories = json_decode($this->config['exts_composer_repositories'], true) ?: [];
+ 		$repositories = array_map(function($repo) {
+ 			return $repo === 'https://www.phpbb.com/customise/db/composer/'
+ 				? 'https://www.phpbb.com/customise/db/composer/40/'
+ 				: $repo;
+ 		}, $repositories);
+ 
+ 		return [
+ 			['config.update', ['exts_composer_repositories', json_encode($repositories, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)]],
+ 		];
+ 	}
+ 
+ 	public static function depends_on()
+ 	{
+ 		return [
+ 			'\phpbb\db\migration\data\v400\extensions_composer',
+ 			'\phpbb\db\migration\data\v400\extensions_composer_2',
+ 			'\phpbb\db\migration\data\v400\extensions_composer_3'
+ 		];
+ 	}
+ }
diff -x images -crNEB release-4.0.0-a1/phpbb/db/migration/data/v400/remove_jabber.php release-4.0.0-a2-dev/phpbb/db/migration/data/v400/remove_jabber.php
*** release-4.0.0-a1/phpbb/db/migration/data/v400/remove_jabber.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/db/migration/data/v400/remove_jabber.php	Wed Apr  1 17:47:36 2026
***************
*** 114,139 ****
  		];
  	}
  
! 	public function move_jabber_to_email_notifications(int|null $start)
  	{
! 		$limit = 1000;
  
! 		$sql = 'SELECT id FROM ' . $this->tables['user_notifications'] . "
! 			WHERE method = 'notification.method.jabber'
! 			ORDER BY id ASC";
! 		$result = $this->db->sql_query_limit($sql, $limit, $start ?: 0);
! 		$rowset = $this->db->sql_fetchrowset($result);
! 		$this->db->sql_freeresult($result);
! 		$ids_array = array_column($rowset, 'id');
! 
! 		if (count($ids_array))
! 		{
! 			$sql = 'UPDATE ' . $this->tables['user_notifications'] . '
! 				SET ' . $this->db->sql_build_array('UPDATE', ['method' => 'notification.method.email']) . '
! 				WHERE ' . $this->db->sql_in_set('id', $ids_array);
! 			$this->db->sql_query($sql);
! 		}
! 
! 		return count($ids_array) < $limit ? true : $start + $limit;
  	}
  }
--- 114,125 ----
  		];
  	}
  
! 	public function move_jabber_to_email_notifications()
  	{
! 		$sql = 'DELETE FROM ' . $this->tables['user_notifications'] . "
! 			WHERE method = 'notification.method.jabber'";
! 		$this->db->sql_query($sql);
  
! 		return true;
  	}
  }
diff -x images -crNEB release-4.0.0-a1/phpbb/db/migration/data/v400/storage_track.php release-4.0.0-a2-dev/phpbb/db/migration/data/v400/storage_track.php
*** release-4.0.0-a1/phpbb/db/migration/data/v400/storage_track.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/db/migration/data/v400/storage_track.php	Wed Apr  1 17:47:36 2026
***************
*** 98,104 ****
  			$filename = $this->config['avatar_salt'] . '_' . ($avatar_group ? 'g' : '') . $filename . '.' . $ext;
  			$files[] = [
  				'file_path' => $filename,
! 				'filesize' => filesize($this->phpbb_root_path . $this->config['storage\\avatar\\config\\path'] . '/' . $filename),
  			];
  
  			if (count($files) >= self::BATCH_SIZE)
--- 98,104 ----
  			$filename = $this->config['avatar_salt'] . '_' . ($avatar_group ? 'g' : '') . $filename . '.' . $ext;
  			$files[] = [
  				'file_path' => $filename,
! 				'filesize' => @filesize($this->phpbb_root_path . $this->config['storage\\avatar\\config\\path'] . '/' . $filename),
  			];
  
  			if (count($files) >= self::BATCH_SIZE)
diff -x images -crNEB release-4.0.0-a1/phpbb/db/migration/tool/module.php release-4.0.0-a2-dev/phpbb/db/migration/tool/module.php
*** release-4.0.0-a1/phpbb/db/migration/tool/module.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/db/migration/tool/module.php	Wed Apr  1 17:47:36 2026
***************
*** 377,383 ****
  		{
  			if (!$this->exists($class, $parent, $module, true))
  			{
! 				throw new \phpbb\db\migration\exception('MODULE_NOT_EXIST', $module);
  			}
  
  			$parent_sql = '';
--- 377,383 ----
  		{
  			if (!$this->exists($class, $parent, $module, true))
  			{
! 				return;
  			}
  
  			$parent_sql = '';
diff -x images -crNEB release-4.0.0-a1/phpbb/db/migrator.php release-4.0.0-a2-dev/phpbb/db/migrator.php
*** release-4.0.0-a1/phpbb/db/migrator.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/db/migrator.php	Wed Apr  1 17:47:36 2026
***************
*** 15,26 ****
  
  use phpbb\config\config;
  use phpbb\db\driver\driver_interface;
  use phpbb\db\migration\exception;
  use phpbb\db\migration\helper;
  use phpbb\db\output_handler\migrator_output_handler_interface;
  use phpbb\db\output_handler\null_migrator_output_handler;
  use phpbb\db\tools\tools_interface;
- use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  use Symfony\Component\DependencyInjection\ContainerInterface;
  
  /**
--- 15,26 ----
  
  use phpbb\config\config;
  use phpbb\db\driver\driver_interface;
+ use phpbb\db\migration\container_aware_migration;
  use phpbb\db\migration\exception;
  use phpbb\db\migration\helper;
  use phpbb\db\output_handler\migrator_output_handler_interface;
  use phpbb\db\output_handler\null_migrator_output_handler;
  use phpbb\db\tools\tools_interface;
  use Symfony\Component\DependencyInjection\ContainerInterface;
  
  /**
***************
*** 980,986 ****
  	{
  		$migration = new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix, $this->tables);
  
! 		if ($migration instanceof ContainerAwareInterface)
  		{
  			$migration->setContainer($this->container);
  		}
--- 980,986 ----
  	{
  		$migration = new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix, $this->tables);
  
! 		if ($migration instanceof container_aware_migration)
  		{
  			$migration->setContainer($this->container);
  		}
diff -x images -crNEB release-4.0.0-a1/phpbb/db/tools/doctrine.php release-4.0.0-a2-dev/phpbb/db/tools/doctrine.php
*** release-4.0.0-a1/phpbb/db/tools/doctrine.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/db/tools/doctrine.php	Wed Apr  1 17:47:36 2026
***************
*** 406,411 ****
--- 406,447 ----
  	}
  
  	/**
+ 	 * Returns an array of the table index names and relevant data in format
+ 	 * [
+ 	 *		[$index_name] = [
+ 	 *			'columns'	=> (array) $index_columns,
+ 	 *			'flags'		=> (array) $index_flags,
+ 	 *			'options'	=> (array) $index_options,
+ 	 *			'is_primary'=> (bool) $isPrimary,
+ 	 *			'is_unique'	=> (bool) $isUnique,
+ 	 *			'is_simple'	=> (bool) $isSimple,
+ 	 *		]
+ 	 *
+ 	 * @param string $table_name
+ 	 *
+ 	 * @return array
+ 	 */
+ 	public function sql_get_table_index_data(string $table_name): array
+ 	{
+ 		$schema = $this->get_schema();
+ 		$table = $schema->getTable($table_name);
+ 		$indexes = [];
+ 		foreach ($table->getIndexes() as $index)
+ 		{
+ 			$indexes[$index->getName()] = [
+ 				'columns'	=> array_map('strtolower', $index->getUnquotedColumns()),
+ 				'flags'		=> $index->getFlags(),
+ 				'options'	=> $index->getOptions(),
+ 				'is_primary'=> $index->isPrimary(),
+ 				'is_unique'	=> $index->isUnique(),
+ 				'is_simple'	=> $index->isSimpleIndex(),
+ 			];
+ 		}
+ 
+ 		return $indexes;
+ 	}
+ 
+ 	/**
  	 * Returns an array of indices for either unique and primary keys, or simple indices.
  	 *
  	 * @param string $table_name    The name of the table.
***************
*** 655,678 ****
  				$columns = (is_array($key_data[1])) ? $key_data[1] : [$key_data[1]];
  				$key_name = !str_starts_with($key_name, $short_table_name) ? self::add_prefix($key_name, $short_table_name) : $key_name;
  
! 				// Supports key columns defined with there length
! 				$columns = array_map(function (string $column)
! 				{
! 					if (strpos($column, ':') !== false)
! 					{
! 						$parts = explode(':', $column, 2);
! 						return $parts[0];
! 					}
! 					return $column;
! 				}, $columns);
  
  				if ($key_data[0] === 'UNIQUE')
  				{
! 					$table->addUniqueIndex($columns, $key_name);
  				}
  				else
  				{
! 					$table->addIndex($columns, $key_name);
  				}
  			}
  		}
--- 691,706 ----
  				$columns = (is_array($key_data[1])) ? $key_data[1] : [$key_data[1]];
  				$key_name = !str_starts_with($key_name, $short_table_name) ? self::add_prefix($key_name, $short_table_name) : $key_name;
  
! 				$options = [];
! 				$this->schema_get_index_key_data($columns, $options);
  
  				if ($key_data[0] === 'UNIQUE')
  				{
! 					$table->addUniqueIndex($columns, $key_name, $options);
  				}
  				else
  				{
! 					$table->addIndex($columns, $key_name, [], $options);
  				}
  			}
  		}
***************
*** 869,875 ****
  			return;
  		}
  
! 		$table->addIndex($columns, $index_name);
  	}
  
  	/**
--- 897,906 ----
  			return;
  		}
  
! 		$options = [];
! 		$this->schema_get_index_key_data($columns, $options);
! 
! 		$table->addIndex($columns, $index_name, [], $options);
  	}
  
  	/**
***************
*** 925,931 ****
  			return;
  		}
  
! 		$table->addUniqueIndex($columns, $index_name);
  	}
  
  	/**
--- 956,965 ----
  			return;
  		}
  
! 		$options = [];
! 		$this->schema_get_index_key_data($columns, $options);
! 
! 		$table->addUniqueIndex($columns, $index_name, $options);
  	}
  
  	/**
***************
*** 975,980 ****
--- 1009,1038 ----
  	}
  
  	/**
+ 	 * Checks if index data contains key length
+ 	 * and put it into $options['lengths'] array.
+ 	 * Handles key length in formats of 'keyname:123' or 'keyname(123)'
+ 	 *
+ 	 * @param array  $columns
+ 	 * @param array  $options
+ 	 */
+ 	protected function schema_get_index_key_data(array &$columns, array &$options): void
+ 	{
+ 		if (!empty($columns))
+ 		{
+ 			$columns = array_map(function (string $column) use (&$options)
+ 			{
+ 				if (preg_match('/^([a-zA-Z0-9_]+)(?:(?:\:|\()([0-9]{1,3})\)?)?$/', $column, $parts))
+ 				{
+ 					$options['lengths'][] = $parts[2] ?? null;
+ 					return $parts[1];
+ 				}
+ 				return $column;
+ 			}, $columns);
+ 		}
+ 	}
+ 
+ 	/**
  	 * Recreate an index of a table
  	 *
  	 * @param Table $table
diff -x images -crNEB release-4.0.0-a1/phpbb/di/container_builder.php release-4.0.0-a2-dev/phpbb/di/container_builder.php
*** release-4.0.0-a1/phpbb/di/container_builder.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/di/container_builder.php	Wed Apr  1 17:47:36 2026
***************
*** 13,23 ****
  
  namespace phpbb\di;
  
- use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
  use Symfony\Component\Config\ConfigCache;
  use Symfony\Component\Config\FileLocator;
  use Symfony\Component\DependencyInjection\ContainerBuilder;
  use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
  use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
  use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
  use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
--- 13,23 ----
  
  namespace phpbb\di;
  
  use Symfony\Component\Config\ConfigCache;
  use Symfony\Component\Config\FileLocator;
  use Symfony\Component\DependencyInjection\ContainerBuilder;
  use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
+ use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper;
  use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
  use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
  use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
***************
*** 517,523 ****
  		try
  		{
  			$dumper = new PhpDumper($this->container);
! 			$proxy_dumper = new ProxyDumper();
  			$dumper->setProxyDumper($proxy_dumper);
  
  			$cached_container_dump = $dumper->dump(array(
--- 517,523 ----
  		try
  		{
  			$dumper = new PhpDumper($this->container);
! 			$proxy_dumper = new LazyServiceDumper();
  			$dumper->setProxyDumper($proxy_dumper);
  
  			$cached_container_dump = $dumper->dump(array(
***************
*** 542,548 ****
  	protected function create_container(array $extensions)
  	{
  		$container = new ContainerBuilder(new ParameterBag($this->get_core_parameters()));
- 		$container->setProxyInstantiator(new proxy_instantiator($this->get_cache_dir()));
  
  		$extensions_alias = array();
  
--- 542,547 ----
diff -x images -crNEB release-4.0.0-a1/phpbb/di/extension/container_configuration.php release-4.0.0-a2-dev/phpbb/di/extension/container_configuration.php
*** release-4.0.0-a1/phpbb/di/extension/container_configuration.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/di/extension/container_configuration.php	Wed Apr  1 17:47:36 2026
***************
*** 24,30 ****
  	 *
  	 * @return TreeBuilder The tree builder
  	 */
! 	public function getConfigTreeBuilder()
  	{
  		$treeBuilder = new TreeBuilder('core');
  		$rootNode = $treeBuilder->getRootNode();
--- 24,30 ----
  	 *
  	 * @return TreeBuilder The tree builder
  	 */
! 	public function getConfigTreeBuilder(): TreeBuilder
  	{
  		$treeBuilder = new TreeBuilder('core');
  		$rootNode = $treeBuilder->getRootNode();
diff -x images -crNEB release-4.0.0-a1/phpbb/di/proxy_instantiator.php release-4.0.0-a2-dev/phpbb/di/proxy_instantiator.php
*** release-4.0.0-a1/phpbb/di/proxy_instantiator.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/di/proxy_instantiator.php	Thu Jan  1 00:00:00 1970
***************
*** 1,72 ****
- <?php
- /**
-  *
-  * This file is part of the phpBB Forum Software package.
-  *
-  * @copyright (c) phpBB Limited <https://www.phpbb.com>
-  * @license GNU General Public License, version 2 (GPL-2.0)
-  *
-  * For full copyright and license information, please see
-  * the docs/CREDITS.txt file.
-  *
-  */
- 
- namespace phpbb\di;
- 
- use ProxyManager\Configuration;
- use ProxyManager\Factory\LazyLoadingValueHolderFactory;
- use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
- use Symfony\Component\DependencyInjection\ContainerInterface;
- use Symfony\Component\DependencyInjection\Definition;
- use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
- 
- /**
-  * Runtime lazy loading proxy generator extended for allowing use while using
-  * open_basedir restrictions
-  *
-  * Original author: Marco Pivetta <ocramius@gmail.com>
-  */
- class proxy_instantiator implements InstantiatorInterface
- {
- 	/**
- 	 * @var LazyLoadingValueHolderFactory
- 	 */
- 	private $factory;
- 
- 	/**
- 	 * proxy_instantiator constructor
- 	 * @param string $cache_dir Cache dir for fall back when using open_basedir
- 	 */
- 	public function __construct($cache_dir)
- 	{
- 		$config = new Configuration();
- 
- 		// Prevent trying to write to system temp dir in case of open_basedir
- 		// restrictions being in effect
- 		$tmp_dir = (function_exists('sys_get_temp_dir')) ? sys_get_temp_dir() : '';
- 		if (empty($tmp_dir) || !@file_exists($tmp_dir) || !@is_writable($tmp_dir))
- 		{
- 			$config->setProxiesTargetDir($cache_dir);
- 		}
- 		$config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
- 
- 		$this->factory = new LazyLoadingValueHolderFactory($config);
- 	}
- 
- 	/**
- 	 * {@inheritdoc}
- 	 */
- 	public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator)
- 	{
- 		return $this->factory->createProxy(
- 			$definition->getClass(),
- 			function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($realInstantiator) {
- 				$wrappedInstance = call_user_func($realInstantiator);
- 
- 				$proxy->setProxyInitializer(null);
- 
- 				return true;
- 			}
- 		);
- 	}
- }
--- 0 ----
diff -x images -crNEB release-4.0.0-a1/phpbb/extension/manager.php release-4.0.0-a2-dev/phpbb/extension/manager.php
*** release-4.0.0-a1/phpbb/extension/manager.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/extension/manager.php	Wed Apr  1 17:47:36 2026
***************
*** 258,266 ****
  	*/
  	public function enable($name)
  	{
- 		// @codingStandardsIgnoreStart
  		while ($this->enable_step($name));
- 		// @codingStandardsIgnoreEnd
  	}
  
  	/**
--- 258,264 ----
***************
*** 306,314 ****
  	*/
  	public function disable($name)
  	{
- 		// @codingStandardsIgnoreStart
  		while ($this->disable_step($name));
- 		// @codingStandardsIgnoreEnd
  	}
  
  	/**
--- 304,310 ----
***************
*** 361,369 ****
  	*/
  	public function purge($name)
  	{
- 		// @codingStandardsIgnoreStart
  		while ($this->purge_step($name));
- 		// @codingStandardsIgnoreEnd
  	}
  
  	/**
--- 357,363 ----
diff -x images -crNEB release-4.0.0-a1/phpbb/feed/event/http_auth_subscriber.php release-4.0.0-a2-dev/phpbb/feed/event/http_auth_subscriber.php
*** release-4.0.0-a1/phpbb/feed/event/http_auth_subscriber.php	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/phpbb/feed/event/http_auth_subscriber.php	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,211 ----
+ <?php
+ /**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+ 
+ namespace phpbb\feed\event;
+ 
+ use phpbb\auth\auth;
+ use phpbb\config\config;
+ use phpbb\language\language;
+ use phpbb\request\request_interface;
+ use phpbb\user;
+ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+ use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\HttpKernel\Event\RequestEvent;
+ use Symfony\Component\HttpKernel\KernelEvents;
+ 
+ /**
+  * Event subscriber for HTTP authentication on feed routes
+  */
+ class http_auth_subscriber implements EventSubscriberInterface
+ {
+ 	/** @var auth */
+ 	protected $auth;
+ 
+ 	/** @var config */
+ 	protected $config;
+ 
+ 	/** @var language */
+ 	protected $language;
+ 
+ 	/** @var request_interface */
+ 	protected $request;
+ 
+ 	/** @var user */
+ 	protected $user;
+ 
+ 	/**
+ 	 * Constructor
+ 	 *
+ 	 * @param auth				$auth		Auth object
+ 	 * @param config			$config		Config object
+ 	 * @param language	$language	Language object
+ 	 * @param request_interface	$request	Request object
+ 	 * @param user				$user		User object
+ 	 */
+ 	public function __construct(auth $auth, config $config, language $language, request_interface $request, user $user)
+ 	{
+ 		$this->auth = $auth;
+ 		$this->config = $config;
+ 		$this->language = $language;
+ 		$this->request = $request;
+ 		$this->user = $user;
+ 	}
+ 
+ 	/**
+ 	 * Handle HTTP authentication for feed routes
+ 	 *
+ 	 * @param RequestEvent $event
+ 	 * @return void
+ 	 */
+ 	public function on_kernel_request(RequestEvent $event)
+ 	{
+ 		// Check if HTTP authentication is enabled
+ 		if (!$this->config['feed_http_auth'])
+ 		{
+ 			return;
+ 		}
+ 
+ 		$request = $event->getRequest();
+ 		$route = $request->attributes->get('_route');
+ 
+ 		// Only apply to feed routes
+ 		if (strpos($route, 'phpbb_feed_') !== 0)
+ 		{
+ 			return;
+ 		}
+ 
+ 		// Only allow HTTP authentication in secure context (HTTPS)
+ 		if (!$request->isSecure())
+ 		{
+ 			return;
+ 		}
+ 
+ 		// User is already logged in, no need to authenticate
+ 		if (!empty($this->user->data['is_registered']))
+ 		{
+ 			return;
+ 		}
+ 
+ 		// Get HTTP authentication credentials
+ 		[$username, $password] = $this->get_credentials();
+ 
+ 		// If no credentials provided, send authentication challenge
+ 		if ($username === null || $password === null)
+ 		{
+ 			$this->send_auth_challenge($event);
+ 			return;
+ 		}
+ 
+ 		// Attempt to login with the provided credentials
+ 		$auth_result = $this->auth->login($username, $password, false, true, false);
+ 
+ 		if ($auth_result['status'] == LOGIN_SUCCESS)
+ 		{
+ 			// Reload ACL for the newly logged-in user
+ 			$this->auth->acl($this->user->data);
+ 			return;
+ 		}
+ 		else if ($auth_result['status'] == LOGIN_ERROR_ATTEMPTS)
+ 		{
+ 			// Too many login attempts
+ 			$response = new Response($this->language->lang('LOGIN_ERROR_ATTEMPTS'), Response::HTTP_UNAUTHORIZED);
+ 			$event->setResponse($response);
+ 			return;
+ 		}
+ 
+ 		// Authentication failed, send challenge
+ 		$this->send_auth_challenge($event);
+ 	}
+ 
+ 	/**
+ 	 * Retrieve HTTP authentication credentials from server variables
+ 	 *
+ 	 * @return array [username, password] Array containing the username and password, or null if not found
+ 	 */
+ 	protected function get_credentials(): array
+ 	{
+ 		$username_keys = [
+ 			'PHP_AUTH_USER',
+ 			'Authorization',
+ 			'REMOTE_USER',
+ 			'REDIRECT_REMOTE_USER',
+ 			'HTTP_AUTHORIZATION',
+ 			'REDIRECT_HTTP_AUTHORIZATION',
+ 			'REMOTE_AUTHORIZATION',
+ 			'REDIRECT_REMOTE_AUTHORIZATION',
+ 			'AUTH_USER',
+ 		];
+ 
+ 		$password_keys = [
+ 			'PHP_AUTH_PW',
+ 			'REMOTE_PASSWORD',
+ 			'AUTH_PASSWORD',
+ 		];
+ 
+ 		$username = null;
+ 		foreach ($username_keys as $key)
+ 		{
+ 			if ($this->request->is_set($key, request_interface::SERVER))
+ 			{
+ 				$username = htmlspecialchars_decode($this->request->server($key));
+ 				break;
+ 			}
+ 		}
+ 
+ 		$password = null;
+ 		foreach ($password_keys as $key)
+ 		{
+ 			if ($this->request->is_set($key, request_interface::SERVER))
+ 			{
+ 				$password =  htmlspecialchars_decode($this->request->server($key));
+ 				break;
+ 			}
+ 		}
+ 
+ 		// Decode Basic authentication header if needed
+ 		if (!is_null($username) && is_null($password) && strpos($username, 'Basic ') === 0)
+ 		{
+ 			[$username, $password] = explode(':', base64_decode(substr($username, 6)), 2);
+ 		}
+ 
+ 		return [$username, $password];
+ 	}
+ 
+ 	/**
+ 	 * Send HTTP authentication challenge
+ 	 *
+ 	 * @param RequestEvent $event
+ 	 * @return void
+ 	 */
+ 	protected function send_auth_challenge(RequestEvent $event)
+ 	{
+ 		$realm = $this->config['sitename'];
+ 
+ 		// Filter out non-ASCII characters per RFC2616
+ 		$realm = preg_replace('/[\x80-\xFF]/', '?', $realm);
+ 
+ 		$response = new Response($this->language->lang('NOT_AUTHORISED'), Response::HTTP_UNAUTHORIZED);
+ 		$response->headers->set('WWW-Authenticate', 'Basic realm="' . $realm . ' - Feed"');
+ 		$event->setResponse($response);
+ 	}
+ 
+ 	/**
+ 	 * {@inheritdoc}
+ 	 */
+ 	public static function getSubscribedEvents(): array
+ 	{
+ 		return [
+ 			KernelEvents::REQUEST => ['on_kernel_request', 5],
+ 		];
+ 	}
+ }
diff -x images -crNEB release-4.0.0-a1/phpbb/feed/helper.php release-4.0.0-a2-dev/phpbb/feed/helper.php
*** release-4.0.0-a1/phpbb/feed/helper.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/feed/helper.php	Wed Apr  1 17:47:36 2026
***************
*** 167,173 ****
  			$content .= implode('<br />', $post_attachments);
  
  			// Convert attachments' relative path to absolute path
! 			$pattern = '#(/app.php)?/download/attachment/#';
  			$replacement = $this->get_board_url() . '\1/download/attachment/';
  			$content = preg_replace($pattern, $replacement, $content);
  		}
--- 167,173 ----
  			$content .= implode('<br />', $post_attachments);
  
  			// Convert attachments' relative path to absolute path
! 			$pattern = '#(/index.php)?/download/attachment/#';
  			$replacement = $this->get_board_url() . '\1/download/attachment/';
  			$content = preg_replace($pattern, $replacement, $content);
  		}
***************
*** 191,196 ****
  		// Other control characters
  		$content = preg_replace('#(?:[\x00-\x1F\x7F]+|(?:\xC2[\x80-\x9F])+)#', '', $content);
  
! 		return $content;
  	}
  }
--- 191,196 ----
  		// Other control characters
  		$content = preg_replace('#(?:[\x00-\x1F\x7F]+|(?:\xC2[\x80-\x9F])+)#', '', $content);
  
! 		return $content ?: '';
  	}
  }
diff -x images -crNEB release-4.0.0-a1/phpbb/files/filespec.php release-4.0.0-a2-dev/phpbb/files/filespec.php
*** release-4.0.0-a1/phpbb/files/filespec.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/files/filespec.php	Wed Apr  1 17:47:36 2026
***************
*** 355,361 ****
  	 */
  	public function get_filesize($filename)
  	{
! 		return @filesize($filename);
  	}
  
  
--- 355,361 ----
  	 */
  	public function get_filesize($filename)
  	{
! 		return @filesize($filename) ?: 0;
  	}
  
  
diff -x images -crNEB release-4.0.0-a1/phpbb/files/filespec_storage.php release-4.0.0-a2-dev/phpbb/files/filespec_storage.php
*** release-4.0.0-a1/phpbb/files/filespec_storage.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/files/filespec_storage.php	Wed Apr  1 17:47:36 2026
***************
*** 339,345 ****
  	 */
  	public function get_filesize($filename)
  	{
! 		return @filesize($filename);
  	}
  
  
--- 339,345 ----
  	 */
  	public function get_filesize($filename)
  	{
! 		return @filesize($filename) ?: 0;
  	}
  
  
diff -x images -crNEB release-4.0.0-a1/phpbb/filesystem/helper.php release-4.0.0-a2-dev/phpbb/filesystem/helper.php
*** release-4.0.0-a1/phpbb/filesystem/helper.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/filesystem/helper.php	Wed Apr  1 17:47:36 2026
***************
*** 303,309 ****
  					{
  						if (defined('PHP_WINDOWS_VERSION_MAJOR'))
  						{
! 							$prefix = $link[0] . ':';
  							$link = substr($link, 2);
  						}
  						else
--- 303,309 ----
  					{
  						if (defined('PHP_WINDOWS_VERSION_MAJOR'))
  						{
! 							$prefix = substr($link, 0, 1) . ':';
  							$link = substr($link, 2);
  						}
  						else
diff -x images -crNEB release-4.0.0-a1/phpbb/forum/birthday_helper.php release-4.0.0-a2-dev/phpbb/forum/birthday_helper.php
*** release-4.0.0-a1/phpbb/forum/birthday_helper.php	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/phpbb/forum/birthday_helper.php	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,156 ----
+ <?php
+ /**
+  *
+  * This file is part of the phpBB Forum Software package.
+  *
+  * @copyright (c) phpBB Limited <https://www.phpbb.com>
+  * @license GNU General Public License, version 2 (GPL-2.0)
+  *
+  * For full copyright and license information, please see
+  * the docs/CREDITS.txt file.
+  *
+  */
+ 
+ namespace phpbb\forum;
+ 
+ use phpbb\auth\auth;
+ use phpbb\config\config;
+ use phpbb\db\driver\driver_interface;
+ use phpbb\event\dispatcher;
+ use phpbb\language\language;
+ use phpbb\template\template;
+ use phpbb\user;
+ 
+ class birthday_helper
+ {
+ 	/** @var auth */
+ 	protected $auth;
+ 
+ 	/** @var config */
+ 	protected $config;
+ 
+ 	/** @var driver_interface */
+ 	protected $db;
+ 
+ 	/** @var dispatcher */
+ 	protected $dispatcher;
+ 
+ 	/** @var language */
+ 	protected $language;
+ 
+ 	/** @var template */
+ 	protected $template;
+ 
+ 	/** @var user */
+ 	protected $user;
+ 
+ 	/**
+ 	 * Constructor
+ 	 *
+ 	 * @param auth $auth
+ 	 * @param config $config
+ 	 * @param driver_interface $db
+ 	 * @param dispatcher $dispatcher
+ 	 * @param language $language
+ 	 * @param template $template
+ 	 * @param user $user
+ 	 */
+ 	public function __construct(auth $auth, config $config, driver_interface $db, dispatcher $dispatcher, language $language, template $template, user $user)
+ 	{
+ 		$this->auth = $auth;
+ 		$this->config = $config;
+ 		$this->db = $db;
+ 		$this->dispatcher = $dispatcher;
+ 		$this->language = $language;
+ 		$this->template = $template;
+ 		$this->user = $user;
+ 	}
+ 
+ 	/**
+ 	 * Display birthdays in index page
+ 	 *
+ 	 * @return void
+ 	 *
+ 	 * @psalm-suppress InvalidArgument
+ 	 */
+ 	public function display_birthdays(): void
+ 	{
+ 		$show_birthdays = ($this->config['load_birthdays'] && $this->config['allow_birthdays'] && $this->auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel'));
+ 
+ 		$birthdays = array();
+ 		if ($show_birthdays)
+ 		{
+ 			$time = $this->user->create_datetime();
+ 			$now = phpbb_gmgetdate($time->getTimestamp() + $time->getOffset());
+ 
+ 			// Display birthdays of 29th february on 28th february in non-leap-years
+ 			$leap_year_birthdays = '';
+ 			if ($now['mday'] == 28 && $now['mon'] == 2 && !$time->format('L'))
+ 			{
+ 				$leap_year_birthdays = " OR u.user_birthday LIKE '" . $this->db->sql_escape(sprintf('%2d-%2d-', 29, 2)) . "%'";
+ 			}
+ 
+ 			$sql_ary = array(
+ 				'SELECT' => 'u.user_id, u.username, u.user_colour, u.user_birthday',
+ 				'FROM' => array(
+ 					USERS_TABLE => 'u',
+ 				),
+ 				'LEFT_JOIN' => array(
+ 					array(
+ 						'FROM' => array(BANS_TABLE => 'b'),
+ 						'ON' => 'u.user_id = b.ban_userid',
+ 					),
+ 				),
+ 				'WHERE' => 'b.ban_id IS NULL
+ 			AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ")
+ 			AND (u.user_birthday LIKE '" . $this->db->sql_escape(sprintf('%2d-%2d-', $now['mday'], $now['mon'])) . "%' $leap_year_birthdays)",
+ 			);
+ 
+ 			/**
+ 			 * Event to modify the SQL query to get birthdays data
+ 			 *
+ 			 * @event core.index_modify_birthdays_sql
+ 			 * @var	array	now			The assoc array with the 'now' local timestamp data
+ 			 * @var	array	sql_ary		The SQL array to get the birthdays data
+ 			 * @var	object	time		The user related Datetime object
+ 			 * @since 3.1.7-RC1
+ 			 */
+ 			$vars = array('now', 'sql_ary', 'time');
+ 			extract($this->dispatcher->trigger_event('core.index_modify_birthdays_sql', compact($vars)));
+ 
+ 			$sql = $this->db->sql_build_query('SELECT', $sql_ary);
+ 			$result = $this->db->sql_query($sql);
+ 			$rows = $this->db->sql_fetchrowset($result);
+ 			$this->db->sql_freeresult($result);
+ 
+ 			foreach ($rows as $row)
+ 			{
+ 				$birthday_username	= get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']);
+ 				$birthday_year		= (int) substr($row['user_birthday'], -4);
+ 				$birthday_age		= ($birthday_year) ? max(0, $now['year'] - $birthday_year) : '';
+ 
+ 				$birthdays[] = array(
+ 					'USERNAME'	=> $birthday_username,
+ 					'AGE'		=> $birthday_age,
+ 				);
+ 			}
+ 
+ 			/**
+ 			 * Event to modify the birthdays list
+ 			 *
+ 			 * @event core.index_modify_birthdays_list
+ 			 * @var	array	birthdays		Array with the users birthdays data
+ 			 * @var	array	rows			Array with the birthdays SQL query result
+ 			 * @since 3.1.7-RC1
+ 			 */
+ 			$vars = array('birthdays', 'rows');
+ 			extract($this->dispatcher->trigger_event('core.index_modify_birthdays_list', compact($vars)));
+ 
+ 			$this->template->assign_block_vars_array('birthdays', $birthdays);
+ 		}
+ 
+ 		$this->template->assign_vars([
+ 			'S_DISPLAY_BIRTHDAY_LIST' => $show_birthdays,
+ 		]);
+ 	}
+ }
diff -x images -crNEB release-4.0.0-a1/phpbb/forum/controller/index.php release-4.0.0-a2-dev/phpbb/forum/controller/index.php
*** release-4.0.0-a1/phpbb/forum/controller/index.php	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/phpbb/forum/controller/index.php	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,147 ----
+ <?php
+ /**
+  *
+  * This file is part of the phpBB Forum Software package.
+  *
+  * @copyright (c) phpBB Limited <https://www.phpbb.com>
+  * @license GNU General Public License, version 2 (GPL-2.0)
+  *
+  * For full copyright and license information, please see
+  * the docs/CREDITS.txt file.
+  *
+  */
+ 
+ namespace phpbb\forum\controller;
+ 
+ use phpbb\auth\auth;
+ use phpbb\config\config;
+ use phpbb\controller\helper as controller_helper;
+ use phpbb\event\dispatcher;
+ use phpbb\forum\birthday_helper;
+ use phpbb\group\helper as group_helper;
+ use phpbb\language\language;
+ use phpbb\template\template;
+ use phpbb\user;
+ use Symfony\Component\HttpFoundation\Response;
+ 
+ class index
+ {
+ 	/** @var auth */
+ 	protected $auth;
+ 
+ 	/** @var birthday_helper */
+ 	protected $birthday_helper;
+ 
+ 	/** @var config */
+ 	protected $config;
+ 
+ 	/** @var controller_helper */
+ 	protected $controller_helper;
+ 
+ 	/** @var dispatcher */
+ 	protected $dispatcher;
+ 
+ 	/** @var group_helper */
+ 	protected $group_helper;
+ 
+ 	/** @var language */
+ 	protected $language;
+ 
+ 	/** @var user */
+ 	protected $user;
+ 
+ 	/** @var template */
+ 	protected $template;
+ 
+ 	/** @var string */
+ 	protected $phpbb_root_path;
+ 
+ 	/** @var string */
+ 	protected $phpEx;
+ 
+ 	/**
+ 	 * Constructor
+ 	 *
+ 	 * @param auth $auth
+ 	 * @param birthday_helper $birthday_helper
+ 	 * @param config $config
+ 	 * @param controller_helper $controller_helper
+ 	 * @param dispatcher $dispatcher
+ 	 * @param group_helper $group_helper
+ 	 * @param language $language
+ 	 * @param user $user
+ 	 * @param template $template
+ 	 * @param string $phpbb_root_path
+ 	 * @param string $phpEx
+ 	 */
+ 	public function __construct(auth $auth, birthday_helper $birthday_helper, config $config, controller_helper $controller_helper, dispatcher $dispatcher, group_helper $group_helper, language $language, user $user, template $template, string $phpbb_root_path, string $phpEx)
+ 	{
+ 		$this->auth = $auth;
+ 		$this->birthday_helper = $birthday_helper;
+ 		$this->config = $config;
+ 		$this->controller_helper = $controller_helper;
+ 		$this->dispatcher = $dispatcher;
+ 		$this->group_helper = $group_helper;
+ 		$this->language = $language;
+ 		$this->user = $user;
+ 		$this->template = $template;
+ 		$this->phpbb_root_path = $phpbb_root_path;
+ 		$this->phpEx = $phpEx;
+ 	}
+ 
+ 	/**
+ 	 * Display the index page
+ 	 *
+ 	 * @return Response
+ 	 */
+ 	public function handle(): Response
+ 	{
+ 		if (!function_exists('display_forums'))
+ 		{
+ 			include($this->phpbb_root_path . 'includes/functions_display.' . $this->phpEx);
+ 		}
+ 
+ 		if (!function_exists('get_username_string'))
+ 		{
+ 			include($this->phpbb_root_path . 'includes/functions_content.' . $this->phpEx);
+ 		}
+ 
+ 		$this->language->add_lang('viewforum');
+ 
+ 		display_forums('', $this->config['load_moderators']);
+ 
+ 		// This is shown only if display_online_list is true
+ 		$this->group_helper->display_legend();
+ 
+ 		$this->birthday_helper->display_birthdays();
+ 
+ 		$this->template->assign_vars(array(
+ 			'TOTAL_POSTS'	=> $this->language->lang('TOTAL_POSTS_COUNT', (int) $this->config['num_posts']),
+ 			'TOTAL_TOPICS'	=> $this->language->lang('TOTAL_TOPICS', (int) $this->config['num_topics']),
+ 			'TOTAL_USERS'	=> $this->language->lang('TOTAL_USERS', (int) $this->config['num_users']),
+ 			'NEWEST_USER'	=> $this->language->lang('NEWEST_USER', get_username_string('full', $this->config['newest_user_id'], $this->config['newest_username'], $this->config['newest_user_colour'])),
+ 
+ 			'S_LOGIN_ACTION'			=> append_sid("{$this->phpbb_root_path}ucp.$this->phpEx", 'mode=login'),
+ 			'U_SEND_PASSWORD'			=> ($this->config['email_enable'] && $this->config['allow_password_reset']) ? $this->controller_helper->route('phpbb_ucp_forgot_password_controller') : '',
+ 			'S_INDEX'					=> true,
+ 
+ 			'U_CANONICAL'		=> generate_board_url() . '/',
+ 			'U_MARK_FORUMS'		=> ($this->user->data['is_registered'] || $this->config['load_anon_lastread']) ? $this->controller_helper->route('phpbb_index_controller', ['hash' => generate_link_hash('global'), 'mark' => 'forums', 'mark_time' => time()]) : '',
+ 			'U_MCP'				=> ($this->auth->acl_get('m_') || $this->auth->acl_getf_global('m_')) ? append_sid("{$this->phpbb_root_path}mcp.$this->phpEx", 'i=main&amp;mode=front') : '')
+ 		);
+ 
+ 		$page_title = ($this->config['board_index_text'] !== '') ? $this->config['board_index_text'] : $this->language->lang('INDEX');
+ 
+ 		/**
+ 		 * You can use this event to modify the page title and load data for the index
+ 		 *
+ 		 * @event core.index_modify_page_title
+ 		 * @var	string	page_title		Title of the index page
+ 		 * @since 3.1.0-a1
+ 		 */
+ 		$vars = array('page_title');
+ 		extract($this->dispatcher->trigger_event('core.index_modify_page_title', compact($vars)));
+ 
+ 		return $this->controller_helper->render('index_body.html', $page_title, 200, true);
+ 	}
+ }
diff -x images -crNEB release-4.0.0-a1/phpbb/group/helper.php release-4.0.0-a2-dev/phpbb/group/helper.php
*** release-4.0.0-a1/phpbb/group/helper.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/group/helper.php	Wed Apr  1 17:47:36 2026
***************
*** 32,37 ****
--- 32,40 ----
  	/** @var avatar_helper */
  	protected $avatar_helper;
  
+ 	/** @var driver_interface */
+ 	protected $db;
+ 
  	/** @var cache */
  	protected $cache;
  
***************
*** 47,52 ****
--- 50,58 ----
  	/** @var path_helper */
  	protected $path_helper;
  
+ 	/** @var template */
+ 	protected $template;
+ 
  	/** @var user */
  	protected $user;
  
***************
*** 61,82 ****
  	 *
  	 * @param auth					$auth			Authentication object
  	 * @param avatar_helper			$avatar_helper	Avatar helper object
  	 * @param cache					$cache			Cache service object
  	 * @param config				$config			Configuration object
  	 * @param language				$language		Language object
  	 * @param dispatcher_interface	$dispatcher		Event dispatcher object
  	 * @param path_helper			$path_helper	Path helper object
  	 * @param user					$user			User object
  	 */
! 	public function __construct(auth $auth, avatar_helper $avatar_helper, cache $cache, config $config, language $language, dispatcher_interface $dispatcher, path_helper $path_helper, user $user)
  	{
  		$this->auth = $auth;
  		$this->avatar_helper = $avatar_helper;
  		$this->cache = $cache;
  		$this->config = $config;
  		$this->language = $language;
  		$this->dispatcher = $dispatcher;
  		$this->path_helper = $path_helper;
  		$this->user = $user;
  
  		$this->phpbb_root_path = $path_helper->get_phpbb_root_path();
--- 67,92 ----
  	 *
  	 * @param auth					$auth			Authentication object
  	 * @param avatar_helper			$avatar_helper	Avatar helper object
+ 	 * @param driver_interface 		$db 			Database connection
  	 * @param cache					$cache			Cache service object
  	 * @param config				$config			Configuration object
  	 * @param language				$language		Language object
  	 * @param dispatcher_interface	$dispatcher		Event dispatcher object
  	 * @param path_helper			$path_helper	Path helper object
+ 	 * @param template 				$template 		Template service
  	 * @param user					$user			User object
  	 */
! 	public function __construct(auth $auth, avatar_helper $avatar_helper, driver_interface $db, cache $cache, config $config, language $language, dispatcher_interface $dispatcher, path_helper $path_helper, template $template, user $user)
  	{
  		$this->auth = $auth;
  		$this->avatar_helper = $avatar_helper;
+ 		$this->db = $db;
  		$this->cache = $cache;
  		$this->config = $config;
  		$this->language = $language;
  		$this->dispatcher = $dispatcher;
  		$this->path_helper = $path_helper;
+ 		$this->template = $template;
  		$this->user = $user;
  
  		$this->phpbb_root_path = $path_helper->get_phpbb_root_path();
***************
*** 304,314 ****
  	/**
  	 * Display groups legend
  	 *
- 	 * @param driver_interface $db
- 	 * @param template $template
  	 * @return void
  	 */
! 	public function display_legend(driver_interface $db, template $template): void
  	{
  		$order_legend = $this->config['legend_sort_groupname'] ? 'group_name' : 'group_legend';
  
--- 314,322 ----
  	/**
  	 * Display groups legend
  	 *
  	 * @return void
  	 */
! 	public function display_legend(): void
  	{
  		$order_legend = $this->config['legend_sort_groupname'] ? 'group_name' : 'group_legend';
  
***************
*** 334,351 ****
  					AND (g.group_type <> ' . GROUP_HIDDEN . ' OR ug.user_id = ' . $this->user->data['user_id'] . ')
  				ORDER BY g.' . $order_legend . ' ASC';
  		}
! 		$result = $db->sql_query($sql);
  
! 		while ($row = $db->sql_fetchrow($result))
  		{
  			$show_group_url = $row['group_name'] != 'BOTS' && $this->auth->acl_get('u_viewprofile');
  
! 			$template->assign_block_vars('LEGEND', [
  				'GROUP_COLOR'		=> $row['group_colour'] ?: '',
  				'GROUP_NAME'		=> $this->get_name($row['group_name']),
  				'GROUP_URL'			=> $show_group_url ? append_sid("{$this->path_helper->get_phpbb_root_path()}memberlist.{$this->path_helper->get_php_ext()}", 'mode=group&amp;g=' . $row['group_id']) : '',
  			]);
  		}
! 		$db->sql_freeresult($result);
  	}
  }
--- 342,359 ----
  					AND (g.group_type <> ' . GROUP_HIDDEN . ' OR ug.user_id = ' . $this->user->data['user_id'] . ')
  				ORDER BY g.' . $order_legend . ' ASC';
  		}
! 		$result = $this->db->sql_query($sql);
  
! 		while ($row = $this->db->sql_fetchrow($result))
  		{
  			$show_group_url = $row['group_name'] != 'BOTS' && $this->auth->acl_get('u_viewprofile');
  
! 			$this->template->assign_block_vars('LEGEND', [
  				'GROUP_COLOR'		=> $row['group_colour'] ?: '',
  				'GROUP_NAME'		=> $this->get_name($row['group_name']),
  				'GROUP_URL'			=> $show_group_url ? append_sid("{$this->path_helper->get_phpbb_root_path()}memberlist.{$this->path_helper->get_php_ext()}", 'mode=group&amp;g=' . $row['group_id']) : '',
  			]);
  		}
! 		$this->db->sql_freeresult($result);
  	}
  }
diff -x images -crNEB release-4.0.0-a1/phpbb/install/console/command/install/config/show.php release-4.0.0-a2-dev/phpbb/install/console/command/install/config/show.php
*** release-4.0.0-a1/phpbb/install/console/command/install/config/show.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/console/command/install/config/show.php	Wed Apr  1 17:47:36 2026
***************
*** 76,82 ****
  	 *
  	 * @return int 0 if everything went fine, or a non-zero exit code
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$this->iohandler_factory->set_environment('cli');
  
--- 76,82 ----
  	 *
  	 * @return int 0 if everything went fine, or a non-zero exit code
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$this->iohandler_factory->set_environment('cli');
  
***************
*** 96,102 ****
  
  		try
  		{
! 			$config = Yaml::parse(file_get_contents($config_file), true);
  		}
  		catch (ParseException $e)
  		{
--- 96,102 ----
  
  		try
  		{
! 			$config = Yaml::parse(file_get_contents($config_file), Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
  		}
  		catch (ParseException $e)
  		{
diff -x images -crNEB release-4.0.0-a1/phpbb/install/console/command/install/config/validate.php release-4.0.0-a2-dev/phpbb/install/console/command/install/config/validate.php
*** release-4.0.0-a1/phpbb/install/console/command/install/config/validate.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/console/command/install/config/validate.php	Wed Apr  1 17:47:36 2026
***************
*** 76,82 ****
  	 *
  	 * @return int 0 if everything went fine, or a non-zero exit code
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$this->iohandler_factory->set_environment('cli');
  
--- 76,82 ----
  	 *
  	 * @return int 0 if everything went fine, or a non-zero exit code
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$this->iohandler_factory->set_environment('cli');
  
***************
*** 96,102 ****
  
  		try
  		{
! 			$config = Yaml::parse(file_get_contents($config_file), true);
  		}
  		catch (ParseException $e)
  		{
--- 96,102 ----
  
  		try
  		{
! 			$config = Yaml::parse(file_get_contents($config_file), Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
  		}
  		catch (ParseException $e)
  		{
diff -x images -crNEB release-4.0.0-a1/phpbb/install/console/command/install/install.php release-4.0.0-a2-dev/phpbb/install/console/command/install/install.php
*** release-4.0.0-a1/phpbb/install/console/command/install/install.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/console/command/install/install.php	Wed Apr  1 17:47:36 2026
***************
*** 97,103 ****
  	 * @return int 0 if everything went fine, or a non-zero exit code
  	 * @throws iohandler_not_implemented_exception
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$this->iohandler_factory->set_environment('cli');
  
--- 97,103 ----
  	 * @return int 0 if everything went fine, or a non-zero exit code
  	 * @throws iohandler_not_implemented_exception
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$this->iohandler_factory->set_environment('cli');
  
***************
*** 126,132 ****
  
  		try
  		{
! 			$config = Yaml::parse(file_get_contents($config_file), true);
  		}
  		catch (ParseException $e)
  		{
--- 126,132 ----
  
  		try
  		{
! 			$config = Yaml::parse(file_get_contents($config_file), Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
  		}
  		catch (ParseException $e)
  		{
diff -x images -crNEB release-4.0.0-a1/phpbb/install/console/command/update/config/show.php release-4.0.0-a2-dev/phpbb/install/console/command/update/config/show.php
*** release-4.0.0-a1/phpbb/install/console/command/update/config/show.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/console/command/update/config/show.php	Wed Apr  1 17:47:36 2026
***************
*** 76,82 ****
  	 *
  	 * @return int 0 if everything went fine, or a non-zero exit code
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$this->iohandler_factory->set_environment('cli');
  
--- 76,82 ----
  	 *
  	 * @return int 0 if everything went fine, or a non-zero exit code
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$this->iohandler_factory->set_environment('cli');
  
***************
*** 96,102 ****
  
  		try
  		{
! 			$config = Yaml::parse(file_get_contents($config_file), true);
  		}
  		catch (ParseException $e)
  		{
--- 96,102 ----
  
  		try
  		{
! 			$config = Yaml::parse(file_get_contents($config_file), Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
  		}
  		catch (ParseException $e)
  		{
diff -x images -crNEB release-4.0.0-a1/phpbb/install/console/command/update/config/validate.php release-4.0.0-a2-dev/phpbb/install/console/command/update/config/validate.php
*** release-4.0.0-a1/phpbb/install/console/command/update/config/validate.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/console/command/update/config/validate.php	Wed Apr  1 17:47:36 2026
***************
*** 76,82 ****
  	 *
  	 * @return int 0 if everything went fine, or a non-zero exit code
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$this->iohandler_factory->set_environment('cli');
  
--- 76,82 ----
  	 *
  	 * @return int 0 if everything went fine, or a non-zero exit code
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$this->iohandler_factory->set_environment('cli');
  
***************
*** 96,102 ****
  
  		try
  		{
! 			$config = Yaml::parse(file_get_contents($config_file), true);
  		}
  		catch (ParseException $e)
  		{
--- 96,102 ----
  
  		try
  		{
! 			$config = Yaml::parse(file_get_contents($config_file), Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
  		}
  		catch (ParseException $e)
  		{
diff -x images -crNEB release-4.0.0-a1/phpbb/install/console/command/update/update.php release-4.0.0-a2-dev/phpbb/install/console/command/update/update.php
*** release-4.0.0-a1/phpbb/install/console/command/update/update.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/console/command/update/update.php	Wed Apr  1 17:47:36 2026
***************
*** 95,101 ****
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output)
  	{
  		$this->iohandler_factory->set_environment('cli');
  
--- 95,101 ----
  	 *
  	 * @return int
  	 */
! 	protected function execute(InputInterface $input, OutputInterface $output): int
  	{
  		$this->iohandler_factory->set_environment('cli');
  
***************
*** 124,130 ****
  
  		try
  		{
! 			$config = Yaml::parse(file_get_contents($config_file), true);
  		}
  		catch (ParseException $e)
  		{
--- 124,130 ----
  
  		try
  		{
! 			$config = Yaml::parse(file_get_contents($config_file), Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
  		}
  		catch (ParseException $e)
  		{
diff -x images -crNEB release-4.0.0-a1/phpbb/install/controller/helper.php release-4.0.0-a2-dev/phpbb/install/controller/helper.php
*** release-4.0.0-a1/phpbb/install/controller/helper.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/controller/helper.php	Wed Apr  1 17:47:36 2026
***************
*** 205,210 ****
--- 205,227 ----
  	}
  
  	/**
+ 	 * Handle installer restart
+ 	 */
+ 	public function handle_installer_restart(): void
+ 	{
+ 		$restart = $this->phpbb_request->variable('install_restart', false);
+ 		if ($restart)
+ 		{
+ 			// Clean up config file to restart installer
+ 			$this->installer_config->clean_up_config_file();
+ 		}
+ 		else if ($this->installer_config->exists())
+ 		{
+ 			$this->template->assign_var('SHOW_RESTART_BUTTON', true);
+ 		}
+ 	}
+ 
+ 	/**
  	 * Process navigation data to reflect active/completed stages
  	 *
  	 * @param \phpbb\install\helper\iohandler\iohandler_interface|null	$iohandler
diff -x images -crNEB release-4.0.0-a1/phpbb/install/controller/install.php release-4.0.0-a2-dev/phpbb/install/controller/install.php
*** release-4.0.0-a1/phpbb/install/controller/install.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/controller/install.php	Wed Apr  1 17:47:36 2026
***************
*** 126,131 ****
--- 126,132 ----
  		// Set the appropriate input-output handler
  		$this->installer->set_iohandler($this->iohandler_factory->get());
  		$this->controller_helper->handle_language_select();
+ 		$this->controller_helper->handle_installer_restart();
  
  		if ($this->request->is_ajax())
  		{
diff -x images -crNEB release-4.0.0-a1/phpbb/install/helper/config.php release-4.0.0-a2-dev/phpbb/install/helper/config.php
*** release-4.0.0-a1/phpbb/install/helper/config.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/helper/config.php	Wed Apr  1 17:47:36 2026
***************
*** 218,228 ****
  	}
  
  	/**
  	 * Recovers install configuration from file
  	 */
  	public function load_config()
  	{
! 		if (!$this->filesystem->exists($this->install_config_file))
  		{
  			return;
  		}
--- 218,238 ----
  	}
  
  	/**
+ 	 * Checks whether install config exists, i.e. whether an installation is in progress
+ 	 *
+ 	 * @return bool
+ 	 */
+ 	public function exists(): bool
+ 	{
+ 		return $this->filesystem->exists($this->install_config_file);
+ 	}
+ 
+ 	/**
  	 * Recovers install configuration from file
  	 */
  	public function load_config()
  	{
! 		if (!$this->exists())
  		{
  			return;
  		}
diff -x images -crNEB release-4.0.0-a1/phpbb/install/helper/database.php release-4.0.0-a2-dev/phpbb/install/helper/database.php
*** release-4.0.0-a1/phpbb/install/helper/database.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/helper/database.php	Wed Apr  1 17:47:36 2026
***************
*** 225,231 ****
  		// Remove # style comments
  		$sql_query = preg_replace('/\n{2,}/', "\n", preg_replace('/^#.*$/m', "\n", $sql_query));
  
! 		return $sql_query;
  	}
  
  	/**
--- 225,231 ----
  		// Remove # style comments
  		$sql_query = preg_replace('/\n{2,}/', "\n", preg_replace('/^#.*$/m', "\n", $sql_query));
  
! 		return $sql_query ?: '';
  	}
  
  	/**
diff -x images -crNEB release-4.0.0-a1/phpbb/install/installer_configuration.php release-4.0.0-a2-dev/phpbb/install/installer_configuration.php
*** release-4.0.0-a1/phpbb/install/installer_configuration.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/installer_configuration.php	Wed Apr  1 17:47:36 2026
***************
*** 24,30 ****
  	 *
  	 * @return TreeBuilder The tree builder
  	 */
! 	public function getConfigTreeBuilder()
  	{
  		$treeBuilder = new TreeBuilder('installer');
  		$rootNode = $treeBuilder->getRootNode();
--- 24,30 ----
  	 *
  	 * @return TreeBuilder The tree builder
  	 */
! 	public function getConfigTreeBuilder(): TreeBuilder
  	{
  		$treeBuilder = new TreeBuilder('installer');
  		$rootNode = $treeBuilder->getRootNode();
diff -x images -crNEB release-4.0.0-a1/phpbb/install/module/install_database/task/add_default_data.php release-4.0.0-a2-dev/phpbb/install/module/install_database/task/add_default_data.php
*** release-4.0.0-a1/phpbb/install/module/install_database/task/add_default_data.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/module/install_database/task/add_default_data.php	Wed Apr  1 17:47:36 2026
***************
*** 147,153 ****
  			break;
  		}
  
! 		return $query;
  	}
  
  	/**
--- 147,153 ----
  			break;
  		}
  
! 		return $query ?: '';
  	}
  
  	/**
diff -x images -crNEB release-4.0.0-a1/phpbb/install/module/install_database/task/create_schema_file.php release-4.0.0-a2-dev/phpbb/install/module/install_database/task/create_schema_file.php
*** release-4.0.0-a1/phpbb/install/module/install_database/task/create_schema_file.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/module/install_database/task/create_schema_file.php	Wed Apr  1 17:47:36 2026
***************
*** 15,20 ****
--- 15,21 ----
  
  use phpbb\db\doctrine\connection_factory;
  use phpbb\install\exception\resource_limit_reached_exception;
+ use Symfony\Component\Yaml\Yaml;
  
  /**
   * Create database schema
***************
*** 146,152 ****
  			$factory = new \phpbb\db\tools\factory();
  			$db_tools = $factory->get($this->db_doctrine, true);
  			$db_tools->set_table_prefix($table_prefix);
! 			$tables_data = \Symfony\Component\Yaml\Yaml::parseFile($this->phpbb_root_path . '/config/default/container/tables.yml');
  			$tables = [];
  			foreach ($tables_data['parameters'] as $parameter => $table)
  			{
--- 147,153 ----
  			$factory = new \phpbb\db\tools\factory();
  			$db_tools = $factory->get($this->db_doctrine, true);
  			$db_tools->set_table_prefix($table_prefix);
! 			$tables_data = Yaml::parseFile($this->phpbb_root_path . '/config/default/container/tables.yml', Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
  			$tables = [];
  			foreach ($tables_data['parameters'] as $parameter => $table)
  			{
diff -x images -crNEB release-4.0.0-a1/phpbb/install/module/obtain_data/task/obtain_server_data.php release-4.0.0-a2-dev/phpbb/install/module/obtain_data/task/obtain_server_data.php
*** release-4.0.0-a1/phpbb/install/module/obtain_data/task/obtain_server_data.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/module/obtain_data/task/obtain_server_data.php	Wed Apr  1 17:47:36 2026
***************
*** 73,79 ****
  		}
  
  		$script_path = str_replace(array('\\', '//'), '/', $script_path);
! 		$script_path = trim(dirname(dirname(dirname($script_path)))); // Because we are in install/app.php/route_name
  
  		// Server data
  		$cookie_secure		= $this->io_handler->get_input('cookie_secure', $cookie_secure);
--- 73,79 ----
  		}
  
  		$script_path = str_replace(array('\\', '//'), '/', $script_path);
! 		$script_path = trim(dirname(dirname(dirname($script_path)))); // Because we are in install/index.php/route_name
  
  		// Server data
  		$cookie_secure		= $this->io_handler->get_input('cookie_secure', $cookie_secure);
diff -x images -crNEB release-4.0.0-a1/phpbb/install/module/requirements/task/check_server_environment.php release-4.0.0-a2-dev/phpbb/install/module/requirements/task/check_server_environment.php
*** release-4.0.0-a1/phpbb/install/module/requirements/task/check_server_environment.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/module/requirements/task/check_server_environment.php	Wed Apr  1 17:47:36 2026
***************
*** 96,102 ****
  	 */
  	protected function check_php_version()
  	{
! 		if (version_compare(PHP_VERSION, '8.1.0', '<'))
  		{
  			$this->response_helper->add_error_message('PHP_VERSION_REQD', 'PHP_VERSION_REQD_EXPLAIN');
  
--- 96,102 ----
  	 */
  	protected function check_php_version()
  	{
! 		if (version_compare(PHP_VERSION, '8.2.0', '<'))
  		{
  			$this->response_helper->add_error_message('PHP_VERSION_REQD', 'PHP_VERSION_REQD_EXPLAIN');
  
diff -x images -crNEB release-4.0.0-a1/phpbb/install/module/update_filesystem/task/diff_files.php release-4.0.0-a2-dev/phpbb/install/module/update_filesystem/task/diff_files.php
*** release-4.0.0-a1/phpbb/install/module/update_filesystem/task/diff_files.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/module/update_filesystem/task/diff_files.php	Wed Apr  1 17:47:36 2026
***************
*** 187,194 ****
  
  					if (!$empty && in_array($filename, $merge_conflicts))
  					{
! 					$merge_conflicts[] = $filename;
! 				}
  					else
  					{
  						$file_is_merged = true;
--- 187,194 ----
  
  					if (!$empty && in_array($filename, $merge_conflicts))
  					{
! 						$merge_conflicts[] = $filename;
! 					}
  					else
  					{
  						$file_is_merged = true;
diff -x images -crNEB release-4.0.0-a1/phpbb/install/updater_configuration.php release-4.0.0-a2-dev/phpbb/install/updater_configuration.php
*** release-4.0.0-a1/phpbb/install/updater_configuration.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/install/updater_configuration.php	Wed Apr  1 17:47:36 2026
***************
*** 24,30 ****
  	 *
  	 * @return TreeBuilder The tree builder
  	 */
! 	public function getConfigTreeBuilder()
  	{
  		$treeBuilder = new TreeBuilder('updater');
  		$rootNode = $treeBuilder->getRootNode();
--- 24,30 ----
  	 *
  	 * @return TreeBuilder The tree builder
  	 */
! 	public function getConfigTreeBuilder(): TreeBuilder
  	{
  		$treeBuilder = new TreeBuilder('updater');
  		$rootNode = $treeBuilder->getRootNode();
diff -x images -crNEB release-4.0.0-a1/phpbb/manifest.php release-4.0.0-a2-dev/phpbb/manifest.php
*** release-4.0.0-a1/phpbb/manifest.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/manifest.php	Wed Apr  1 17:47:36 2026
***************
*** 22,30 ****
  	/** @var config */
  	protected $config;
  
- 	/** @var path_helper */
- 	protected $path_helper;
- 
  	/** @var dispatcher_interface */
  	protected $phpbb_dispatcher;
  
--- 22,27 ----
***************
*** 35,48 ****
  	 * Constructor for manifest controller
  	 *
  	 * @param config $config
- 	 * @param path_helper $path_helper
  	 * @param dispatcher_interface $phpbb_dispatcher
  	 * @param user $user
  	 */
! 	public function __construct(config $config, path_helper $path_helper, dispatcher_interface $phpbb_dispatcher, user $user)
  	{
  		$this->config = $config;
- 		$this->path_helper = $path_helper;
  		$this->phpbb_dispatcher = $phpbb_dispatcher;
  		$this->user = $user;
  	}
--- 32,43 ----
  	 * Constructor for manifest controller
  	 *
  	 * @param config $config
  	 * @param dispatcher_interface $phpbb_dispatcher
  	 * @param user $user
  	 */
! 	public function __construct(config $config, dispatcher_interface $phpbb_dispatcher, user $user)
  	{
  		$this->config = $config;
  		$this->phpbb_dispatcher = $phpbb_dispatcher;
  		$this->user = $user;
  	}
***************
*** 54,60 ****
  	 */
  	public function handle(): JsonResponse
  	{
! 		$board_path = $this->config['force_server_vars'] ? $this->config['script_path'] : $this->path_helper->get_web_root_path();
  
  		$sitename = html_entity_decode($this->config['sitename'], ENT_QUOTES, 'UTF-8');
  		$sitename_short = html_entity_decode($this->config['sitename_short'], ENT_QUOTES, 'UTF-8');
--- 49,60 ----
  	 */
  	public function handle(): JsonResponse
  	{
! 		// Get the board URL and extract the path component
! 		$board_path = $this->config['force_server_vars'] ? $this->config['script_path'] : (parse_url(generate_board_url(), PHP_URL_PATH) ?? '');
! 
! 		// Ensure path ends with '/' for PWA scope
! 		$scope = rtrim($board_path, '/\\') . '/';
! 		$start_url = $scope;
  
  		$sitename = html_entity_decode($this->config['sitename'], ENT_QUOTES, 'UTF-8');
  		$sitename_short = html_entity_decode($this->config['sitename_short'], ENT_QUOTES, 'UTF-8');
***************
*** 64,71 ****
  			'short_name'	=> $sitename_short ?: utf8_substr($sitename, 0, 12),
  			'display'		=> 'standalone',
  			'orientation'	=> 'portrait',
! 			'start_url'		=> $board_path,
! 			'scope'			=> $board_path,
  		];
  
  		/**
--- 64,71 ----
  			'short_name'	=> $sitename_short ?: utf8_substr($sitename, 0, 12),
  			'display'		=> 'standalone',
  			'orientation'	=> 'portrait',
! 			'start_url'		=> $start_url,
! 			'scope'			=> $scope,
  		];
  
  		/**
***************
*** 73,84 ****
  		 *
  		 * @event core.modify_manifest
  		 * @var	array	manifest	    Array of manifest members
! 		 * @var	string	board_path	    Path to the board root
  		 * @var	string	sitename	    Full name of the board
  		 * @var	string	sitename_short	Shortened name of the board
  		 * @since 4.0.0-a1
  		 */
! 		$vars = ['manifest', 'board_path', 'sitename', 'sitename_short'];
  		extract($this->phpbb_dispatcher->trigger_event('core.modify_manifest', compact($vars)));
  
  		$response = new JsonResponse($manifest);
--- 73,85 ----
  		 *
  		 * @event core.modify_manifest
  		 * @var	array	manifest	    Array of manifest members
! 		 * @var string  scope           PWA scope path
! 		 * @var string  start_url       PWA start URL
  		 * @var	string	sitename	    Full name of the board
  		 * @var	string	sitename_short	Shortened name of the board
  		 * @since 4.0.0-a1
  		 */
! 		$vars = ['manifest', 'scope', 'start_url', 'sitename', 'sitename_short'];
  		extract($this->phpbb_dispatcher->trigger_event('core.modify_manifest', compact($vars)));
  
  		$response = new JsonResponse($manifest);
diff -x images -crNEB release-4.0.0-a1/phpbb/members/controller/online.php release-4.0.0-a2-dev/phpbb/members/controller/online.php
*** release-4.0.0-a1/phpbb/members/controller/online.php	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/phpbb/members/controller/online.php	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,311 ----
+ <?php
+ /**
+  *
+  * This file is part of the phpBB Forum Software package.
+  *
+  * @copyright (c) phpBB Limited <https://www.phpbb.com>
+  * @license GNU General Public License, version 2 (GPL-2.0)
+  *
+  * For full copyright and license information, please see
+  * the docs/CREDITS.txt file.
+  *
+  */
+ 
+ namespace phpbb\members\controller;
+ 
+ use phpbb\auth\auth;
+ use phpbb\config\config;
+ use phpbb\event\dispatcher;
+ use phpbb\exception\http_exception;
+ use phpbb\group\helper as group_helper;
+ use phpbb\controller\helper;
+ use phpbb\language\language;
+ use phpbb\members\viewonline_helper;
+ use phpbb\pagination;
+ use phpbb\request\request;
+ use phpbb\template\template;
+ use phpbb\user;
+ use Symfony\Component\HttpFoundation\Response;
+ 
+ class online
+ {
+ 	/** @var auth */
+ 	protected $auth;
+ 
+ 	/** @var config */
+ 	protected $config;
+ 
+ 	/** @var dispatcher */
+ 	protected $dispatcher;
+ 
+ 	/** @var group_helper */
+ 	protected $group_helper;
+ 
+ 	/** @var viewonline_helper */
+ 	protected $viewonline_helper;
+ 
+ 	/** @var helper */
+ 	protected $helper;
+ 
+ 	/** @var language */
+ 	protected $language;
+ 
+ 	/** @var pagination */
+ 	protected $pagination;
+ 
+ 	/** @var request */
+ 	protected $request;
+ 
+ 	/** @var template */
+ 	protected $template;
+ 
+ 	/** @var user */
+ 	protected $user;
+ 
+ 	/** @var string */
+ 	protected $phpbb_root_path;
+ 
+ 	/** @var string */
+ 	protected $php_ex;
+ 
+ 	/**
+ 	 * online constructor.
+ 	 * @param auth $auth
+ 	 * @param config $config
+ 	 * @param dispatcher $dispatcher
+ 	 * @param group_helper $group_helper
+ 	 * @param viewonline_helper $viewonline_helper
+ 	 * @param helper $helper
+ 	 * @param language $language
+ 	 * @param pagination $pagination
+ 	 * @param request $request
+ 	 * @param template $template
+ 	 * @param user $user
+ 	 * @param string $phpbb_root_path
+ 	 * @param string $php_ex
+ 	 */
+ 	public function __construct(auth $auth, config $config, dispatcher $dispatcher, group_helper $group_helper, viewonline_helper $viewonline_helper, helper $helper, language $language, pagination $pagination, request $request, template $template, user $user, string $phpbb_root_path, string $php_ex)
+ 	{
+ 		$this->auth					= $auth;
+ 		$this->config				= $config;
+ 		$this->dispatcher			= $dispatcher;
+ 		$this->group_helper			= $group_helper;
+ 		$this->viewonline_helper	= $viewonline_helper;
+ 		$this->helper				= $helper;
+ 		$this->language				= $language;
+ 		$this->pagination			= $pagination;
+ 		$this->request				= $request;
+ 		$this->template				= $template;
+ 		$this->user					= $user;
+ 		$this->phpbb_root_path		= $phpbb_root_path;
+ 		$this->php_ex				= $php_ex;
+ 	}
+ 
+ 	/**
+ 	 * Controller for /online route
+ 	 *
+ 	 * @return Response a Symfony response object
+ 	 */
+ 	public function handle(): Response
+ 	{
+ 		// Display a listing of board admins, moderators
+ 		if (!function_exists('display_user_activity'))
+ 		{
+ 			include($this->phpbb_root_path . 'includes/functions_display.' . $this->php_ex);
+ 		}
+ 
+ 		// Load language strings
+ 		$this->language->add_lang('memberlist');
+ 
+ 		// Can this user view profiles/memberlist?
+ 		if (!$this->auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel'))
+ 		{
+ 			if ($this->user->data['user_id'] != ANONYMOUS)
+ 			{
+ 				throw new http_exception(403, 'NO_VIEW_USERS');
+ 			}
+ 
+ 			login_box('', $this->language->lang('LOGIN_EXPLAIN_VIEWONLINE'));
+ 		}
+ 
+ 		// Get and set some variables
+ 		$mode			= $this->request->variable('mode', '');
+ 		$session_id		= $this->request->variable('s', '');
+ 		$start			= $this->request->variable('start', 0);
+ 		$sort_key		= $this->request->variable('sk', 'b');
+ 		$sort_dir		= $this->request->variable('sd', 'd');
+ 		$show_guests	= ($this->config['load_online_guests']) ? $this->request->variable('sg', 0) : 0;
+ 
+ 		$sort_key_text = ['a' => $this->language->lang('SORT_USERNAME'), 'b' => $this->language->lang('SORT_JOINED'), 'c' => $this->language->lang('SORT_LOCATION')];
+ 		$sort_key_sql = ['a' => 'u.username_clean', 'b' => 's.session_time', 'c' => 's.session_page'];
+ 
+ 		// Sorting and order
+ 		if (!isset($sort_key_text[$sort_key]))
+ 		{
+ 			$sort_key = 'b';
+ 		}
+ 
+ 		$order_by = $sort_key_sql[$sort_key] . ' ' . (($sort_dir == 'a') ? 'ASC' : 'DESC');
+ 
+ 		$this->user->update_session_infos();
+ 
+ 		// Get number of online guests (if we do not display them)
+ 		$guest_counter = (!$show_guests) ? $this->viewonline_helper->get_number_guests() : 0;
+ 
+ 		// Get user list (moved into viewonline_helper)
+ 		$session_data_rowset = $this->viewonline_helper->get_session_data_rowset($show_guests, $order_by, $guest_counter);
+ 
+ 		$prev_id = $prev_ip = $user_list = [];
+ 		$logged_visible_online = $logged_hidden_online = $counter = 0;
+ 
+ 		// Get forum IDs for session pages which have only 't' parameter
+ 		$this->viewonline_helper->get_forum_ids($session_data_rowset);
+ 
+ 		foreach ($session_data_rowset as $row)
+ 		{
+ 			if ($row['user_id'] != ANONYMOUS && !isset($prev_id[$row['user_id']]))
+ 			{
+ 				$view_online = $s_user_hidden = false;
+ 				$user_colour = ($row['user_colour']) ? ' style="color:#' . $row['user_colour'] . '" class="username-coloured"' : '';
+ 
+ 				$username_full = ($row['user_type'] != USER_IGNORE) ? get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']) : '<span' . $user_colour . '>' . $row['username'] . '</span>';
+ 
+ 				if (!$row['session_viewonline'])
+ 				{
+ 					$view_online = ($this->auth->acl_get('u_viewonline') || $row['user_id'] === $this->user->data['user_id']) ? true : false;
+ 					$logged_hidden_online++;
+ 
+ 					$username_full = '<em>' . $username_full . '</em>';
+ 					$s_user_hidden = true;
+ 				}
+ 				else
+ 				{
+ 					$view_online = true;
+ 					$logged_visible_online++;
+ 				}
+ 
+ 				$prev_id[$row['user_id']] = 1;
+ 
+ 				if ($view_online)
+ 				{
+ 					$counter++;
+ 				}
+ 
+ 				if (!$view_online || $counter > $start + $this->config['topics_per_page'] || $counter <= $start)
+ 				{
+ 					continue;
+ 				}
+ 			}
+ 			else if ($show_guests && $row['user_id'] == ANONYMOUS && !isset($prev_ip[$row['session_ip']]))
+ 			{
+ 				$prev_ip[$row['session_ip']] = 1;
+ 				$guest_counter++;
+ 				$counter++;
+ 
+ 				if ($counter > $start + $this->config['topics_per_page'] || $counter <= $start)
+ 				{
+ 					continue;
+ 				}
+ 
+ 				$s_user_hidden = false;
+ 				$username_full = get_username_string('full', $row['user_id'], $this->language->lang('GUEST'));
+ 			}
+ 			else
+ 			{
+ 				continue;
+ 			}
+ 
+ 			list($location, $location_url) = $this->viewonline_helper->get_location($row['session_page'], $row['session_forum_id']);
+ 
+ 			$session_page = parse_url($row['session_page'], PHP_URL_PATH);
+ 			$session_page = preg_replace('/^\/index\.php\//', '/', $session_page);
+ 			$on_page = $this->viewonline_helper->get_user_page($session_page);
+ 
+ 			$forum_data = $this->viewonline_helper->get_forum_data();
+ 
+ 			/**
+ 			* Overwrite the location's name and URL, which are displayed in the list
+ 			*
+ 			* @event core.viewonline_overwrite_location
+ 			* @var	array	on_page			File name and query string
+ 			* @var	array	row				Array with the users sql row
+ 			* @var	string	location		Page name to displayed in the list
+ 			* @var	string	location_url	Page url to displayed in the list
+ 			* @var	array	forum_data		Array with forum data
+ 			* @since 3.1.0-a1
+ 			* @changed 3.1.0-a2 Added var forum_data
+ 			*/
+ 			$vars = ['on_page', 'row', 'location', 'location_url', 'forum_data'];
+ 			extract($this->dispatcher->trigger_event('core.viewonline_overwrite_location', compact($vars)));
+ 
+ 			$template_row = [
+ 				'USERNAME' 			=> $row['username'],
+ 				'USERNAME_COLOUR'	=> $row['user_colour'],
+ 				'USERNAME_FULL'		=> $username_full,
+ 				'LASTUPDATE'		=> $this->user->format_date($row['session_time']),
+ 				'FORUM_LOCATION'	=> $location,
+ 				'USER_IP'			=> ($this->auth->acl_get('a_')) ? (($mode == 'lookup' && $session_id == $row['session_id']) ? gethostbyaddr($row['session_ip']) : $row['session_ip']) : '',
+ 				'USER_BROWSER'		=> ($this->auth->acl_get('a_user')) ? $row['session_browser'] : '',
+ 
+ 				'U_USER_PROFILE'	=> ($row['user_type'] != USER_IGNORE) ? get_username_string('profile', $row['user_id'], '') : '',
+ 				'U_USER_IP'			=> append_sid($this->phpbb_root_path . "viewonline." . $this->php_ex, 'mode=lookup' . (($mode != 'lookup' || $row['session_id'] != $session_id) ? '&amp;s=' . $row['session_id'] : '') . "&amp;sg=$show_guests&amp;start=$start&amp;sk=$sort_key&amp;sd=$sort_dir"),
+ 				'U_WHOIS'			=> $this->helper->route('phpbb_members_online_whois', ['session_id' => $row['session_id']]),
+ 				'U_FORUM_LOCATION'	=> $location_url,
+ 
+ 				'S_USER_HIDDEN'		=> $s_user_hidden,
+ 				'S_GUEST'			=> ($row['user_id'] == ANONYMOUS) ? true : false,
+ 				'S_USER_TYPE'		=> $row['user_type'],
+ 			];
+ 
+ 			/**
+ 			* Modify viewonline template data before it is displayed in the list
+ 			*
+ 			* @event core.viewonline_modify_user_row
+ 			* @var	array	on_page			File name and query string
+ 			* @var	array	row				Array with the users sql row
+ 			* @var	array	forum_data		Array with forum data
+ 			* @var	array	template_row	Array with template variables for the user row
+ 			* @since 3.1.0-RC4
+ 			*/
+ 			$vars = ['on_page', 'row', 'forum_data', 'template_row'];
+ 			extract($this->dispatcher->trigger_event('core.viewonline_modify_user_row', compact($vars)));
+ 
+ 			$this->template->assign_block_vars('user_row', $template_row);
+ 		}
+ 
+ 		$this->group_helper->display_legend();
+ 
+ 		// Refreshing the page every 60 seconds...
+ 		meta_refresh(60, $this->helper->route('phpbb_members_online', ['sg' => $show_guests, 'sk' => $sort_key, 'sd' => $sort_dir, 'start' => $start]));
+ 
+ 		$start = $this->pagination->validate_start($start, $this->config['topics_per_page'], $counter);
+ 		$base_url = $this->helper->route('phpbb_members_online', ['sg' => $show_guests, 'sk' => $sort_key, 'sd' => $sort_dir]);
+ 		$this->pagination->generate_template_pagination($base_url, 'pagination', 'start', $counter, $this->config['topics_per_page'], $start);
+ 
+ 		// Send data to template
+ 		$this->template->assign_vars([
+ 			'TOTAL_REGISTERED_USERS_ONLINE'	=> $this->language->lang('REG_USERS_ONLINE', (int) $logged_visible_online, $this->language->lang('HIDDEN_USERS_ONLINE', (int) $logged_hidden_online)),
+ 			'TOTAL_GUEST_USERS_ONLINE'		=> $this->language->lang('GUEST_USERS_ONLINE', (int) $guest_counter),
+ 
+ 			'U_SORT_USERNAME'		=> $this->helper->route('phpbb_members_online', ['sg' => (int) $show_guests, 'sk' => 'a', 'sd' => (($sort_key == 'a' && $sort_dir == 'a') ? 'd' : 'a')]),
+ 			'U_SORT_UPDATED'		=> $this->helper->route('phpbb_members_online', ['sg' => (int) $show_guests, 'sk' => 'b', 'sd' => (($sort_key == 'b' && $sort_dir == 'a') ? 'd' : 'a')]),
+ 			'U_SORT_LOCATION'		=> $this->helper->route('phpbb_members_online', ['sg' => (int) $show_guests, 'sk' => 'c', 'sd' => (($sort_key == 'c' && $sort_dir == 'a') ? 'd' : 'a')]),
+ 
+ 			'U_SWITCH_GUEST_DISPLAY'	=> $this->helper->route('phpbb_members_online', ['sg' => (int) !$show_guests]),
+ 			'L_SWITCH_GUEST_DISPLAY'	=> ($show_guests) ? $this->language->lang('HIDE_GUESTS') : $this->language->lang('DISPLAY_GUESTS'),
+ 			'S_SWITCH_GUEST_DISPLAY'	=> ($this->config['load_online_guests']) ? true : false,
+ 			'S_VIEWONLINE'				=> true,
+ 		]);
+ 
+ 		$this->template->assign_block_vars('navlinks', [
+ 			'BREADCRUMB_NAME'	=> $this->language->lang('WHO_IS_ONLINE'),
+ 			'U_BREADCRUMB'		=> $this->helper->route('phpbb_members_online'),
+ 		]);
+ 
+ 		make_jumpbox(append_sid($this->phpbb_root_path . "viewforum." . $this->php_ex));
+ 
+ 		// Render
+ 		return $this->helper->render('viewonline_body.html', $this->language->lang('WHO_IS_ONLINE'));
+ 	}
+ 
+ }
diff -x images -crNEB release-4.0.0-a1/phpbb/members/controller/online_whois.php release-4.0.0-a2-dev/phpbb/members/controller/online_whois.php
*** release-4.0.0-a1/phpbb/members/controller/online_whois.php	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/phpbb/members/controller/online_whois.php	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,122 ----
+ <?php
+ /**
+  *
+  * This file is part of the phpBB Forum Software package.
+  *
+  * @copyright (c) phpBB Limited <https://www.phpbb.com>
+  * @license GNU General Public License, version 2 (GPL-2.0)
+  *
+  * For full copyright and license information, please see
+  * the docs/CREDITS.txt file.
+  *
+  */
+ 
+ namespace phpbb\members\controller;
+ 
+ use phpbb\auth\auth;
+ use phpbb\db\driver\driver_interface;
+ use phpbb\controller\helper;
+ use phpbb\exception\http_exception;
+ use phpbb\language\language;
+ use phpbb\template\template;
+ use phpbb\user;
+ use Symfony\Component\HttpFoundation\Response;
+ 
+ class online_whois
+ {
+ 	/** @var auth */
+ 	protected $auth;
+ 
+ 	/** @var driver_interface */
+ 	protected $db;
+ 
+ 	/** @var helper */
+ 	protected $helper;
+ 
+ 	/** @var language */
+ 	protected $language;
+ 
+ 	/** @var template */
+ 	protected $template;
+ 
+ 	/** @var user */
+ 	protected $user;
+ 
+ 	/** @var string */
+ 	private $users_table;
+ 
+ 	/** @var string */
+ 	private $sessions_table;
+ 
+ 	/** @var string */
+ 	private $phpbb_root_path;
+ 
+ 	/** @var string */
+ 	private $php_ex;
+ 
+ 	/**
+ 	 * online_whois constructor.
+ 	 * @param auth $auth
+ 	 * @param driver_interface $db
+ 	 * @param helper $helper
+ 	 * @param language $language
+ 	 * @param template $template
+ 	 * @param user $user
+ 	 * @param string $users_table
+ 	 * @param string $sessions_table
+ 	 * @param string $phpbb_root_path
+ 	 * @param string $php_ex
+ 	 */
+ 	public function __construct(auth $auth, driver_interface $db, helper $helper, language $language, template $template, user $user, string $users_table, string $sessions_table, string $phpbb_root_path, string $php_ex)
+ 	{
+ 		$this->auth				= $auth;
+ 		$this->db				= $db;
+ 		$this->helper			= $helper;
+ 		$this->language			= $language;
+ 		$this->template			= $template;
+ 		$this->user				= $user;
+ 		$this->users_table		= $users_table;
+ 		$this->sessions_table	= $sessions_table;
+ 		$this->phpbb_root_path	= $phpbb_root_path;
+ 		$this->php_ex			= $php_ex;
+ 	}
+ 
+ 	/**
+ 	 * Controller for /online/whois/{session_id} route
+ 	 *
+ 	 * @param $session_id
+ 	 * @return Response a Symfony response object
+ 	 */
+ 	public function handle($session_id): Response
+ 	{
+ 		if (!function_exists('user_ipwhois'))
+ 		{
+ 			include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ex);
+ 		}
+ 
+ 		// Load language strings
+ 		$this->language->add_lang('memberlist');
+ 
+ 		if (!$this->auth->acl_get('a_'))
+ 		{
+ 			$this->language->add_lang('acp/common');
+ 
+ 			throw new http_exception(403, 'NO_ADMIN');
+ 		}
+ 
+ 		$sql = 'SELECT u.user_id, u.username, u.user_type, s.session_ip
+ 			FROM ' . $this->users_table . ' u, ' . $this->sessions_table . " s
+ 			WHERE s.session_id = '" . $this->db->sql_escape($session_id) . "'
+ 				AND	u.user_id = s.session_user_id";
+ 		$result = $this->db->sql_query($sql);
+ 
+ 		if ($row = $this->db->sql_fetchrow($result))
+ 		{
+ 			$this->template->assign_var('WHOIS', user_ipwhois($row['session_ip']));
+ 		}
+ 		$this->db->sql_freeresult($result);
+ 
+ 		// Render
+ 		return $this->helper->render('viewonline_whois.html', $this->language->lang('WHO_IS_ONLINE'));
+ 	}
+ }
diff -x images -crNEB release-4.0.0-a1/phpbb/members/controller/team.php release-4.0.0-a2-dev/phpbb/members/controller/team.php
*** release-4.0.0-a1/phpbb/members/controller/team.php	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/phpbb/members/controller/team.php	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,439 ----
+ <?php
+ /**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+ 
+ namespace phpbb\members\controller;
+ 
+ use phpbb\auth\auth;
+ use phpbb\config\config;
+ use phpbb\db\driver\driver_interface;
+ use phpbb\event\dispatcher;
+ use phpbb\exception\http_exception;
+ use phpbb\group\helper as group_helper;
+ use phpbb\controller\helper;
+ use phpbb\language\language;
+ use phpbb\template\template;
+ use phpbb\user;
+ use Symfony\Component\HttpFoundation\Response;
+ 
+ class team
+ {
+ 	/**
+ 	 * @var auth
+ 	 */
+ 	protected $auth;
+ 
+ 	/**
+ 	 * @var config
+ 	 */
+ 	protected $config;
+ 
+ 	/**
+ 	 * @var driver_interface
+ 	 */
+ 	protected $db;
+ 
+ 	/**
+ 	 * @var dispatcher
+ 	 */
+ 	protected $dispatcher;
+ 
+ 	/**
+ 	 * @var group_helper
+ 	 */
+ 	protected $group_helper;
+ 
+ 	/**
+ 	 * @var helper
+ 	 */
+ 	protected $helper;
+ 
+ 	/**
+ 	 * @var language
+ 	 */
+ 	protected $language;
+ 
+ 	/**
+ 	 * @var template
+ 	 */
+ 	protected $template;
+ 
+ 	/**
+ 	 * @var user
+ 	 */
+ 	protected $user;
+ 
+ 	/**
+ 	 * @var string
+ 	 */
+ 	protected $phpbb_root_path;
+ 
+ 	/**
+ 	 * @var string
+ 	 */
+ 	protected $php_ext;
+ 
+ 	/**
+ 	 * @var string
+ 	 */
+ 	protected $forums_table;
+ 
+ 	/**
+ 	 * @var string
+ 	 */
+ 	protected $groups_table;
+ 
+ 	/**
+ 	 * @var string
+ 	 */
+ 	protected $teampage_table;
+ 
+ 	/**
+ 	 * @var string
+ 	 */
+ 	protected $user_group_table;
+ 
+ 	/**
+ 	 * @var string
+ 	 */
+ 	protected $users_table;
+ 
+ 
+ 	/**
+ 	 * Constructor
+ 	 *
+ 	 * @param auth             $auth           Authentication service
+ 	 * @param config           $config         Configuration
+ 	 * @param driver_interface $db             Database driver
+ 	 * @param dispatcher       $dispatcher     Event dispatcher
+ 	 * @param group_helper     $group_helper   Group helper
+ 	 * @param helper           $helper         Controller helper
+ 	 * @param language         $language       Language service
+ 	 * @param template         $template       Template service
+ 	 * @param user             $user           User object
+ 	 * @param string           $phpbb_root_path Path to phpBB root
+ 	 * @param string           $phpEx          PHP file extension
+ 	 * @param string           $forums_table   Table name for forums
+ 	 * @param string           $groups_table   Table name for groups
+ 	 * @param string           $teampage_table Table name for teampage
+ 	 * @param string           $user_group_table Table name for user_group
+ 	 * @param string           $users_table    Table name for users
+ 	 */
+ 	public function __construct(auth $auth, config $config, driver_interface $db, dispatcher $dispatcher, group_helper $group_helper, helper $helper, language $language, template $template, user $user, string $phpbb_root_path, string $phpEx, string $forums_table, string $groups_table, string $teampage_table, string $user_group_table, string $users_table)
+ 	{
+ 		$this->auth				= $auth;
+ 		$this->config			= $config;
+ 		$this->db				= $db;
+ 		$this->dispatcher		= $dispatcher;
+ 		$this->group_helper		= $group_helper;
+ 		$this->helper			= $helper;
+ 		$this->language			= $language;
+ 		$this->template			= $template;
+ 		$this->user				= $user;
+ 		$this->phpbb_root_path	= $phpbb_root_path;
+ 		$this->php_ext			= $phpEx;
+ 		$this->forums_table	= $forums_table;
+ 		$this->groups_table	= $groups_table;
+ 		$this->teampage_table	= $teampage_table;
+ 		$this->user_group_table	= $user_group_table;
+ 		$this->users_table	= $users_table;
+ 	}
+ 
+ 	/**
+ 	 * Controller for /team route
+ 	 *
+ 	 * @return Response a Symfony response object
+ 	 */
+ 	public function handle() : Response
+ 	{
+ 		// Display a listing of board admins, moderators
+ 		if (!function_exists('user_get_id_name'))
+ 		{
+ 			include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext);
+ 		}
+ 		if (!function_exists('phpbb_get_user_rank'))
+ 		{
+ 			include($this->phpbb_root_path . 'includes/functions_display.' . $this->php_ext);
+ 		}
+ 
+ 		// Load language strings
+ 		$this->language->add_lang('memberlist');
+ 
+ 		// Can this user view profiles/memberlist?
+ 		if (!$this->auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel'))
+ 		{
+ 			if ($this->user->data['user_id'] != ANONYMOUS)
+ 			{
+ 				throw new http_exception(403, 'NO_VIEW_USERS');
+ 			}
+ 
+ 			login_box('', $this->language->lang('LOGIN_EXPLAIN_TEAM'));
+ 		}
+ 
+ 		$sql = 'SELECT *
+ 			FROM ' . $this->teampage_table . '
+ 			ORDER BY teampage_position ASC';
+ 		$result = $this->db->sql_query($sql, 3600);
+ 		$teampage_data = $this->db->sql_fetchrowset($result);
+ 		$this->db->sql_freeresult($result);
+ 
+ 		$sql_ary = [
+ 			'SELECT'	=> 'g.group_id, g.group_name, g.group_colour, g.group_type, ug.user_id as ug_user_id, t.teampage_id',
+ 
+ 			'FROM'		=> [$this->groups_table => 'g'],
+ 
+ 			'LEFT_JOIN'	=> [
+ 				[
+ 					'FROM'	=> [$this->teampage_table => 't'],
+ 					'ON'	=> 't.group_id = g.group_id',
+ 				],
+ 				[
+ 					'FROM'	=> [$this->user_group_table => 'ug'],
+ 					'ON'	=> 'ug.group_id = g.group_id AND ug.user_pending = 0 AND ug.user_id = ' . (int) $this->user->data['user_id'],
+ 				],
+ 			],
+ 		];
+ 
+ 		$result = $this->db->sql_query($this->db->sql_build_query('SELECT', $sql_ary));
+ 
+ 		$group_ids = $groups_ary = [];
+ 		while ($row = $this->db->sql_fetchrow($result))
+ 		{
+ 			if ($row['group_type'] == GROUP_HIDDEN && !$this->auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel') && $row['ug_user_id'] != $this->user->data['user_id'])
+ 			{
+ 				$row['group_name'] = $this->language->lang('GROUP_UNDISCLOSED');
+ 				$row['u_group'] = '';
+ 			}
+ 			else
+ 			{
+ 				$row['group_name'] = $this->group_helper->get_name($row['group_name']);
+ 				$row['u_group'] = append_sid("{$this->phpbb_root_path}memberlist.{$this->php_ext}", 'mode=group&amp;g=' . $row['group_id']);
+ 			}
+ 
+ 			if ($row['teampage_id'])
+ 			{
+ 				// Only put groups into the array we want to display.
+ 				// We are fetching all groups, to ensure we got all data for default groups.
+ 				$group_ids[] = (int) $row['group_id'];
+ 			}
+ 			$groups_ary[(int) $row['group_id']] = $row;
+ 		}
+ 		$this->db->sql_freeresult($result);
+ 
+ 		$sql_ary = [
+ 			'SELECT'	=> 'u.user_id, u.group_id as default_group, u.username, u.username_clean, u.user_colour, u.user_type, u.user_rank, u.user_posts, u.user_allow_pm, g.group_id',
+ 
+ 			'FROM'		=> [
+ 				$this->user_group_table => 'ug',
+ 			],
+ 
+ 			'LEFT_JOIN'	=> [
+ 				[
+ 					'FROM'	=> [$this->users_table => 'u'],
+ 					'ON'	=> 'ug.user_id = u.user_id',
+ 				],
+ 				[
+ 					'FROM'	=> [$this->groups_table => 'g'],
+ 					'ON'	=> 'ug.group_id = g.group_id',
+ 				],
+ 			],
+ 
+ 			'WHERE'		=> $this->db->sql_in_set('g.group_id', $group_ids, false, true) . ' AND ug.user_pending = 0',
+ 
+ 			'ORDER_BY'	=> 'u.username_clean ASC',
+ 		];
+ 
+ 		/**
+ 		 * Modify the query used to get the users for the team page
+ 		 *
+ 		 * @event core.memberlist_team_modify_query
+ 		 * @var array	sql_ary			Array containing the query
+ 		 * @var array	group_ids		Array of group ids
+ 		 * @var array	teampage_data	The teampage data
+ 		 * @since 3.1.3-RC1
+ 		 */
+ 		$vars = ['sql_ary', 'group_ids', 'teampage_data'];
+ 		extract($this->dispatcher->trigger_event('core.memberlist_team_modify_query', compact($vars)));
+ 
+ 		$result = $this->db->sql_query($this->db->sql_build_query('SELECT', $sql_ary));
+ 
+ 		$user_ary = $user_ids = $group_users = [];
+ 		while ($row = $this->db->sql_fetchrow($result))
+ 		{
+ 			$row['forums'] = '';
+ 			$row['forums_ary'] = [];
+ 			$user_ary[(int) $row['user_id']] = $row;
+ 			$user_ids[] = (int) $row['user_id'];
+ 			$group_users[(int) $row['group_id']][] = (int) $row['user_id'];
+ 		}
+ 		$this->db->sql_freeresult($result);
+ 
+ 		$user_ids = array_unique($user_ids);
+ 
+ 		if (!empty($user_ids) && $this->config['teampage_forums'])
+ 		{
+ 			$this->template->assign_var('S_DISPLAY_MODERATOR_FORUMS', true);
+ 			// Get all moderators
+ 			$perm_ary = $this->auth->acl_get_list($user_ids, ['m_'], false);
+ 
+ 			foreach ($perm_ary as $forum_id => $forum_ary)
+ 			{
+ 				foreach ($forum_ary as $id_ary)
+ 				{
+ 					foreach ($id_ary as $id)
+ 					{
+ 						if (!$forum_id)
+ 						{
+ 							$user_ary[$id]['forums'] = $this->language->lang('ALL_FORUMS');
+ 						}
+ 						else
+ 						{
+ 							$user_ary[$id]['forums_ary'][] = $forum_id;
+ 						}
+ 					}
+ 				}
+ 			}
+ 
+ 			$sql = 'SELECT forum_id, forum_name
+ 				FROM ' . $this->forums_table;
+ 			$result = $this->db->sql_query($sql);
+ 
+ 			$forums = [];
+ 			while ($row = $this->db->sql_fetchrow($result))
+ 			{
+ 				$forums[$row['forum_id']] = $row['forum_name'];
+ 			}
+ 			$this->db->sql_freeresult($result);
+ 
+ 			foreach ($user_ary as $user_id => $user_data)
+ 			{
+ 				if (!$user_data['forums'])
+ 				{
+ 					foreach ($user_data['forums_ary'] as $forum_id)
+ 					{
+ 						$user_ary[$user_id]['forums_options'] = true;
+ 						if (isset($forums[$forum_id]))
+ 						{
+ 							if ($this->auth->acl_get('f_list', $forum_id))
+ 							{
+ 								$user_ary[$user_id]['forums'] .= '<option value="">' . $forums[$forum_id] . '</option>';
+ 							}
+ 						}
+ 					}
+ 				}
+ 			}
+ 		}
+ 
+ 		foreach ($teampage_data as $team_data)
+ 		{
+ 			// If this team entry has no group, it's a category
+ 			if (!$team_data['group_id'])
+ 			{
+ 				$this->template->assign_block_vars('group', [
+ 					'GROUP_NAME'  => $team_data['teampage_name'],
+ 				]);
+ 
+ 				continue;
+ 			}
+ 
+ 			$group_data = $groups_ary[(int) $team_data['group_id']];
+ 			$group_id = (int) $team_data['group_id'];
+ 
+ 			if (!$team_data['teampage_parent'])
+ 			{
+ 				// If the group does not have a parent category, we display the groupname as category
+ 				$this->template->assign_block_vars('group', [
+ 					'GROUP_NAME'	=> $group_data['group_name'],
+ 					'GROUP_COLOR'	=> $group_data['group_colour'],
+ 					'U_GROUP'		=> $group_data['u_group'],
+ 				]);
+ 			}
+ 
+ 			// Display group members.
+ 			if (!empty($group_users[$group_id]))
+ 			{
+ 				foreach ($group_users[$group_id] as $user_id)
+ 				{
+ 					if (isset($user_ary[$user_id]))
+ 					{
+ 						$row = $user_ary[$user_id];
+ 						if ($this->config['teampage_memberships'] == 1 && ($group_id != $groups_ary[$row['default_group']]['group_id']) && $groups_ary[$row['default_group']]['teampage_id'])
+ 						{
+ 							// Display users in their primary group, instead of the first group, when it is displayed on the teampage.
+ 							continue;
+ 						}
+ 
+ 						$user_rank_data = phpbb_get_user_rank($row, (($row['user_id'] == ANONYMOUS) ? false : $row['user_posts']));
+ 
+ 						$template_vars = [
+ 							'USER_ID'		=> $row['user_id'],
+ 							'FORUMS'		=> $row['forums'],
+ 							'FORUM_OPTIONS'	=> (isset($row['forums_options'])) ? true : false,
+ 							'RANK_TITLE'	=> $user_rank_data['title'],
+ 
+ 							'GROUP_NAME'	=> $groups_ary[$row['default_group']]['group_name'],
+ 							'GROUP_COLOR'	=> $groups_ary[$row['default_group']]['group_colour'],
+ 							'U_GROUP'		=> $groups_ary[$row['default_group']]['u_group'],
+ 
+ 							'RANK_IMG'		=> $user_rank_data['img'],
+ 							'RANK_IMG_SRC'	=> $user_rank_data['img_src'],
+ 
+ 							'S_INACTIVE'	=> $row['user_type'] == USER_INACTIVE,
+ 
+ 							'U_PM'			=> ($this->config['allow_privmsg'] && $this->auth->acl_get('u_sendpm') && ($row['user_allow_pm'] || $this->auth->acl_gets('a_', 'm_') || $this->auth->acl_getf_global('m_'))) ? append_sid("{$this->phpbb_root_path}ucp.{$this->php_ext}", 'i=pm&amp;mode=compose&amp;u=' . $row['user_id']) : '',
+ 
+ 							'USERNAME_FULL'		=> get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']),
+ 							'USERNAME'			=> get_username_string('username', $row['user_id'], $row['username'], $row['user_colour']),
+ 							'USER_COLOR'		=> get_username_string('colour', $row['user_id'], $row['username'], $row['user_colour']),
+ 							'U_VIEW_PROFILE'	=> get_username_string('profile', $row['user_id'], $row['username'], $row['user_colour']),
+ 						];
+ 
+ 						/**
+ 						 * Modify the template vars for displaying the user in the groups on the teampage
+ 						 *
+ 						 * @event core.memberlist_team_modify_template_vars
+ 						 * @var array	template_vars		Array containing the query
+ 						 * @var array	row					Array containing the action user row
+ 						 * @var array	groups_ary			Array of groups with all users that should be displayed
+ 						 * @since 3.1.3-RC1
+ 						 */
+ 						$vars = ['template_vars', 'row', 'groups_ary'];
+ 						extract($this->dispatcher->trigger_event('core.memberlist_team_modify_template_vars', compact($vars)));
+ 
+ 						$this->template->assign_block_vars('group.user', $template_vars);
+ 
+ 						if ($this->config['teampage_memberships'] != 2)
+ 						{
+ 							unset($user_ary[$user_id]);
+ 						}
+ 					}
+ 				}
+ 			}
+ 		}
+ 
+ 		$this->template->assign_vars([
+ 			'PM_IMG' => $this->user->img('icon_contact_pm', $this->language->lang('SEND_PRIVATE_MESSAGE')),
+ 		]);
+ 
+ 		// Breadcrumbs
+ 		$this->template->assign_block_vars('navlinks', [
+ 			'BREADCRUMB_NAME' => $this->language->lang('THE_TEAM'),
+ 			'U_BREADCRUMB' => $this->helper->route('phpbb_members_team'),
+ 		]);
+ 
+ 		make_jumpbox(append_sid("{$this->phpbb_root_path}viewforum.{$this->php_ext}"));
+ 
+ 		// Render
+ 		return $this->helper->render('memberlist_team.html', $this->language->lang('THE_TEAM'));
+ 	}
+ 
+ }
diff -x images -crNEB release-4.0.0-a1/phpbb/members/viewonline_helper.php release-4.0.0-a2-dev/phpbb/members/viewonline_helper.php
*** release-4.0.0-a1/phpbb/members/viewonline_helper.php	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/phpbb/members/viewonline_helper.php	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,469 ----
+ <?php
+ /**
+ *
+ * This file is part of the phpBB Forum Software package.
+ *
+ * @copyright (c) phpBB Limited <https://www.phpbb.com>
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+ 
+ namespace phpbb\members;
+ 
+ use phpbb\filesystem\helper as filesystem_helper;
+ 
+ /**
+  * Class to handle viewonline related tasks
+  */
+ class viewonline_helper
+ {
+ 	/** @var \phpbb\db\driver\driver_interface */
+ 	protected $db;
+ 
+ 	/** @var \phpbb\config\config */
+ 	protected $config;
+ 
+ 	/** @var \phpbb\event\dispatcher_interface */
+ 	protected $dispatcher;
+ 
+ 	/** @var \phpbb\routing\router */
+ 	protected $router;
+ 
+ 	/** @var \phpbb\controller\helper */
+ 	protected $helper;
+ 
+ 	/** @var \phpbb\language\language */
+ 	protected $language;
+ 
+ 	/** @var \phpbb\auth\auth */
+ 	protected $auth;
+ 
+ 	/** @var string */
+ 	protected $phpbb_root_path;
+ 
+ 	/** @var string */
+ 	protected $php_ex;
+ 
+ 	/** @var string */
+ 	protected $phpbb_adm_relative_path;
+ 
+ 	/** @var string */
+ 	protected $forums_table;
+ 
+ 	/** @var string */
+ 	protected $topics_table;
+ 
+ 	/** @var string */
+ 	protected $users_table;
+ 
+ 	/** @var string */
+ 	protected $sessions_table;
+ 
+ 	/**
+ 	 * @param \phpbb\db\driver\driver_interface $db
+ 	 * @param \phpbb\config\config $config
+ 	 * @param \phpbb\event\dispatcher_interface $dispatcher
+ 	 * @param \phpbb\routing\router $router
+ 	 * @param \phpbb\controller\helper $helper
+ 	 * @param \phpbb\language\language $language
+ 	 * @param \phpbb\auth\auth $auth
+ 	 * @param string $phpbb_root_path
+ 	 * @param string $php_ex
+ 	 * @param string $phpbb_adm_relative_path
+ 	 * @param string $users_table
+ 	 * @param string $sessions_table
+ 	 * @param string $topics_table
+ 	 * @param string $forums_table
+ 	 */
+ 	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\routing\router $router, \phpbb\controller\helper $helper, \phpbb\language\language $language, \phpbb\auth\auth $auth, string $phpbb_root_path, string $php_ex, string $phpbb_adm_relative_path, string $users_table, string $sessions_table, string $topics_table, string $forums_table)
+ 	{
+ 		$this->db = $db;
+ 		$this->config = $config;
+ 		$this->dispatcher = $dispatcher;
+ 
+ 		$this->router = $router;
+ 		$this->helper = $helper;
+ 		$this->language = $language;
+ 		$this->auth = $auth;
+ 		$this->phpbb_root_path = $phpbb_root_path;
+ 		$this->php_ex = $php_ex;
+ 		$this->phpbb_adm_relative_path = $phpbb_adm_relative_path;
+ 
+ 		$this->users_table = $users_table;
+ 		$this->sessions_table = $sessions_table;
+ 		$this->topics_table = $topics_table;
+ 		$this->forums_table = $forums_table;
+ 	}
+ 
+ 	/**
+ 	 * Get forum IDs for topics
+ 	 *
+ 	 * Retrieve forum IDs and add the data into the session data array
+ 	 * Array structure matches sql_fethrowset() result array
+ 	 *
+ 	 * @param array $session_data_rowset Users' session data array
+ 	 * @return void
+ 	 */
+ 	public function get_forum_ids(array &$session_data_rowset): void
+ 	{
+ 		$topic_ids = $match = [];
+ 		foreach ($session_data_rowset as $number => $row)
+ 		{
+ 			if ($row['session_forum_id'] == 0 && preg_match('#t=([0-9]+)#', $row['session_page'], $match))
+ 			{
+ 				$topic_ids[$number] = (int) $match[1];
+ 			}
+ 		}
+ 
+ 		if (count($topic_ids = array_unique($topic_ids)))
+ 		{
+ 			$sql_ary = [
+ 				'SELECT'	=> 't.topic_id, t.forum_id',
+ 				'FROM'		=> [
+ 					$this->topics_table => 't',
+ 				],
+ 				'WHERE'		=> $this->db->sql_in_set('t.topic_id', $topic_ids),
+ 				'ORDER_BY'	=> 't.topic_id',
+ 			];
+ 			$result = $this->db->sql_query($this->db->sql_build_query('SELECT', $sql_ary));
+ 			$forum_ids_rowset = $this->db->sql_fetchrowset($result);
+ 			$this->db->sql_freeresult($result);
+ 
+ 			foreach ($forum_ids_rowset as $forum_ids_row)
+ 			{
+ 				$session_data_row_number = array_search((int) $forum_ids_row['topic_id'], $topic_ids);
+ 				$session_data_rowset[$session_data_row_number]['session_forum_id'] = (int) $forum_ids_row['forum_id'];
+ 			}
+ 		}
+ 	}
+ 
+ 	/**
+ 	 * Get user page
+ 	 *
+ 	 * @param string $session_page User's session page
+ 	 * @return array Match array filled by preg_match()
+ 	 */
+ 	public function get_user_page($session_page): array
+ 	{
+ 		$session_page = filesystem_helper::clean_path($session_page);
+ 
+ 		if (str_starts_with($session_page, './'))
+ 		{
+ 			$session_page = substr($session_page, 2);
+ 		}
+ 
+ 		preg_match('#^((\.\./)*([a-z0-9/_-]+))#i', $session_page, $on_page);
+ 		if (empty($on_page))
+ 		{
+ 			$on_page[1] = '';
+ 		}
+ 
+ 		return $on_page;
+ 	}
+ 
+ 	/**
+ 	 * Given a certain page, it returns the title of the page and a link to it.
+ 	 * There are 2 strategies to detect the page the user is in:
+ 	 *  - Try to get the controller route from the router service
+ 	 *  - And second, if is not possible to get the controller route, the
+ 	 * page is analyzed by path, analyzing the file that is accessed. This
+ 	 * is mostly for legacy pages, and should be removed in the future.
+ 	 *
+ 	 * @param string $session_page
+ 	 * @param int $forum_id
+ 	 * @return array
+ 	 */
+ 	public function get_location(string $session_page, int $forum_id): array
+ 	{
+ 		try
+ 		{
+ 			$match = $this->router->match($session_page);
+ 
+ 			switch ($match['_route'])
+ 			{
+ 				case 'phpbb_index_controller':
+ 					$location = $this->language->lang('INDEX');
+ 					$location_url = $this->helper->route('phpbb_index_controller');
+ 					break;
+ 
+ 				case 'phpbb_help_bbcode_controller':
+ 				case 'phpbb_help_faq_controller':
+ 					$location = $this->language->lang('VIEWING_FAQ');
+ 					$location_url = $this->helper->route('phpbb_help_faq_controller');
+ 					break;
+ 
+ 				case 'phpbb_members_online':
+ 				case 'phpbb_members_online_whois':
+ 					$location = $this->language->lang('VIEWING_ONLINE');
+ 					$location_url = $this->helper->route('phpbb_members_online');
+ 					break;
+ 
+ 				case 'phpbb_members_team':
+ 					$location_url = append_sid($this->phpbb_root_path . "memberlist." . $this->php_ex);
+ 					$location = $this->language->lang('VIEWING_MEMBERS');
+ 					break;
+ 
+ 				case 'phpbb_report_pm_controller':
+ 				case 'phpbb_report_post_controller':
+ 					$location = $this->language->lang('REPORTING_POST');
+ 					$location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
+ 					break;
+ 
+ 				default:
+ 					// Is a route, but not in the switch
+ 					$location = $this->language->lang('INDEX');
+ 					$location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
+ 			}
+ 		}
+ 		catch (\RuntimeException $e) // Urls without route
+ 		{
+ 			$on_page = $this->get_user_page($session_page);
+ 
+ 			switch ($on_page[1])
+ 			{
+ 				case $this->phpbb_adm_relative_path . 'index':
+ 					$location = $this->language->lang('ACP');
+ 					$location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
+ 					break;
+ 
+ 				case 'posting':
+ 				case 'viewforum':
+ 				case 'viewtopic':
+ 
+ 					$forum_data = $this->get_forum_data();
+ 
+ 					if ($forum_id && $this->auth->acl_get('f_list', $forum_id))
+ 					{
+ 						$location = '';
+ 						$location_url = append_sid($this->phpbb_root_path . "viewforum." . $this->php_ex, 'f=' . $forum_id);
+ 
+ 						if ($forum_data[$forum_id]['forum_type'] == FORUM_LINK)
+ 						{
+ 							$location = $this->language->lang('READING_LINK', $forum_data[$forum_id]['forum_name']);
+ 							break;
+ 						}
+ 
+ 						switch ($on_page[1])
+ 						{
+ 							case 'posting':
+ 								preg_match('#mode=([a-z]+)#', $session_page, $on_page);
+ 								$posting_mode = (!empty($on_page[1])) ? $on_page[1] : '';
+ 
+ 								switch ($posting_mode)
+ 								{
+ 									case 'reply':
+ 									case 'quote':
+ 										$location = $this->language->lang('REPLYING_MESSAGE', $forum_data[$forum_id]['forum_name']);
+ 										break;
+ 
+ 									default:
+ 										$location = $this->language->lang('POSTING_MESSAGE', $forum_data[$forum_id]['forum_name']);
+ 										break;
+ 								}
+ 								break;
+ 
+ 							case 'viewtopic':
+ 								$location = $this->language->lang('READING_TOPIC', $forum_data[$forum_id]['forum_name']);
+ 								break;
+ 
+ 							case 'viewforum':
+ 								$location = $this->language->lang('READING_FORUM', $forum_data[$forum_id]['forum_name']);
+ 								break;
+ 						}
+ 					}
+ 					else
+ 					{
+ 						$location = $this->language->lang('INDEX');
+ 						$location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
+ 					}
+ 					break;
+ 
+ 				case 'search':
+ 					$location = $this->language->lang('SEARCHING_FORUMS');
+ 					$location_url = append_sid($this->phpbb_root_path . "search." . $this->php_ex);
+ 					break;
+ 
+ 				case 'memberlist':
+ 					$location_url = append_sid($this->phpbb_root_path . "memberlist." . $this->php_ex);
+ 
+ 					if (str_contains($session_page, 'mode=viewprofile'))
+ 					{
+ 						$location = $this->language->lang('VIEWING_MEMBER_PROFILE');
+ 					}
+ 					else if (str_contains($session_page, 'mode=contactadmin'))
+ 					{
+ 						$location = $this->language->lang('VIEWING_CONTACT_ADMIN');
+ 						$location_url = append_sid($this->phpbb_root_path . "memberlist." . $this->php_ex, 'mode=contactadmin');
+ 					}
+ 					else
+ 					{
+ 						$location = $this->language->lang('VIEWING_MEMBERS');
+ 					}
+ 					break;
+ 
+ 				case 'mcp':
+ 					$location = $this->language->lang('VIEWING_MCP');
+ 					$location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
+ 					break;
+ 
+ 				case 'ucp':
+ 					$location = $this->language->lang('VIEWING_UCP');
+ 
+ 					// Grab some common modules
+ 					$url_params = [
+ 						'mode=register'		=> 'VIEWING_REGISTER',
+ 						'i=pm&mode=compose'	=> 'POSTING_PRIVATE_MESSAGE',
+ 						'i=pm&'				=> 'VIEWING_PRIVATE_MESSAGES',
+ 						'i=profile&'		=> 'CHANGING_PROFILE',
+ 						'i=prefs&'			=> 'CHANGING_PREFERENCES',
+ 					];
+ 
+ 					foreach ($url_params as $param => $lang)
+ 					{
+ 						if (strpos($session_page, $param) !== false)
+ 						{
+ 							$location = $this->language->lang($lang);
+ 							break;
+ 						}
+ 					}
+ 
+ 					$location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
+ 					break;
+ 
+ 				default:
+ 					$location = $this->language->lang('INDEX');
+ 					$location_url = append_sid($this->phpbb_root_path . "index." . $this->php_ex);
+ 					break;
+ 			}
+ 		}
+ 
+ 		return [$location, $location_url];
+ 	}
+ 
+ 	/**
+ 	 * Get number of guests online
+ 	 *
+ 	 * @return int
+ 	 */
+ 	public function get_number_guests(): int
+ 	{
+ 		switch ($this->db->get_sql_layer())
+ 		{
+ 			case 'sqlite3':
+ 				$sql = 'SELECT COUNT(session_ip) as num_guests
+ 					FROM (
+ 						SELECT DISTINCT session_ip
+ 							FROM ' . $this->sessions_table . '
+ 							WHERE session_user_id = ' . ANONYMOUS . '
+ 								AND session_time >= ' . (time() - ($this->config['load_online_time'] * 60)) .
+ 					')';
+ 				break;
+ 
+ 			default:
+ 				$sql = 'SELECT COUNT(DISTINCT session_ip) as num_guests
+ 					FROM ' . $this->sessions_table . '
+ 					WHERE session_user_id = ' . ANONYMOUS . '
+ 						AND session_time >= ' . (time() - ($this->config['load_online_time'] * 60));
+ 				break;
+ 		}
+ 		$result = $this->db->sql_query($sql);
+ 		$guest_counter = (int) $this->db->sql_fetchfield('num_guests');
+ 		$this->db->sql_freeresult($result);
+ 
+ 		return $guest_counter;
+ 	}
+ 
+ 	/**
+ 	 * Get forum data
+ 	 *
+ 	 * @return array
+ 	 */
+ 	public function get_forum_data(): array
+ 	{
+ 		static $forum_data;
+ 
+ 		if (isset($forum_data))
+ 		{
+ 			return $forum_data;
+ 		}
+ 
+ 		// Forum info
+ 		$sql_ary = [
+ 			'SELECT'	=> 'f.forum_id, f.forum_name, f.parent_id, f.forum_type, f.left_id, f.right_id',
+ 			'FROM'		=> [
+ 				$this->forums_table    => 'f',
+ 			],
+ 			'ORDER_BY'	=> 'f.left_id ASC',
+ 		];
+ 
+ 		/**
+ 		 * Modify the forum data SQL query for getting additional fields if needed
+ 		 *
+ 		 * @event core.viewonline_modify_forum_data_sql
+ 		 * @var	array	sql_ary			The SQL array
+ 		 * @since 3.1.5-RC1
+ 		 */
+ 		$vars = ['sql_ary'];
+ 		extract($this->dispatcher->trigger_event('core.viewonline_modify_forum_data_sql', compact($vars)));
+ 
+ 		$result = $this->db->sql_query($this->db->sql_build_query('SELECT', $sql_ary), 600);
+ 		unset($sql_ary);
+ 
+ 		$forum_data = [];
+ 		while ($row = $this->db->sql_fetchrow($result))
+ 		{
+ 			$forum_data[$row['forum_id']] = $row;
+ 		}
+ 		$this->db->sql_freeresult($result);
+ 
+ 		return $forum_data;
+ 	}
+ 
+ 	/**
+ 	 * Build and execute the sessions query and return the rowset.
+ 	 *
+ 	 * @param bool $show_guests
+ 	 * @param string $order_by
+ 	 * @param int &$guest_counter
+ 	 * @return array
+ 	 */
+ 	public function get_session_data_rowset(bool &$show_guests, string $order_by, int &$guest_counter): array
+ 	{
+ 		$forum_data = $this->get_forum_data();
+ 
+ 		$sql_ary = [
+ 			'SELECT'    => 'u.user_id, u.username, u.username_clean, u.user_type, u.user_colour, s.session_id, s.session_time, s.session_page, s.session_ip, s.session_browser, s.session_viewonline, s.session_forum_id',
+ 			'FROM'      => [
+ 				$this->users_table     => 'u',
+ 				$this->sessions_table  => 's',
+ 			],
+ 			'WHERE'     => 'u.user_id = s.session_user_id
+ 				AND s.session_time >= ' . (time() - ($this->config['load_online_time'] * 60)) .
+ 				(($show_guests) ? '' : ' AND s.session_user_id <> ' . ANONYMOUS),
+ 			'ORDER_BY'  => $order_by,
+ 		];
+ 
+ 		/**
+ 		 * Modify the SQL query for getting the user data to display viewonline list
+ 		 *
+ 		 * @event core.viewonline_modify_sql
+ 		 * @var	array	sql_ary			The SQL array
+ 		 * @var	bool	show_guests		Do we display guests in the list
+ 		 * @var	int		guest_counter	Number of guests displayed
+ 		 * @var	array	forum_data		Array with forum data
+ 		 * @since 3.1.0-a1
+ 		 * @changed 3.1.0-a2 Added vars guest_counter and forum_data
+ 		 */
+ 		$vars = ['sql_ary', 'show_guests', 'guest_counter', 'forum_data'];
+ 		extract($this->dispatcher->trigger_event('core.viewonline_modify_sql', compact($vars)));
+ 
+ 		$result = $this->db->sql_query($this->db->sql_build_query('SELECT', $sql_ary));
+ 		$session_data_rowset = $this->db->sql_fetchrowset($result);
+ 		$this->db->sql_freeresult($result);
+ 
+ 		return $session_data_rowset;
+ 	}
+ }
diff -x images -crNEB release-4.0.0-a1/phpbb/mention/controller/mention.php release-4.0.0-a2-dev/phpbb/mention/controller/mention.php
*** release-4.0.0-a1/phpbb/mention/controller/mention.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/mention/controller/mention.php	Wed Apr  1 17:47:36 2026
***************
*** 13,18 ****
--- 13,19 ----
  
  namespace phpbb\mention\controller;
  
+ use phpbb\controller\helper;
  use phpbb\di\service_collection;
  use phpbb\request\request_interface;
  use Symfony\Component\HttpFoundation\JsonResponse;
***************
*** 26,31 ****
--- 27,35 ----
  	/** @var  request_interface */
  	protected $request;
  
+ 	/** @var helper */
+ 	protected $helper;
+ 
  	/** @var string */
  	protected $phpbb_root_path;
  
***************
*** 37,49 ****
  	 *
  	 * @param service_collection|array $mention_sources
  	 * @param request_interface $request
  	 * @param string $phpbb_root_path
  	 * @param string $phpEx
  	 */
! 	public function __construct($mention_sources, request_interface $request, string $phpbb_root_path, string $phpEx)
  	{
  		$this->mention_sources = $mention_sources;
  		$this->request = $request;
  		$this->phpbb_root_path = $phpbb_root_path;
  		$this->php_ext = $phpEx;
  	}
--- 41,55 ----
  	 *
  	 * @param service_collection|array $mention_sources
  	 * @param request_interface $request
+ 	 * @param helper $helper
  	 * @param string $phpbb_root_path
  	 * @param string $phpEx
  	 */
! 	public function __construct($mention_sources, request_interface $request, helper $helper, string $phpbb_root_path, string $phpEx)
  	{
  		$this->mention_sources = $mention_sources;
  		$this->request = $request;
+ 		$this->helper = $helper;
  		$this->phpbb_root_path = $phpbb_root_path;
  		$this->php_ext = $phpEx;
  	}
***************
*** 57,63 ****
  	{
  		if (!$this->request->is_ajax())
  		{
! 			return new RedirectResponse(append_sid($this->phpbb_root_path . 'index.' . $this->php_ext));
  		}
  
  		$keyword = $this->request->variable('keyword', '', true);
--- 63,69 ----
  	{
  		if (!$this->request->is_ajax())
  		{
! 			return new RedirectResponse($this->helper->route('phpbb_index_controller'));
  		}
  
  		$keyword = $this->request->variable('keyword', '', true);
diff -x images -crNEB release-4.0.0-a1/phpbb/message/admin_form.php release-4.0.0-a2-dev/phpbb/message/admin_form.php
*** release-4.0.0-a1/phpbb/message/admin_form.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/message/admin_form.php	Wed Apr  1 17:47:36 2026
***************
*** 39,52 ****
  	* @param \phpbb\config\config $config
  	* @param \phpbb\config\db_text $config_text
  	* @param \phpbb\db\driver\driver_interface $db
  	* @param \phpbb\user $user
  	* @param \phpbb\event\dispatcher_interface $dispatcher
  	* @param string $phpbb_root_path
  	* @param string $phpEx
  	*/
! 	public function __construct(\phpbb\auth\auth $auth, \phpbb\config\config $config, \phpbb\config\db_text $config_text, \phpbb\db\driver\driver_interface $db, \phpbb\user $user, \phpbb\event\dispatcher_interface $dispatcher, $phpbb_root_path, $phpEx)
  	{
! 		parent::__construct($auth, $config, $db, $user, $phpbb_root_path, $phpEx);
  		$this->config_text = $config_text;
  		$this->dispatcher = $dispatcher;
  	}
--- 39,53 ----
  	* @param \phpbb\config\config $config
  	* @param \phpbb\config\db_text $config_text
  	* @param \phpbb\db\driver\driver_interface $db
+ 	* @param \phpbb\controller\helper $controller_helper
  	* @param \phpbb\user $user
  	* @param \phpbb\event\dispatcher_interface $dispatcher
  	* @param string $phpbb_root_path
  	* @param string $phpEx
  	*/
! 	public function __construct(\phpbb\auth\auth $auth, \phpbb\config\config $config, \phpbb\config\db_text $config_text, \phpbb\db\driver\driver_interface $db, \phpbb\controller\helper $controller_helper, \phpbb\user $user, \phpbb\event\dispatcher_interface $dispatcher, $phpbb_root_path, $phpEx)
  	{
! 		parent::__construct($auth, $config, $db, $controller_helper, $user, $phpbb_root_path, $phpEx);
  		$this->config_text = $config_text;
  		$this->dispatcher = $dispatcher;
  	}
diff -x images -crNEB release-4.0.0-a1/phpbb/message/form.php release-4.0.0-a2-dev/phpbb/message/form.php
*** release-4.0.0-a1/phpbb/message/form.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/message/form.php	Wed Apr  1 17:47:36 2026
***************
*** 22,27 ****
--- 22,29 ----
  	protected $auth;
  	/** @var \phpbb\config\config */
  	protected $config;
+ 	/** @var \phpbb\controller\helper */
+ 	protected $controller_helper;
  	/** @var \phpbb\db\driver\driver_interface */
  	protected $db;
  	/** @var \phpbb\message\message */
***************
*** 47,63 ****
  	* @param \phpbb\auth\auth $auth
  	* @param \phpbb\config\config $config
  	* @param \phpbb\db\driver\driver_interface $db
  	* @param \phpbb\user $user
  	* @param string $phpbb_root_path
  	* @param string $phpEx
  	*/
! 	public function __construct(\phpbb\auth\auth $auth, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\user $user, $phpbb_root_path, $phpEx)
  	{
  		$this->phpbb_root_path = $phpbb_root_path;
  		$this->phpEx = $phpEx;
  		$this->user = $user;
  		$this->auth = $auth;
  		$this->config = $config;
  		$this->db = $db;
  
  		$this->message = new message($config['server_name']);
--- 49,67 ----
  	* @param \phpbb\auth\auth $auth
  	* @param \phpbb\config\config $config
  	* @param \phpbb\db\driver\driver_interface $db
+ 	* @param \phpbb\controller\helper $controller_helper
  	* @param \phpbb\user $user
  	* @param string $phpbb_root_path
  	* @param string $phpEx
  	*/
! 	public function __construct(\phpbb\auth\auth $auth, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\controller\helper $controller_helper, \phpbb\user $user, $phpbb_root_path, $phpEx)
  	{
  		$this->phpbb_root_path = $phpbb_root_path;
  		$this->phpEx = $phpEx;
  		$this->user = $user;
  		$this->auth = $auth;
  		$this->config = $config;
+ 		$this->controller_helper = $controller_helper;
  		$this->db = $db;
  
  		$this->message = new message($config['server_name']);
***************
*** 111,117 ****
  	*/
  	public function get_return_message()
  	{
! 		return sprintf($this->user->lang['RETURN_INDEX'], '<a href="' . append_sid($this->phpbb_root_path . 'index.' . $this->phpEx) . '">', '</a>');
  	}
  
  	/**
--- 115,121 ----
  	*/
  	public function get_return_message()
  	{
! 		return sprintf($this->user->lang['RETURN_INDEX'], '<a href="' . $this->controller_helper->route('phpbb_index_controller') . '">', '</a>');
  	}
  
  	/**
***************
*** 153,159 ****
  
  			$this->message->send($messenger, phpbb_get_board_contact($this->config, $this->phpEx));
  
! 			meta_refresh(3, append_sid($this->phpbb_root_path . 'index.' . $this->phpEx));
  			trigger_error($this->user->lang['EMAIL_SENT'] . '<br /><br />' . $this->get_return_message());
  		}
  	}
--- 157,163 ----
  
  			$this->message->send($messenger, phpbb_get_board_contact($this->config, $this->phpEx));
  
! 			meta_refresh(3, $this->controller_helper->route('phpbb_index_controller'));
  			trigger_error($this->user->lang['EMAIL_SENT'] . '<br /><br />' . $this->get_return_message());
  		}
  	}
diff -x images -crNEB release-4.0.0-a1/phpbb/notification/controller/mark_read.php release-4.0.0-a2-dev/phpbb/notification/controller/mark_read.php
*** release-4.0.0-a1/phpbb/notification/controller/mark_read.php	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/phpbb/notification/controller/mark_read.php	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,137 ----
+ <?php
+ /**
+  *
+  * This file is part of the phpBB Forum Software package.
+  *
+  * @copyright (c) phpBB Limited <https://www.phpbb.com>
+  * @license GNU General Public License, version 2 (GPL-2.0)
+  *
+  * For full copyright and license information, please see
+  * the docs/CREDITS.txt file.
+  *
+  */
+ 
+ namespace phpbb\notification\controller;
+ 
+ use phpbb\event\dispatcher;
+ use phpbb\exception\http_exception;
+ use phpbb\language\language;
+ use phpbb\notification\manager;
+ use phpbb\notification\type\type_interface;
+ use phpbb\request\request;
+ use phpbb\user;
+ use Symfony\Component\HttpFoundation\JsonResponse;
+ use Symfony\Component\HttpFoundation\RedirectResponse;
+ use Symfony\Component\HttpFoundation\Response;
+ 
+ class mark_read
+ {
+ 	/** @var dispatcher  */
+ 	protected $dispatcher;
+ 
+ 	/** @var language */
+ 	protected $language;
+ 
+ 	/** @var manager */
+ 	protected $manager;
+ 
+ 	/** @var request */
+ 	protected $request;
+ 
+ 	/** @var user */
+ 	protected $user;
+ 
+ 	/** @var string */
+ 	protected $phpbb_root_path;
+ 
+ 	/**
+ 	 * Constructor
+ 	 *
+ 	 * @param dispatcher	$dispatcher		Event dispatcher
+ 	 * @param language		$language		Language object
+ 	 * @param manager		$manager		Notification manager
+ 	 * @param request		$request		Request object
+ 	 * @param user			$user			User object
+ 	 * @param string		$phpbb_root_path phpBB root path
+ 	 */
+ 	public function __construct(dispatcher $dispatcher, language $language, manager $manager, request $request, user $user, string $phpbb_root_path)
+ 	{
+ 		$this->dispatcher = $dispatcher;
+ 		$this->language = $language;
+ 		$this->manager = $manager;
+ 		$this->request = $request;
+ 		$this->user = $user;
+ 		$this->phpbb_root_path = $phpbb_root_path;
+ 	}
+ 
+ 	/**
+ 	 * Mark notification as read
+ 	 *
+ 	 * @param int $id Notification ID
+ 	 *
+ 	 * @return Response
+ 	 *
+ 	 * @throws http_exception
+ 	 *
+ 	 * @psalm-suppress InvalidArgument
+ 	 */
+ 	public function handle(int $id): Response
+ 	{
+ 		$mark_notification = $id;
+ 
+ 		if ($this->user->data['user_id'] == ANONYMOUS)
+ 		{
+ 			if ($this->request->is_ajax())
+ 			{
+ 				throw new http_exception(403, 'LOGIN_REQUIRED');
+ 			}
+ 			login_box('', $this->language->lang('LOGIN_REQUIRED'));
+ 		}
+ 
+ 		if (!check_link_hash($this->request->variable('hash', ''), 'mark_notification_read'))
+ 		{
+ 			// Link hash error
+ 			throw new http_exception(403, 'NOT_AUTHORISED');
+ 		}
+ 
+ 		$notification = $this->manager->load_notifications('notification.method.board', [
+ 			'notification_id'	=> $mark_notification,
+ 		]);
+ 
+ 		if (!isset($notification['notifications'][$mark_notification]))
+ 		{
+ 			// Notification id not found
+ 			throw new http_exception(404, 'PAGE_NOT_FOUND');
+ 		}
+ 
+ 		$notification = $notification['notifications'][$mark_notification];
+ 
+ 		$notification->mark_read();
+ 
+ 		/**
+ 		 * You can use this event to perform additional tasks or redirect user elsewhere.
+ 		 *
+ 		 * @event core.index_mark_notification_after
+ 		 * @var		int				mark_notification	Notification ID
+ 		 * @var		type_interface	notification		Notification instance
+ 		 * @since 3.2.6-RC1
+ 		 */
+ 		$vars = ['mark_notification', 'notification'];
+ 		extract($this->dispatcher->trigger_event('core.index_mark_notification_after', compact($vars)));
+ 
+ 		if ($this->request->is_ajax())
+ 		{
+ 			$data = [
+ 				'success'	=> true,
+ 			];
+ 			return new JsonResponse($data);
+ 		}
+ 
+ 		if (($redirect = $this->request->variable('redirect', '')))
+ 		{
+ 			return new RedirectResponse(append_sid($this->phpbb_root_path . $redirect));
+ 		}
+ 
+ 		return new RedirectResponse($notification->get_redirect_url());
+ 	}
+ }
diff -x images -crNEB release-4.0.0-a1/phpbb/notification/type/base.php release-4.0.0-a2-dev/phpbb/notification/type/base.php
*** release-4.0.0-a1/phpbb/notification/type/base.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/notification/type/base.php	Wed Apr  1 17:47:36 2026
***************
*** 13,18 ****
--- 13,25 ----
  
  namespace phpbb\notification\type;
  
+ use phpbb\auth\auth;
+ use phpbb\avatar\helper as avatar_helper;
+ use phpbb\controller\helper;
+ use phpbb\db\driver\driver_interface;
+ use phpbb\language\language;
+ use phpbb\user;
+ 
  /**
  * Base notifications class
  */
***************
*** 21,36 ****
  	/** @var \phpbb\notification\manager */
  	protected $notification_manager;
  
! 	/** @var \phpbb\db\driver\driver_interface */
  	protected $db;
  
! 	/** @var \phpbb\language\language */
  	protected $language;
  
! 	/** @var \phpbb\user */
  	protected $user;
  
! 	/** @var \phpbb\auth\auth */
  	protected $auth;
  
  	/** @var string */
--- 28,49 ----
  	/** @var \phpbb\notification\manager */
  	protected $notification_manager;
  
! 	/** @var avatar_helper */
! 	protected $avatar_helper;
! 
! 	/** @var helper|null */
! 	protected $controller_helper;
! 
! 	/** @var driver_interface */
  	protected $db;
  
! 	/** @var language */
  	protected $language;
  
! 	/** @var user */
  	protected $user;
  
! 	/** @var auth */
  	protected $auth;
  
  	/** @var string */
***************
*** 76,91 ****
  	/**
  	 * Notification Type Base Constructor
  	 *
! 	 * @param \phpbb\db\driver\driver_interface $db
! 	 * @param \phpbb\language\language          $language
! 	 * @param \phpbb\user                       $user
! 	 * @param \phpbb\auth\auth                  $auth
! 	 * @param string                            $phpbb_root_path
! 	 * @param string                            $php_ext
! 	 * @param string                            $user_notifications_table
  	 */
! 	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\language\language $language, \phpbb\user $user, \phpbb\auth\auth $auth, $phpbb_root_path, $php_ext, $user_notifications_table)
  	{
  		$this->db = $db;
  		$this->language = $language;
  		$this->user = $user;
--- 89,109 ----
  	/**
  	 * Notification Type Base Constructor
  	 *
! 	 * @param avatar_helper		$avatar_helper
! 	 * @param helper			$controller_helper
! 	 * @param driver_interface	$db
! 	 * @param language			$language
! 	 * @param user				$user
! 	 * @param auth				$auth
! 	 * @param string			$phpbb_root_path
! 	 * @param string			$php_ext
! 	 * @param string			$user_notifications_table
  	 */
! 	public function __construct(avatar_helper $avatar_helper, helper $controller_helper, driver_interface $db, language $language,
! 								user $user, auth $auth, string $phpbb_root_path, string $php_ext, string $user_notifications_table)
  	{
+ 		$this->avatar_helper = $avatar_helper;
+ 		$this->controller_helper = $controller_helper;
  		$this->db = $db;
  		$this->language = $language;
  		$this->user = $user;
***************
*** 286,303 ****
  
  		if ($this->get_url())
  		{
! 			$u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&amp;hash=' . $mark_hash);
  		}
  		else
  		{
  			$redirect = (($this->user->page['page_dir']) ? $this->user->page['page_dir'] . '/' : '') . $this->user->page['page_name'] . (($this->user->page['query_string']) ? '?' . $this->user->page['query_string'] : '');
! 
! 			$u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&amp;hash=' . $mark_hash . '&amp;redirect=' . urlencode($redirect));
  		}
  
  		$avatar = $this->get_avatar();
  
! 		return [
  			'NOTIFICATION_ID'	=> $this->notification_id,
  			'STYLING'			=> $this->get_style_class(),
  			'FORMATTED_TITLE'	=> $this->get_title(),
--- 303,320 ----
  
  		if ($this->get_url())
  		{
! 			$u_mark_read = $this->controller_helper->route('phpbb_notifications_mark_read', ['id' => $this->notification_id, 'hash' => $mark_hash]);
  		}
  		else
  		{
  			$redirect = (($this->user->page['page_dir']) ? $this->user->page['page_dir'] . '/' : '') . $this->user->page['page_name'] . (($this->user->page['query_string']) ? '?' . $this->user->page['query_string'] : '');
! 			$u_mark_read = $this->controller_helper->route('phpbb_notifications_mark_read', ['id' => $this->notification_id, 'hash' => $mark_hash, 'redirect' => $redirect]);
  		}
  
  		$avatar = $this->get_avatar();
+ 		$avatar_data = count($avatar) ? $this->avatar_helper->get_template_vars($avatar) : [];
  
! 		return array_merge($avatar_data, [
  			'NOTIFICATION_ID'	=> $this->notification_id,
  			'STYLING'			=> $this->get_style_class(),
  			'FORMATTED_TITLE'	=> $this->get_title(),
***************
*** 308,325 ****
  			'TIME'	   			=> $this->user->format_date($this->notification_time),
  			'UNREAD'			=> !$this->notification_read,
  
- 			'AVATAR_SOURCE'		=> $avatar ? $avatar['src'] : '',
- 			'AVATAR_TITLE'		=> $avatar ? $avatar['title'] : '',
- 			'AVATAR_TYPE'		=> $avatar ? $avatar['type'] : '',
- 
- 			'AVATAR_WIDTH'		=> $avatar ? $avatar['width'] : 0,
- 			'AVATAR_HEIGHT'		=> $avatar ? $avatar['height'] : 0,
- 
- 			'AVATAR_HTML'		=> $avatar ? $avatar['html'] : '',
- 			'AVATAR_LAZY'		=> $avatar ? $avatar['lazy'] : true,
- 
  			'U_MARK_READ'		=> (!$this->notification_read) ? $u_mark_read : '',
! 		];
  	}
  
  	/**
--- 325,332 ----
  			'TIME'	   			=> $this->user->format_date($this->notification_time),
  			'UNREAD'			=> !$this->notification_read,
  
  			'U_MARK_READ'		=> (!$this->notification_read) ? $u_mark_read : '',
! 		]);
  	}
  
  	/**
diff -x images -crNEB release-4.0.0-a1/phpbb/passwords/driver/helper.php release-4.0.0-a2-dev/phpbb/passwords/driver/helper.php
*** release-4.0.0-a1/phpbb/passwords/driver/helper.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/passwords/driver/helper.php	Wed Apr  1 17:47:36 2026
***************
*** 140,145 ****
--- 140,148 ----
  			}
  			$random = substr($random, 0, $length);
  		}
+ 		/**
+ 		 * @psalm-suppress FalsableReturnStatement
+ 		 */
  		return $random;
  	}
  
diff -x images -crNEB release-4.0.0-a1/phpbb/path_helper.php release-4.0.0-a2-dev/phpbb/path_helper.php
*** release-4.0.0-a1/phpbb/path_helper.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/path_helper.php	Wed Apr  1 17:47:36 2026
***************
*** 103,120 ****
  		$web_root_path = $this->get_web_root_path();
  
  		// Removes the web root path if it is already present
! 		if (strpos($path, $web_root_path) === 0)
  		{
  			$path = $this->phpbb_root_path . substr($path, strlen($web_root_path));
  		}
  
! 		if (strpos($path, $this->phpbb_root_path) === 0)
  		{
  			$path = substr($path, strlen($this->phpbb_root_path));
  
! 			if (substr($web_root_path, -8) === 'app.php/' && substr($path, 0, 7) === 'app.php')
  			{
! 				$path = substr($path, 8);
  			}
  
  			$path = filesystem_helper::clean_path($web_root_path . $path);
--- 103,120 ----
  		$web_root_path = $this->get_web_root_path();
  
  		// Removes the web root path if it is already present
! 		if (str_starts_with($path, $web_root_path))
  		{
  			$path = $this->phpbb_root_path . substr($path, strlen($web_root_path));
  		}
  
! 		if (str_starts_with($path, $this->phpbb_root_path))
  		{
  			$path = substr($path, strlen($this->phpbb_root_path));
  
! 			if (str_ends_with($web_root_path, 'index.php/') && str_starts_with($path, 'index.php'))
  			{
! 				$path = substr($path, strlen('index.php/'));
  			}
  
  			$path = filesystem_helper::clean_path($web_root_path . $path);
***************
*** 171,188 ****
  		// Path info (e.g. /foo/bar)
  		$path_info = filesystem_helper::clean_path($this->symfony_request->getPathInfo());
  
! 		// Full request URI (e.g. phpBB/app.php/foo/bar)
  		$request_uri = $this->symfony_request->getRequestUri();
  
! 		// Script name URI (e.g. phpBB/app.php)
  		$script_name = $this->symfony_request->getScriptName();
  
  		/*
! 		* If the path info is empty but we're using app.php, then we
! 		*	might be using an empty route like app.php/ which is
  		*	supported by symfony's routing
  		*/
! 		if ($path_info === '/' && preg_match('/app\.' . $this->php_ext . '\/$/', $request_uri))
  		{
  			return $this->web_root_path = filesystem_helper::clean_path('./../' . $this->phpbb_root_path);
  		}
--- 171,188 ----
  		// Path info (e.g. /foo/bar)
  		$path_info = filesystem_helper::clean_path($this->symfony_request->getPathInfo());
  
! 		// Full request URI (e.g. phpBB/index.php/foo/bar)
  		$request_uri = $this->symfony_request->getRequestUri();
  
! 		// Script name URI (e.g. phpBB/index.php)
  		$script_name = $this->symfony_request->getScriptName();
  
  		/*
! 		* If the path info is empty but we're using index.php, then we
! 		*	might be using an empty route like index.php/ which is
  		*	supported by symfony's routing
  		*/
! 		if ($path_info === '/' && preg_match('/index\.' . $this->php_ext . '\/$/', $request_uri))
  		{
  			return $this->web_root_path = filesystem_helper::clean_path('./../' . $this->phpbb_root_path);
  		}
***************
*** 194,200 ****
  
  		/*
  		* If the path info is empty (single /), then we're not using
! 		*	a route like app.php/foo/bar
  		*/
  		if ($path_info === '/')
  		{
--- 194,200 ----
  
  		/*
  		* If the path info is empty (single /), then we're not using
! 		*	a route like index.php/foo/bar
  		*/
  		if ($path_info === '/')
  		{
***************
*** 211,220 ****
  		* intended locations:
  		*	Referer				desired URL			desired relative root path
  		*	memberlist.php		faq.php				./
! 		*	memberlist.php		app.php/foo/bar		./
! 		*	app.php/foo			memberlist.php		../
! 		*	app.php/foo			app.php/fox			../
! 		*	app.php/foo/bar		memberlist.php		../../
  		*	../page.php			memberlist.php		./phpBB/
  		*	../sub/page.php		memberlist.php		./../phpBB/
  		*
--- 211,220 ----
  		* intended locations:
  		*	Referer				desired URL			desired relative root path
  		*	memberlist.php		faq.php				./
! 		*	memberlist.php		index.php/foo/bar		./
! 		*	index.php/foo		memberlist.php		../
! 		*	index.php/foo		index.php/fox			../
! 		*	index.php/foo/bar	memberlist.php		../../
  		*	../page.php			memberlist.php		./phpBB/
  		*	../sub/page.php		memberlist.php		./../phpBB/
  		*
***************
*** 236,246 ****
  		$corrections = substr_count($path_info, '/');
  
  		/*
! 		* If the script name (e.g. phpBB/app.php) does not exists in the
! 		* requestUri (e.g. phpBB/app.php/foo/template), then we are rewriting
  		* the URL. So we must reduce the slash count by 1.
  		*/
! 		if (strpos($request_uri, $script_name) !== 0)
  		{
  			$corrections--;
  		}
--- 236,246 ----
  		$corrections = substr_count($path_info, '/');
  
  		/*
! 		* If the script name (e.g. phpBB/index.php) does not exists in the
! 		* requestUri (e.g. phpBB/index.php/foo/template), then we are rewriting
  		* the URL. So we must reduce the slash count by 1.
  		*/
! 		if (!str_starts_with($request_uri, $script_name))
  		{
  			$corrections--;
  		}
***************
*** 266,272 ****
  		// So we just need to count the / (slashes) in the left over part of
  		// the referer and prepend ../ the the current root_path, to get the
  		// web root path of the referer.
! 		if (strpos($absolute_referer_url, $absolute_board_url) === 0)
  		{
  			$relative_referer_path = substr($absolute_referer_url, strlen($absolute_board_url));
  			$has_params = strpos($relative_referer_path, '?');
--- 266,272 ----
  		// So we just need to count the / (slashes) in the left over part of
  		// the referer and prepend ../ the the current root_path, to get the
  		// web root path of the referer.
! 		if (str_starts_with($absolute_referer_url, $absolute_board_url))
  		{
  			$relative_referer_path = substr($absolute_referer_url, strlen($absolute_board_url));
  			$has_params = strpos($relative_referer_path, '?');
***************
*** 295,301 ****
  		// If we do not find a slash at the end of the referer, we come
  		// from a file. So the first dirname() does not need a traversal
  		// path correction.
! 		if (substr($referer_dir, -1) !== '/')
  		{
  			$referer_dir = dirname($referer_dir);
  		}
--- 295,301 ----
  		// If we do not find a slash at the end of the referer, we come
  		// from a file. So the first dirname() does not need a traversal
  		// path correction.
! 		if (!str_ends_with($referer_dir, '/'))
  		{
  			$referer_dir = dirname($referer_dir);
  		}
***************
*** 377,383 ****
  		$separator = ($is_amp) ? '&amp;' : '&';
  		$params = array();
  
! 		if (strpos($url, '?') !== false)
  		{
  			$base = substr($url, 0, strpos($url, '?'));
  			$args = substr($url, strlen($base) + 1);
--- 377,383 ----
  		$separator = ($is_amp) ? '&amp;' : '&';
  		$params = array();
  
! 		if (str_contains($url, '?'))
  		{
  			$base = substr($url, 0, strpos($url, '?'));
  			$args = substr($url, strlen($base) + 1);
***************
*** 391,397 ****
  				}
  
  				// some parameters don't have value
! 				if (strpos($argument, '=') !== false)
  				{
  					list($key, $value) = explode('=', $argument, 2);
  				}
--- 391,397 ----
  				}
  
  				// some parameters don't have value
! 				if (str_contains($argument, '='))
  				{
  					list($key, $value) = explode('=', $argument, 2);
  				}
***************
*** 493,504 ****
  		// URL
  		if ($url_parts === false || empty($url_parts['scheme']) || empty($url_parts['host']))
  		{
! 			// Remove 'app.php/' from the page, when rewrite is enabled.
! 			// Treat app.php as a reserved file name and remove on mod rewrite
  			// even if it might not be in the phpBB root.
! 			if ($mod_rewrite && ($app_position = strpos($page, 'app.' . $this->php_ext . '/')) !== false)
  			{
! 				$page = substr($page, 0, $app_position) . substr($page, $app_position + strlen('app.' . $this->php_ext . '/'));
  			}
  
  			// Remove preceding slashes from page name and prepend root path
--- 493,504 ----
  		// URL
  		if ($url_parts === false || empty($url_parts['scheme']) || empty($url_parts['host']))
  		{
! 			// Remove 'index.php/' from the page, when rewrite is enabled.
! 			// Treat index.php as a reserved file name and remove on mod rewrite
  			// even if it might not be in the phpBB root.
! 			if ($mod_rewrite && ($app_position = strpos($page, 'index.' . $this->php_ext . '/')) !== false)
  			{
! 				$page = substr($page, 0, $app_position) . substr($page, $app_position + strlen('index.' . $this->php_ext . '/'));
  			}
  
  			// Remove preceding slashes from page name and prepend root path
***************
*** 515,523 ****
  	 */
  	public function is_router_used()
  	{
! 		// Script name URI (e.g. phpBB/app.php)
  		$script_name = $this->symfony_request->getScriptName();
  
! 		return basename($script_name) === 'app.' . $this->php_ext;
  	}
  }
--- 515,523 ----
  	 */
  	public function is_router_used()
  	{
! 		// Script name URI (e.g. phpBB/index.php)
  		$script_name = $this->symfony_request->getScriptName();
  
! 		return basename($script_name) === 'index.' . $this->php_ext;
  	}
  }
diff -x images -crNEB release-4.0.0-a1/phpbb/routing/helper.php release-4.0.0-a2-dev/phpbb/routing/helper.php
*** release-4.0.0-a1/phpbb/routing/helper.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/routing/helper.php	Wed Apr  1 17:47:36 2026
***************
*** 118,132 ****
  			$base_url .= '/' . $page_name;
  		}
  
! 		// If enable_mod_rewrite is false we need to replace the current front-end by app.php, otherwise we need to remove it.
! 		$base_url = str_replace('/' . $page_name, empty($this->config['enable_mod_rewrite']) ? '/app.' . $this->php_ext : '', $base_url);
  
! 		// We need to update the base url to move to the directory of the app.php file if the current script is not app.php
! 		if ($page_name !== 'app.php' && !$this->config['force_server_vars'])
  		{
  			if (empty($this->config['enable_mod_rewrite']))
  			{
! 				$base_url = str_replace('/app.' . $this->php_ext, '/' . $this->phpbb_root_path . 'app.' . $this->php_ext, $base_url);
  			}
  			else
  			{
--- 118,132 ----
  			$base_url .= '/' . $page_name;
  		}
  
! 		// If enable_mod_rewrite is false we need to replace the current front-end by index.php, otherwise we need to remove it.
! 		$base_url = str_replace('/' . $page_name, empty($this->config['enable_mod_rewrite']) ? '/index.' . $this->php_ext : '', $base_url);
  
! 		// We need to update the base url to move to the directory of the index.php file if the current script is not index.php
! 		if ((str_starts_with($base_url, '/adm') || $page_name !== 'index.php') && !$this->config['force_server_vars'])
  		{
  			if (empty($this->config['enable_mod_rewrite']))
  			{
! 				$base_url = str_replace('/index.' . $this->php_ext, '/' . $this->phpbb_root_path . 'index.' . $this->php_ext, $base_url);
  			}
  			else
  			{
***************
*** 148,154 ****
  
  		if ($reference_type === UrlGeneratorInterface::RELATIVE_PATH && empty($this->config['enable_mod_rewrite']))
  		{
! 			$route_url = 'app.' . $this->php_ext . '/' . $route_url;
  		}
  
  		return append_sid($route_url . $anchor, false, $is_amp, $session_id, true);
--- 148,154 ----
  
  		if ($reference_type === UrlGeneratorInterface::RELATIVE_PATH && empty($this->config['enable_mod_rewrite']))
  		{
! 			$route_url = 'index.' . $this->php_ext . '/' . $route_url;
  		}
  
  		return append_sid($route_url . $anchor, false, $is_amp, $session_id, true);
diff -x images -crNEB release-4.0.0-a1/phpbb/routing/router.php release-4.0.0-a2-dev/phpbb/routing/router.php
*** release-4.0.0-a1/phpbb/routing/router.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/routing/router.php	Wed Apr  1 17:47:36 2026
***************
*** 146,152 ****
  	/**
  	 * {@inheritdoc}
  	 */
! 	public function getRouteCollection()
  	{
  		return $this->get_routes();
  	}
--- 146,152 ----
  	/**
  	 * {@inheritdoc}
  	 */
! 	public function getRouteCollection(): RouteCollection
  	{
  		return $this->get_routes();
  	}
***************
*** 154,160 ****
  	/**
  	 * {@inheritdoc}
  	 */
! 	public function setContext(RequestContext $context)
  	{
  		$this->context = $context;
  
--- 154,160 ----
  	/**
  	 * {@inheritdoc}
  	 */
! 	public function setContext(RequestContext $context): void
  	{
  		$this->context = $context;
  
***************
*** 189,194 ****
--- 189,205 ----
  	 */
  	public function match(string $pathinfo): array
  	{
+ 		// Remove query string
+ 		$pathinfo = parse_url($pathinfo, PHP_URL_PATH);
+ 
+ 		if ($pathinfo === null)
+ 		{
+ 			throw new RuntimeException('Malformed pathinfo given to the router.');
+ 		}
+ 
+ 		// Remove index.php if present
+ 		$pathinfo = preg_replace('/^\/index\.php\//', '/', $pathinfo);
+ 
  		return $this->get_matcher()->match($pathinfo);
  	}
  
diff -x images -crNEB release-4.0.0-a1/phpbb/search/backend/fulltext_sphinx.php release-4.0.0-a2-dev/phpbb/search/backend/fulltext_sphinx.php
*** release-4.0.0-a1/phpbb/search/backend/fulltext_sphinx.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/search/backend/fulltext_sphinx.php	Wed Apr  1 17:47:36 2026
***************
*** 774,780 ****
  		 */
  		// $search_string = preg_replace('#[0-9]{1,3}\K,(?=[0-9]{3})#', '', $search_string);
  
! 		return $search_string;
  	}
  
  	/**
--- 774,780 ----
  		 */
  		// $search_string = preg_replace('#[0-9]{1,3}\K,(?=[0-9]{3})#', '', $search_string);
  
! 		return $search_string ?: '';
  	}
  
  	/**
diff -x images -crNEB release-4.0.0-a1/phpbb/session.php release-4.0.0-a2-dev/phpbb/session.php
*** release-4.0.0-a1/phpbb/session.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/session.php	Wed Apr  1 17:47:36 2026
***************
*** 818,840 ****
  
  			unset($cookie_expire);
  
! 			$sql = 'SELECT COUNT(session_id) AS sessions
! 					FROM ' . SESSIONS_TABLE . '
! 					WHERE session_user_id = ' . (int) $this->data['user_id'] . '
! 					AND session_time >= ' . (int) ($this->time_now - (max((int) $config['session_length'], (int) $config['form_token_lifetime'])));
! 			$result = $db->sql_query($sql);
! 			$row = $db->sql_fetchrow($result);
! 			$db->sql_freeresult($result);
! 
! 			if ((int) $row['sessions'] <= 1 || empty($this->data['user_form_salt']))
  			{
! 				$this->data['user_form_salt'] = unique_id();
! 				// Update the form key
! 				$sql = 'UPDATE ' . USERS_TABLE . '
  					SET user_form_salt = \'' . $db->sql_escape($this->data['user_form_salt']) . '\',
  						user_last_active = ' . (int) $this->time_now . '
  					WHERE user_id = ' . (int) $this->data['user_id'];
! 				$db->sql_query($sql);
  			}
  			else
  			{
--- 818,843 ----
  
  			unset($cookie_expire);
  
! 			if ($this->data['user_id'] != ANONYMOUS)
  			{
! 				$sql = 'SELECT COUNT(session_id) AS sessions
! 						FROM ' . SESSIONS_TABLE . '
! 						WHERE session_user_id = ' . (int) $this->data['user_id'] . '
! 						AND session_time >= ' . (int) ($this->time_now - (max((int) $config['session_length'], (int) $config['form_token_lifetime'])));
! 				$result = $db->sql_query($sql);
! 				$row = $db->sql_fetchrow($result);
! 				$db->sql_freeresult($result);
! 
! 				if ((int) $row['sessions'] <= 1 || empty($this->data['user_form_salt']))
! 				{
! 					$this->data['user_form_salt'] = unique_id();
! 					// Update the form key
! 					$sql = 'UPDATE ' . USERS_TABLE . '
  					SET user_form_salt = \'' . $db->sql_escape($this->data['user_form_salt']) . '\',
  						user_last_active = ' . (int) $this->time_now . '
  					WHERE user_id = ' . (int) $this->data['user_id'];
! 					$db->sql_query($sql);
! 				}
  			}
  			else
  			{
diff -x images -crNEB release-4.0.0-a1/phpbb/storage/controller/attachment.php release-4.0.0-a2-dev/phpbb/storage/controller/attachment.php
*** release-4.0.0-a1/phpbb/storage/controller/attachment.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/storage/controller/attachment.php	Wed Apr  1 17:47:36 2026
***************
*** 24,31 ****
--- 24,34 ----
  use phpbb\language\language;
  use phpbb\mimetype\extension_guesser;
  use phpbb\request\request;
+ use phpbb\storage\provider\local;
  use phpbb\storage\storage;
  use phpbb\user;
+ use Symfony\Component\HttpFoundation\BinaryFileResponse;
+ use Symfony\Component\HttpFoundation\HeaderUtils;
  use Symfony\Component\HttpFoundation\Request as symfony_request;
  use Symfony\Component\HttpFoundation\RedirectResponse;
  use Symfony\Component\HttpFoundation\Response;
***************
*** 35,60 ****
  /**
   * Controller for /download/attachment/{id} routes
   */
! class attachment extends controller
  {
  	/** @var auth */
  	protected $auth;
  
  	/** @var config */
  	protected $config;
  
  	/** @var content_visibility */
  	protected $content_visibility;
  
  	/** @var dispatcher_interface */
  	protected $dispatcher;
  
  	/** @var language */
  	protected $language;
  
  	/** @var request */
  	protected $request;
  
  	/** @var user */
  	protected $user;
  
--- 38,78 ----
  /**
   * Controller for /download/attachment/{id} routes
   */
! class attachment
  {
  	/** @var auth */
  	protected $auth;
  
+ 	/** @var service */
+ 	protected $cache;
+ 
  	/** @var config */
  	protected $config;
  
  	/** @var content_visibility */
  	protected $content_visibility;
  
+ 	/** @var driver_interface */
+ 	protected $db;
+ 
  	/** @var dispatcher_interface */
  	protected $dispatcher;
  
+ 	/** @var extension_guesser */
+ 	protected $extension_guesser;
+ 
  	/** @var language */
  	protected $language;
  
  	/** @var request */
  	protected $request;
  
+ 	/** @var storage */
+ 	protected $storage;
+ 
+ 	/** @var symfony_request */
+ 	protected $symfony_request;
+ 
  	/** @var user */
  	protected $user;
  
***************
*** 76,89 ****
  	 */
  	public function __construct(auth $auth, service $cache, config $config, content_visibility $content_visibility, driver_interface $db, dispatcher_interface $dispatcher, extension_guesser $extension_guesser, language $language, request $request, storage $storage, symfony_request $symfony_request, user $user)
  	{
- 		parent::__construct($cache, $db, $extension_guesser, $storage, $symfony_request);
- 
  		$this->auth = $auth;
  		$this->config = $config;
  		$this->content_visibility = $content_visibility;
  		$this->dispatcher = $dispatcher;
  		$this->language = $language;
  		$this->request = $request;
  		$this->user = $user;
  	}
  
--- 94,110 ----
  	 */
  	public function __construct(auth $auth, service $cache, config $config, content_visibility $content_visibility, driver_interface $db, dispatcher_interface $dispatcher, extension_guesser $extension_guesser, language $language, request $request, storage $storage, symfony_request $symfony_request, user $user)
  	{
  		$this->auth = $auth;
+ 		$this->cache = $cache;
  		$this->config = $config;
  		$this->content_visibility = $content_visibility;
+ 		$this->db = $db;
  		$this->dispatcher = $dispatcher;
+ 		$this->extension_guesser = $extension_guesser;
  		$this->language = $language;
  		$this->request = $request;
+ 		$this->storage = $storage;
+ 		$this->symfony_request = $symfony_request;
  		$this->user = $user;
  	}
  
***************
*** 110,124 ****
  			throw new http_exception(404, 'NO_ATTACHMENT_SELECTED');
  		}
  
! 		$sql = 'SELECT attach_id, post_msg_id, topic_id, in_message, poster_id,
! 				is_orphan, physical_filename, real_filename, extension, mimetype,
! 				filesize, filetime
! 			FROM ' . ATTACHMENTS_TABLE . "
! 			WHERE attach_id = $attach_id" .
! 				(($filename) ? " AND real_filename = '" . $this->db->sql_escape($filename) . "'" : '');
! 		$result = $this->db->sql_query($sql);
! 		$attachment = $this->db->sql_fetchrow($result);
! 		$this->db->sql_freeresult($result);
  
  		if (!$attachment)
  		{
--- 131,137 ----
  			throw new http_exception(404, 'NO_ATTACHMENT_SELECTED');
  		}
  
! 		$attachment = $this->get_attachment($attach_id, $filename);
  
  		if (!$attachment)
  		{
***************
*** 157,168 ****
  			{
  				$this->phpbb_download_handle_forum_auth($attachment['topic_id']);
  
! 				$sql = 'SELECT forum_id, poster_id, post_visibility
! 					FROM ' . POSTS_TABLE . '
! 					WHERE post_id = ' . (int) $attachment['post_msg_id'];
! 				$result = $this->db->sql_query($sql);
! 				$post_row = $this->db->sql_fetchrow($result);
! 				$this->db->sql_freeresult($result);
  
  				if (!$post_row || !$this->content_visibility->is_visible('post', $post_row['forum_id'], $post_row))
  				{
--- 170,176 ----
  			{
  				$this->phpbb_download_handle_forum_auth($attachment['topic_id']);
  
! 				$post_row = $this->get_post((int) $attachment['post_msg_id']);
  
  				if (!$post_row || !$this->content_visibility->is_visible('post', $post_row['forum_id'], $post_row))
  				{
***************
*** 173,183 ****
  			else
  			{
  				// Attachment is in a private message.
! 				$post_row = array('forum_id' => false);
  				$this->phpbb_download_handle_pm_auth( $attachment['post_msg_id']);
  			}
  
! 			$extensions = array();
  			if (!extension_allowed($post_row['forum_id'], $attachment['extension'], $extensions))
  			{
  				throw new http_exception(403, 'EXTENSION_DISABLED_AFTER_POSTING', [$attachment['extension']]);
--- 181,191 ----
  			else
  			{
  				// Attachment is in a private message.
! 				$post_row = ['forum_id' => false];
  				$this->phpbb_download_handle_pm_auth( $attachment['post_msg_id']);
  			}
  
! 			$extensions = [];
  			if (!extension_allowed($post_row['forum_id'], $attachment['extension'], $extensions))
  			{
  				throw new http_exception(403, 'EXTENSION_DISABLED_AFTER_POSTING', [$attachment['extension']]);
***************
*** 216,228 ****
  		 * @changed 3.3.0-a1		Remove display_cat variable
  		 * @changed 3.3.0-a1		Remove mode variable
  		 */
! 		$vars = array(
  			'attach_id',
  			'attachment',
  			'extensions',
  			'thumbnail',
  			'redirect',
! 		);
  		extract($this->dispatcher->trigger_event('core.download_file_send_to_browser_before', compact($vars)));
  
  		// If the redirect variable have been overwritten, do redirect there
--- 224,236 ----
  		 * @changed 3.3.0-a1		Remove display_cat variable
  		 * @changed 3.3.0-a1		Remove mode variable
  		 */
! 		$vars = [
  			'attach_id',
  			'attachment',
  			'extensions',
  			'thumbnail',
  			'redirect',
! 		];
  		extract($this->dispatcher->trigger_event('core.download_file_send_to_browser_before', compact($vars)));
  
  		// If the redirect variable have been overwritten, do redirect there
***************
*** 247,272 ****
  		 * @changed 3.3.0-a1		Removed size variable
  		 * @changed 3.3.0-a1		Removed filename variable
  		 */
! 		$vars = array(
  			'attachment',
! 		);
  		extract($this->dispatcher->trigger_event('core.send_file_to_browser_before', compact($vars)));
  
! 		// TODO: The next lines should go better in prepare, also the mimetype is handled by the storage table
! 		// so probably can be removed
! 
! 		$response = new StreamedResponse();
! 
! 		// Content-type header
! 		$response->headers->set('Content-Type', $attachment['mimetype']);
! 
! 		// Display file types in browser and force download for others
! 		if (strpos($attachment['mimetype'], 'image') !== false
! 			|| strpos($attachment['mimetype'], 'audio') !== false
! 			|| strpos($attachment['mimetype'], 'video') !== false
! 		)
  		{
! 			$disposition = $response->headers->makeDisposition(
  				ResponseHeaderBag::DISPOSITION_INLINE,
  				$attachment['real_filename'],
  				$this->filenameFallback($attachment['real_filename'])
--- 255,269 ----
  		 * @changed 3.3.0-a1		Removed size variable
  		 * @changed 3.3.0-a1		Removed filename variable
  		 */
! 		$vars = [
  			'attachment',
! 		];
  		extract($this->dispatcher->trigger_event('core.send_file_to_browser_before', compact($vars)));
  
! 		// Display images in browser and force download for others
! 		if ($display_cat == attachment_category::IMAGE && str_starts_with($attachment['mimetype'], 'image'))
  		{
! 			$disposition = HeaderUtils::makeDisposition(
  				ResponseHeaderBag::DISPOSITION_INLINE,
  				$attachment['real_filename'],
  				$this->filenameFallback($attachment['real_filename'])
***************
*** 274,313 ****
  		}
  		else
  		{
! 			$disposition = $response->headers->makeDisposition(
  				ResponseHeaderBag::DISPOSITION_ATTACHMENT,
  				$attachment['real_filename'],
  				$this->filenameFallback($attachment['real_filename'])
  			);
  		}
  
  		$response->headers->set('Content-Disposition', $disposition);
  
! 		// Set expires header for browser cache
! 		$time = new \DateTime();
! 		$response->setExpires($time->modify('+1 year'));
  
! 		return parent::handle($attachment['physical_filename']);
  	}
  
  	/**
! 	 * Remove non valid characters https://github.com/symfony/http-foundation/commit/c7df9082ee7205548a97031683bc6550b5dc9551
  	 */
! 	protected function filenameFallback($filename): string
  	{
! 		$filename = (string) preg_replace(['/[^\x20-\x7e]/', '/%/', '/\//', '/\\\\/'], '', $filename);
  
! 		return !empty($filename) ? $filename : 'File';
  	}
  
  	/**
! 	 * {@inheritdoc}
  	 */
! 	protected function prepare(StreamedResponse $response, string $file): void
  	{
! 		$response->setPrivate();	// By default, should be private, but make sure of it
  
! 		parent::prepare($response, $file);
  	}
  
  	/**
--- 271,380 ----
  		}
  		else
  		{
! 			// Correct the mime type - we force application/octet-stream for all files, except images
! 			$attachment['mimetype'] = 'application/octet-stream';
! 
! 			$disposition = HeaderUtils::makeDisposition(
  				ResponseHeaderBag::DISPOSITION_ATTACHMENT,
  				$attachment['real_filename'],
  				$this->filenameFallback($attachment['real_filename'])
  			);
  		}
  
+ 		if ($this->config['storage\\attachment\\provider'] === local::class)
+ 		{
+ 			$response = new BinaryFileResponse($this->config['storage\\attachment\\config\\path'] . '/' . $attachment['physical_filename']);
+ 		}
+ 		else
+ 		{
+ 			$response = new StreamedResponse();
+ 
+ 			$fp = $this->storage->read($attachment['physical_filename']);
+ 
+ 			$output = fopen('php://output', 'w+b');
+ 
+ 			$response->setCallback(function () use ($fp, $output) {
+ 				stream_copy_to_stream($fp, $output);
+ 				fclose($fp);
+ 				fclose($output);
+ 				flush();
+ 
+ 				// Terminate script to avoid the execution of terminate events
+ 				// This avoids possible errors with db connection closed
+ 				exit;
+ 			});
+ 		}
+ 
+ 		// Close db connection and unload cache
+ 		$this->cache->unload();
+ 		$this->db->sql_close();
+ 
+ 		// Don't cache attachments on proxies
+ 		$response->setPrivate();
+ 
+ 		// Content-type and content-disposition headers
+ 		$response->headers->set('Content-Type', $attachment['mimetype']);
  		$response->headers->set('Content-Disposition', $disposition);
  
! 		@set_time_limit(0);
  
! 		return $response;
  	}
  
  	/**
! 	 * Get attachment data
! 	 *
! 	 * @param int $attach_id The attach_id of the attachment
! 	 * @param string $filename The real filename of the attachment (optional)
! 	 *
! 	 * @return array|null Array with attachment data or null if the attachment is not found
  	 */
! 	protected function get_attachment(int $attach_id, string $filename): array|null
  	{
! 		$sql = 'SELECT attach_id, post_msg_id, topic_id, in_message, poster_id,
! 				is_orphan, physical_filename, real_filename, extension, mimetype,
! 				filesize, filetime
! 			FROM ' . ATTACHMENTS_TABLE . "
! 			WHERE attach_id = $attach_id" .
! 			(($filename) ? " AND real_filename = '" . $this->db->sql_escape($filename) . "'" : '');
! 		$result = $this->db->sql_query($sql);
! 		$attachment = $this->db->sql_fetchrow($result);
! 		$this->db->sql_freeresult($result);
  
! 		return $attachment ?: null;
  	}
  
  	/**
! 	 * Get post data
! 	 *
! 	 * @param int $post_id The post_id of the post
! 	 *
! 	 * @return array|null Array with post data or null if the post is not found
  	 */
! 	protected function get_post(int $post_id): array|null
  	{
! 		$sql = 'SELECT forum_id, poster_id, post_visibility
! 					FROM ' . POSTS_TABLE . '
! 					WHERE post_id = ' . (int) $post_id;
! 		$result = $this->db->sql_query($sql);
! 		$post_row = $this->db->sql_fetchrow($result);
! 		$this->db->sql_freeresult($result);
  
! 		return $post_row ?: null;
! 	}
! 
! 	/**
! 	 * Remove non valid characters https://github.com/symfony/http-foundation/commit/c7df9082ee7205548a97031683bc6550b5dc9551
! 	 *
! 	 * @param string $filename The filename to sanitize
! 	  *
! 	  * @return string Sanitized filename
! 	 */
! 	protected function filenameFallback(string $filename): string
! 	{
! 		$filename = (string) preg_replace(['/[^\x20-\x7e]/', '/%/', '/\//', '/\\\\/'], '', $filename);
! 
! 		return $filename ?: 'File';
  	}
  
  	/**
***************
*** 370,375 ****
--- 437,443 ----
  		}
  
  		$allowed = $this->phpbb_download_check_pm_auth($msg_id);
+ 		$user_id = $this->user->data['user_id'];
  
  		/**
  		 * Event to modify PM attachments download auth
***************
*** 380,386 ****
  		 * @var	int		user_id		The user id for auth check
  		 * @since 3.1.11-RC1
  		 */
! 		$vars = array('allowed', 'msg_id', 'user_id');
  		extract($this->dispatcher->trigger_event('core.modify_pm_attach_download_auth', compact($vars)));
  
  		if (!$allowed)
--- 448,454 ----
  		 * @var	int		user_id		The user id for auth check
  		 * @since 3.1.11-RC1
  		 */
! 		$vars = ['allowed', 'msg_id', 'user_id'];
  		extract($this->dispatcher->trigger_event('core.modify_pm_attach_download_auth', compact($vars)));
  
  		if (!$allowed)
***************
*** 407,413 ****
  				AND (
  					user_id = ' . (int) $user_id . '
  					OR author_id = ' . (int) $user_id . '
! 				)';
  		$result = $this->db->sql_query_limit($sql, 1);
  		$allowed = (bool) $this->db->sql_fetchfield('msg_id');
  		$this->db->sql_freeresult($result);
--- 475,482 ----
  				AND (
  					user_id = ' . (int) $user_id . '
  					OR author_id = ' . (int) $user_id . '
! 				)
! 			';
  		$result = $this->db->sql_query_limit($sql, 1);
  		$allowed = (bool) $this->db->sql_fetchfield('msg_id');
  		$this->db->sql_freeresult($result);
***************
*** 460,466 ****
  		unset($url);
  
  		$allowed = !$this->config['secure_allow_deny'];
! 		$iplist = array();
  
  		if (($ip_ary = @gethostbynamel($hostname)) !== false)
  		{
--- 529,535 ----
  		unset($url);
  
  		$allowed = !$this->config['secure_allow_deny'];
! 		$iplist = [];
  
  		if (($ip_ary = @gethostbynamel($hostname)) !== false)
  		{
diff -x images -crNEB release-4.0.0-a1/phpbb/storage/controller/avatar.php release-4.0.0-a2-dev/phpbb/storage/controller/avatar.php
*** release-4.0.0-a1/phpbb/storage/controller/avatar.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/storage/controller/avatar.php	Wed Apr  1 17:47:36 2026
***************
*** 18,27 ****
  use phpbb\db\driver\driver_interface;
  use phpbb\mimetype\extension_guesser;
  use phpbb\storage\storage;
  use Symfony\Component\HttpFoundation\Request as symfony_request;
  use Symfony\Component\HttpFoundation\Response;
  use Symfony\Component\HttpFoundation\ResponseHeaderBag;
- use Symfony\Component\HttpFoundation\StreamedResponse;
  
  /**
   * Controller for /download/avatar/{file} routes
--- 18,27 ----
  use phpbb\db\driver\driver_interface;
  use phpbb\mimetype\extension_guesser;
  use phpbb\storage\storage;
+ use Symfony\Component\HttpFoundation\HeaderUtils;
  use Symfony\Component\HttpFoundation\Request as symfony_request;
  use Symfony\Component\HttpFoundation\Response;
  use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  
  /**
   * Controller for /download/avatar/{file} routes
***************
*** 98,108 ****
  	/**
  	 * {@inheritdoc}
  	 */
! 	protected function prepare(StreamedResponse $response, string $file): void
  	{
  		$response->setPublic();
  
! 		$disposition = $response->headers->makeDisposition(
  			ResponseHeaderBag::DISPOSITION_INLINE,
  			rawurlencode($file)
  		);
--- 98,108 ----
  	/**
  	 * {@inheritdoc}
  	 */
! 	protected function prepare(Response $response, string $file): void
  	{
  		$response->setPublic();
  
! 		$disposition = HeaderUtils::makeDisposition(
  			ResponseHeaderBag::DISPOSITION_INLINE,
  			rawurlencode($file)
  		);
diff -x images -crNEB release-4.0.0-a1/phpbb/storage/controller/controller.php release-4.0.0-a2-dev/phpbb/storage/controller/controller.php
*** release-4.0.0-a1/phpbb/storage/controller/controller.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/storage/controller/controller.php	Wed Apr  1 17:47:36 2026
***************
*** 72,79 ****
  	 */
  	public function handle(string $file): Response
  	{
- 		$response = new StreamedResponse();
- 
  		if (!static::is_allowed($file))
  		{
  			throw new http_exception(403, 'Forbidden');
--- 72,77 ----
***************
*** 84,89 ****
--- 82,102 ----
  			throw new http_exception(404, 'Not Found');
  		}
  
+ 		$response = new StreamedResponse();
+ 		$fp = $this->storage->read($file);
+ 		$output = fopen('php://output', 'w+b');
+ 
+ 		$response->setCallback(function () use ($fp, $output) {
+ 			stream_copy_to_stream($fp, $output);
+ 			fclose($fp);
+ 			fclose($output);
+ 			flush();
+ 
+ 			// Terminate script to avoid the execution of terminate events
+ 			// This avoids possible errors with db connection closed
+ 			exit;
+ 		});
+ 
  		static::prepare($response, $file);
  
  		if (headers_sent())
***************
*** 91,96 ****
--- 104,117 ----
  			throw new http_exception(500, 'Headers already sent');
  		}
  
+ 		// Close db connection and unload cache
+ 		$this->cache->unload();
+ 		$this->db->sql_close();
+ 
+ 		$response->isNotModified($this->symfony_request);
+ 
+ 		@set_time_limit(0);
+ 
  		return $response;
  	}
  
***************
*** 121,133 ****
  	/**
  	 * Prepare response
  	 *
! 	 * @param StreamedResponse $response
  	 * @param string $file File path
  	 *
  	 * @return void
  	 * @throws storage_exception when there is an error reading the file
  	 */
! 	protected function prepare(StreamedResponse $response, string $file): void
  	{
  		// Add Content-Type header
  		if (!$response->headers->has('Content-Type'))
--- 142,154 ----
  	/**
  	 * Prepare response
  	 *
! 	 * @param Response $response
  	 * @param string $file File path
  	 *
  	 * @return void
  	 * @throws storage_exception when there is an error reading the file
  	 */
! 	protected function prepare(Response $response, string $file): void
  	{
  		// Add Content-Type header
  		if (!$response->headers->has('Content-Type'))
***************
*** 157,191 ****
  			}
  		}
  
- 		@set_time_limit(0);
- 
- 		$fp = $this->storage->read($file);
- 
- 		// Close db connection
- 		$this->file_gc();
- 
- 		$output = fopen('php://output', 'w+b');
- 
- 		$response->setCallback(function () use ($fp, $output) {
- 			stream_copy_to_stream($fp, $output);
- 			fclose($fp);
- 			fclose($output);
- 			flush();
- 
- 			// Terminate script to avoid the execution of terminate events
- 			// This avoids possible errors with db connection closed
- 			exit;
- 		});
- 
- 		$response->isNotModified($this->symfony_request);
- 	}
- 
- 	/**
- 	* Garbage Collection
- 	*/
- 	protected function file_gc(): void
- 	{
- 		$this->cache->unload(); // Equivalent to $this->cache->get_driver()->unload();
- 		$this->db->sql_close();
  	}
  }
--- 178,182 ----
diff -x images -crNEB release-4.0.0-a1/phpbb/template/twig/extension/avatar.php release-4.0.0-a2-dev/phpbb/template/twig/extension/avatar.php
*** release-4.0.0-a1/phpbb/template/twig/extension/avatar.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/template/twig/extension/avatar.php	Wed Apr  1 17:47:36 2026
***************
*** 14,20 ****
  namespace phpbb\template\twig\extension;
  
  use phpbb\avatar\helper;
- use phpbb\avatar\manager;
  use phpbb\template\twig\environment;
  use Twig\Error\Error;
  use Twig\Extension\AbstractExtension;
--- 14,19 ----
***************
*** 63,92 ****
  	 * Get avatar for placing into templates.
  	 *
  	 * How to use in a template:
! 	 * - {{ avatar('mode', row, alt, ignore_config, lazy) }}
  	 *
  	 * The mode and row (group_row or user_row) are required.
  	 * The other fields (alt|ignore_config|lazy) are optional.
  	 *
  	 * @return string	The avatar HTML for the specified mode
  	 */
! 	public function get_avatar(environment $environment, string $mode, array $row, string|null $alt, bool|null $ignore_config, bool|null $lazy): string
  	{
- 		$alt = $alt ?? false;
- 		$ignore_config = $ignore_config ?? false;
- 		$lazy = $lazy ?? false;
- 		$row = manager::clean_row($row, $mode);
- 		$avatar = $this->avatar_helper->get_avatar($row, $alt, $ignore_config, $lazy);
- 
  		try
  		{
  			return $environment->render('macros/avatar.twig', [
! 				'SRC'		=> $avatar['lazy'] ? $this->avatar_helper->get_no_avatar_source() : $avatar['src'],
! 				'DATA_SRC'	=> $avatar['lazy'] ? $avatar['src'] : '',
! 				'WIDTH'		=> $avatar['width'],
! 				'HEIGHT'	=> $avatar['height'],
! 				'TITLE'		=> $avatar['title'],
! 				'LAZY'		=> $avatar['lazy'],
  			]);
  		}
  		catch (Error $e)
--- 62,91 ----
  	 * Get avatar for placing into templates.
  	 *
  	 * How to use in a template:
! 	 * - {{ avatar(row, alt, ignore_config, lazy) }}
  	 *
  	 * The mode and row (group_row or user_row) are required.
  	 * The other fields (alt|ignore_config|lazy) are optional.
  	 *
  	 * @return string	The avatar HTML for the specified mode
  	 */
! 	public function get_avatar(environment $environment, array $row): string
  	{
  		try
  		{
+ 			// Quickly return if required data is missing
+ 			if (!isset($row['src'], $row['width'], $row['height'], $row['title']))
+ 			{
+ 				return '';
+ 			}
+ 
  			return $environment->render('macros/avatar.twig', [
! 				'SRC'		=> !empty($row['lazy']) ? $this->avatar_helper->get_no_avatar_source() : $row['src'],
! 				'DATA_SRC'	=> !empty($row['lazy']) ? $row['src'] : '',
! 				'WIDTH'		=> $row['width'],
! 				'HEIGHT'	=> $row['height'],
! 				'TITLE'		=> $row['title'],
! 				'LAZY'		=> $row['lazy'] ?? false,
  			]);
  		}
  		catch (Error $e)
diff -x images -crNEB release-4.0.0-a1/phpbb/template/twig/extension/icon.php release-4.0.0-a2-dev/phpbb/template/twig/extension/icon.php
*** release-4.0.0-a1/phpbb/template/twig/extension/icon.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/template/twig/extension/icon.php	Wed Apr  1 17:47:36 2026
***************
*** 269,275 ****
  		$string = $doc->saveHTML();
  		$string = preg_replace('/\s+/', ' ', $string);
  
! 		return $string;
  	}
  
  	/**
--- 269,275 ----
  		$string = $doc->saveHTML();
  		$string = preg_replace('/\s+/', ' ', $string);
  
! 		return $string ?: '';
  	}
  
  	/**
diff -x images -crNEB release-4.0.0-a1/phpbb/template/twig/lexer.php release-4.0.0-a2-dev/phpbb/template/twig/lexer.php
*** release-4.0.0-a1/phpbb/template/twig/lexer.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/template/twig/lexer.php	Wed Apr  1 17:47:36 2026
***************
*** 139,145 ****
  		// E.g. 'asdf'"' -> asdf'"
  		// E.g. "asdf'"" -> asdf'"
  		// E.g. 'asdf'" -> 'asdf'"
! 		return preg_replace('#<!-- (' . implode('|', $tokens) . ') (([\'"])?(.*?)\1) -->#', '<!-- $1 $2 -->', $code);
  	}
  
  	/**
--- 139,145 ----
  		// E.g. 'asdf'"' -> asdf'"
  		// E.g. "asdf'"" -> asdf'"
  		// E.g. 'asdf'" -> 'asdf'"
! 		return preg_replace('#<!-- (' . implode('|', $tokens) . ') (([\'"])?(.*?)\1) -->#', '<!-- $1 $2 -->', $code) ?: '';
  	}
  
  	/**
***************
*** 162,168 ****
  			return "<!-- {$matches[1]} {$matches[2]} -->";
  		};
  
! 		return preg_replace_callback('#<!-- (' . implode('|', $tokens) . ') (.+?) -->#', $callback, $code);
  	}
  
  	/**
--- 162,168 ----
  			return "<!-- {$matches[1]} {$matches[2]} -->";
  		};
  
! 		return preg_replace_callback('#<!-- (' . implode('|', $tokens) . ') (.+?) -->#', $callback, $code) ?: '';
  	}
  
  	/**
***************
*** 177,183 ****
  	*/
  	protected function add_surrounding_quotes($tokens, $code)
  	{
! 		return preg_replace('#<!-- (' . implode('|', $tokens) . ') (.+?) -->#', '<!-- $1 \'$2\' -->', $code);
  	}
  
  	/**
--- 177,183 ----
  	*/
  	protected function add_surrounding_quotes($tokens, $code)
  	{
! 		return preg_replace('#<!-- (' . implode('|', $tokens) . ') (.+?) -->#', '<!-- $1 \'$2\' -->', $code) ?: '';
  	}
  
  	/**
***************
*** 253,259 ****
  			return "{% for {$name} in {$parent}{$name}{$subset} %}{$body}{% endfor %}";
  		};
  
! 		return preg_replace_callback('#<!-- BEGIN ((?:[a-zA-Z0-9_]+\.)*)([!a-zA-Z0-9_]+)(\([0-9,\-]+\))? -->(.+?)<!-- END \1\2 -->#s', $callback, $code);
  	}
  
  	/**
--- 253,259 ----
  			return "{% for {$name} in {$parent}{$name}{$subset} %}{$body}{% endfor %}";
  		};
  
! 		return preg_replace_callback('#<!-- BEGIN ((?:[a-zA-Z0-9_]+\.)*)([!a-zA-Z0-9_]+)(\([0-9,\-]+\))? -->(.+?)<!-- END \1\2 -->#s', $callback, $code) ?: '';
  	}
  
  	/**
***************
*** 285,291 ****
  			return "<!-- {$matches[1]}IF{$inner}-->";
  		};
  
! 		return preg_replace_callback('#<!-- (ELSE)?IF((.*?) (?:\(*!?[\$|\.]([^\s]+)(.*?))?)-->#', $callback, $code);
  	}
  
  	/**
--- 285,291 ----
  			return "<!-- {$matches[1]}IF{$inner}-->";
  		};
  
! 		return preg_replace_callback('#<!-- (ELSE)?IF((.*?) (?:\(*!?[\$|\.]([^\s]+)(.*?))?)-->#', $callback, $code) ?: '';
  	}
  
  	/**
***************
*** 320,326 ****
  		// Replace all of our variables, ~ $VARNAME ~, with Twig style, ~ definition.VARNAME ~
  		$code = preg_replace('#~ \$([a-zA-Z0-9_\.]+) ~#', '~ definition.$1 ~', $code);
  
! 		return $code;
  	}
  
  	/**
--- 320,326 ----
  		// Replace all of our variables, ~ $VARNAME ~, with Twig style, ~ definition.VARNAME ~
  		$code = preg_replace('#~ \$([a-zA-Z0-9_\.]+) ~#', '~ definition.$1 ~', $code);
  
! 		return $code ?: '';
  	}
  
  	/**
***************
*** 330,336 ****
  	*
  	* @param string $code
  	* @param array $twig_tags All tags we want to create a mask for
! 	* @return string
  	*/
  	protected function replace_twig_tag_masks($code, $twig_tags)
  	{
--- 330,336 ----
  	*
  	* @param string $code
  	* @param array $twig_tags All tags we want to create a mask for
! 	* @return null|string
  	*/
  	protected function replace_twig_tag_masks($code, $twig_tags)
  	{
diff -x images -crNEB release-4.0.0-a1/phpbb/textformatter/s9e/factory.php release-4.0.0-a2-dev/phpbb/textformatter/s9e/factory.php
*** release-4.0.0-a1/phpbb/textformatter/s9e/factory.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/textformatter/s9e/factory.php	Wed Apr  1 17:47:36 2026
***************
*** 647,653 ****
  		// Return the template as-is if there's only one style or all styles share the same template
  		if (count(array_unique($style_templates)) === 1)
  		{
! 			return end($style_templates);
  		}
  
  		// Group identical templates together
--- 647,653 ----
  		// Return the template as-is if there's only one style or all styles share the same template
  		if (count(array_unique($style_templates)) === 1)
  		{
! 			return end($style_templates) ?: '';
  		}
  
  		// Group identical templates together
diff -x images -crNEB release-4.0.0-a1/phpbb/ucp/controller/delete_cookies.php release-4.0.0-a2-dev/phpbb/ucp/controller/delete_cookies.php
*** release-4.0.0-a1/phpbb/ucp/controller/delete_cookies.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/ucp/controller/delete_cookies.php	Wed Apr  1 17:47:36 2026
***************
*** 14,19 ****
--- 14,20 ----
  namespace phpbb\ucp\controller;
  
  use phpbb\config\config;
+ use phpbb\controller\helper;
  use phpbb\event\dispatcher_interface;
  use phpbb\language\language;
  use phpbb\request\request_interface;
***************
*** 27,32 ****
--- 28,36 ----
  	/** @var dispatcher_interface */
  	private $dispatcher;
  
+ 	/** @var helper */
+ 	private $helper;
+ 
  	/** @var language */
  	private $language;
  
***************
*** 47,60 ****
  	 *
  	 * @param config $config
  	 * @param dispatcher_interface $dispatcher
  	 * @param language $language
  	 * @param request_interface $request
  	 * @param user $user
  	 */
! 	public function __construct(config $config, dispatcher_interface $dispatcher, language $language, request_interface $request, user $user, string $phpbb_root_path, string $php_ext)
  	{
  		$this->config = $config;
  		$this->dispatcher = $dispatcher;
  		$this->language = $language;
  		$this->request = $request;
  		$this->user = $user;
--- 51,68 ----
  	 *
  	 * @param config $config
  	 * @param dispatcher_interface $dispatcher
+ 	 * @param helper $helper
  	 * @param language $language
  	 * @param request_interface $request
  	 * @param user $user
+ 	 * @param string $phpbb_root_path
+ 	 * @param string $php_ext
  	 */
! 	public function __construct(config $config, dispatcher_interface $dispatcher, helper $helper, language $language, request_interface $request, user $user, string $phpbb_root_path, string $php_ext)
  	{
  		$this->config = $config;
  		$this->dispatcher = $dispatcher;
+ 		$this->helper = $helper;
  		$this->language = $language;
  		$this->request = $request;
  		$this->user = $user;
***************
*** 119,127 ****
  			$this->user->session_kill();
  			$this->user->session_begin();
  
! 			meta_refresh(3, append_sid("{$this->phpbb_root_path}index.$this->php_ext"));
  
! 			$message = $this->language->lang('COOKIES_DELETED') . '<br><br>' . $this->language->lang('RETURN_INDEX', '<a href="' . append_sid("{$this->phpbb_root_path}index.$this->php_ext") . '">', '</a>');
  			trigger_error($message);
  		}
  		else
--- 127,135 ----
  			$this->user->session_kill();
  			$this->user->session_begin();
  
! 			meta_refresh(3, $this->helper->route('phpbb_index_controller'));
  
! 			$message = $this->language->lang('COOKIES_DELETED') . '<br><br>' . $this->language->lang('RETURN_INDEX', '<a href="' . $this->helper->route('phpbb_index_controller') . '">', '</a>');
  			trigger_error($message);
  		}
  		else
***************
*** 129,134 ****
  			confirm_box(false, 'DELETE_COOKIES', '');
  		}
  
! 		redirect(append_sid("{$this->phpbb_root_path}index.$this->php_ext"));
  	}
  }
--- 137,142 ----
  			confirm_box(false, 'DELETE_COOKIES', '');
  		}
  
! 		redirect($this->helper->route('phpbb_index_controller'));
  	}
  }
diff -x images -crNEB release-4.0.0-a1/phpbb/ucp/controller/reset_password.php release-4.0.0-a2-dev/phpbb/ucp/controller/reset_password.php
*** release-4.0.0-a1/phpbb/ucp/controller/reset_password.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/ucp/controller/reset_password.php	Wed Apr  1 17:47:36 2026
***************
*** 214,220 ****
  			}
  			else
  			{
! 				$message = $this->language->lang('PASSWORD_RESET_LINK_SENT') . '<br /><br />' . $this->language->lang('RETURN_INDEX', '<a href="' . append_sid("{$this->root_path}index.{$this->php_ext}") . '">', '</a>');
  
  				if (empty($rowset))
  				{
--- 214,220 ----
  			}
  			else
  			{
! 				$message = $this->language->lang('PASSWORD_RESET_LINK_SENT') . '<br /><br />' . $this->language->lang('RETURN_INDEX', '<a href="' . $this->helper->route('phpbb_index_controller') . '">', '</a>');
  
  				if (empty($rowset))
  				{
***************
*** 335,341 ****
  		$user_row = $this->db->sql_fetchrow($result);
  		$this->db->sql_freeresult($result);
  
! 		$message = $this->language->lang('RESET_TOKEN_EXPIRED_OR_INVALID') . '<br /><br />' . $this->language->lang('RETURN_INDEX', '<a href="' . append_sid("{$this->root_path}index.{$this->php_ext}") . '">', '</a>');
  
  		if (empty($user_row))
  		{
--- 335,341 ----
  		$user_row = $this->db->sql_fetchrow($result);
  		$this->db->sql_freeresult($result);
  
! 		$message = $this->language->lang('RESET_TOKEN_EXPIRED_OR_INVALID') . '<br /><br />' . $this->language->lang('RETURN_INDEX', '<a href="' . $this->helper->route('phpbb_index_controller') . '">', '</a>');
  
  		if (empty($user_row))
  		{
***************
*** 416,422 ****
  					'reportee_id' => $user_row['user_id'],
  					$user_row['username']
  				]);
! 				meta_refresh(3, append_sid("{$this->root_path}index.{$this->php_ext}"));
  				return $this->helper->message($this->language->lang('PASSWORD_RESET'));
  			}
  		}
--- 416,422 ----
  					'reportee_id' => $user_row['user_id'],
  					$user_row['username']
  				]);
! 				meta_refresh(3, $this->helper->route('phpbb_index_controller'));
  				return $this->helper->message($this->language->lang('PASSWORD_RESET'));
  			}
  		}
diff -x images -crNEB release-4.0.0-a1/phpbb/ucp/controller/webpush.php release-4.0.0-a2-dev/phpbb/ucp/controller/webpush.php
*** release-4.0.0-a1/phpbb/ucp/controller/webpush.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/ucp/controller/webpush.php	Wed Apr  1 17:47:36 2026
***************
*** 166,172 ****
  			throw new http_exception(Response::HTTP_BAD_REQUEST, 'AJAX_ERROR_TEXT');
  		}
  
! 		return $this->get_notification_data($notification_data);
  	}
  
  	/**
--- 166,172 ----
  			throw new http_exception(Response::HTTP_BAD_REQUEST, 'AJAX_ERROR_TEXT');
  		}
  
! 		return $this->get_notification_data($notification_data) ?: '';
  	}
  
  	/**
***************
*** 219,225 ****
  				{
  					$this->language->set_user_language($user_lang, true);
  				}
! 				return $this->get_notification_data($notification_data);
  			}
  		}
  
--- 219,225 ----
  				{
  					$this->language->set_user_language($user_lang, true);
  				}
! 				return $this->get_notification_data($notification_data) ?: '';
  			}
  		}
  
***************
*** 231,239 ****
  	 *
  	 * @param string $notification_data Encoded data stored in database
  	 *
! 	 * @return string Data for notification output with javascript
  	 */
! 	private function get_notification_data(string $notification_data): string
  	{
  		$row_data = json_decode($notification_data, true);
  
--- 231,239 ----
  	 *
  	 * @param string $notification_data Encoded data stored in database
  	 *
! 	 * @return false|string Data for notification output with javascript
  	 */
! 	private function get_notification_data(string $notification_data): false|string
  	{
  		$row_data = json_decode($notification_data, true);
  
diff -x images -crNEB release-4.0.0-a1/phpbb/user_loader.php release-4.0.0-a2-dev/phpbb/user_loader.php
*** release-4.0.0-a1/phpbb/user_loader.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/user_loader.php	Wed Apr  1 17:47:36 2026
***************
*** 197,202 ****
--- 197,204 ----
  		}
  
  		$row = [
+ 			'id'			=> $user['user_id'],
+ 			'username'		=> $user['username'],
  			'avatar'		=> $user['user_avatar'],
  			'avatar_type'	=> $user['user_avatar_type'],
  			'avatar_width'	=> $user['user_avatar_width'],
diff -x images -crNEB release-4.0.0-a1/phpbb/version_helper.php release-4.0.0-a2-dev/phpbb/version_helper.php
*** release-4.0.0-a1/phpbb/version_helper.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/version_helper.php	Wed Apr  1 17:47:36 2026
***************
*** 171,180 ****
  	 * @param string $version
  	 * @return bool Bool true or false
  	 */
! 	public function is_stable($version)
  	{
  		$matches = false;
! 		preg_match('/^[\d\.]+/', $version, $matches);
  
  		if (empty($matches[0]))
  		{
--- 171,180 ----
  	 * @param string $version
  	 * @return bool Bool true or false
  	 */
! 	public function is_stable(string $version): bool
  	{
  		$matches = false;
! 		preg_match('/^[\d.]+/', $version, $matches);
  
  		if (empty($matches[0]))
  		{
***************
*** 185,190 ****
--- 185,204 ----
  	}
  
  	/**
+ 	 * Get branch for version, e.g. 3.2 for version 3.2.5
+ 	 *
+ 	 * @param string $version Version to get branch from
+ 	 * @return string Branch for version or empty string if version is not of expected format
+ 	 */
+ 	protected function get_branch(string $version): string
+ 	{
+ 		$matches = [];
+ 		preg_match('/^(\d+\.\d+).*$/', $version, $matches);
+ 
+ 		return $matches[1] ?? '';
+ 	}
+ 
+ 	/**
  	* Gets the latest version for the current branch the user is on
  	*
  	* @param bool $force_update Ignores cached data. Defaults to false.
***************
*** 192,218 ****
  	* @return string
  	* @throws version_check_exception
  	*/
! 	public function get_latest_on_current_branch($force_update = false, $force_cache = false)
  	{
  		$versions = $this->get_versions_matching_stability($force_update, $force_cache);
  
- 		$self = $this;
  		$current_version = $this->current_version;
  
! 		// Filter out any versions less than the current version
! 		$versions = array_filter($versions, function($data) use ($self, $current_version) {
! 			return $self->compare($data['current'], $current_version, '>=');
  		});
  
! 		// Get the lowest version from the previous list.
! 		return array_reduce($versions, function($value, $data) use ($self) {
! 			if ($value === null || $self->compare($data['current'], $value, '<'))
  			{
  				return $data['current'];
  			}
  
! 			return $value;
! 		});
  	}
  
  	/**
--- 206,242 ----
  	* @return string
  	* @throws version_check_exception
  	*/
! 	public function get_latest_on_current_branch(bool $force_update = false, bool $force_cache = false): string
  	{
  		$versions = $this->get_versions_matching_stability($force_update, $force_cache);
  
  		$current_version = $this->current_version;
  
! 		// Get the branch information for the current version
! 		$current_branch = $this->get_branch($current_version);
! 		if (isset($versions[$current_branch]) && empty($versions[$current_branch]['eol']))
! 		{
! 			return $versions[$current_branch]['current'];
! 		}
! 
! 		// Sort versions in version ascending order so we can loop from the lowest version to the highest version.
! 		uksort($versions, function($version1, $version2) {
! 			return $this->compare($version1, $version2, '>');
  		});
  
! 		// Find next available version from versions info.
! 		// Will suggest newer branches when EoL has been reached for the current branch, and/or version from newer branch
! 		// is needed for having all known security issues fixed ('security' > latest on branch).
! 		foreach ($versions as $branch => $data)
! 		{
! 			if ($this->compare($branch, $current_branch, '>=') && empty($data['eol'])
! 				&& (empty($data['security']) || $this->compare($data['security'], $data['current'], '<=')))
  			{
  				return $data['current'];
  			}
+ 		}
  
! 		return '';
  	}
  
  	/**
***************
*** 226,261 ****
  	 * @return array Version info or empty array if there are no updates
  	 * @throws \RuntimeException
  	 */
! 	public function get_update_on_branch($force_update = false, $force_cache = false)
  	{
  		$versions = $this->get_versions_matching_stability($force_update, $force_cache);
  
- 		$self = $this;
  		$current_version = $this->current_version;
  
! 		// Filter out any versions less than the current version
! 		$versions = array_filter($versions, function($data) use ($self, $current_version) {
! 			return $self->compare($data['current'], $current_version, '>=');
  		});
  
! 		// Get the lowest version from the previous list.
! 		$update_info = array_reduce($versions, function($value, $data) use ($self, $current_version) {
! 			if ($value === null && $self->compare($data['current'], $current_version, '>='))
  			{
! 				if (!$data['eol'] && (!$data['security'] || $self->compare($data['security'], $data['current'], '<=')))
  				{
! 					return ($self->compare($data['current'], $current_version, '>')) ? $data : array();
  				}
  				else
  				{
! 					return null;
  				}
  			}
  
! 			return $value;
! 		});
! 
! 		return $update_info === null ? array() : $update_info;
  	}
  
  	/**
--- 250,292 ----
  	 * @return array Version info or empty array if there are no updates
  	 * @throws \RuntimeException
  	 */
! 	public function get_update_on_branch(bool $force_update = false, bool $force_cache = false): array
  	{
  		$versions = $this->get_versions_matching_stability($force_update, $force_cache);
  
  		$current_version = $this->current_version;
  
! 		// Use current branch information if it exists
! 		$current_branch = $this->get_branch($current_version);
! 		$current_branch_data = $versions[$current_branch] ?? null;
! 		if ($current_branch_data && empty($current_branch_data['eol'])
! 			&& (empty($current_branch_data['security']) || $this->compare($current_branch_data['security'], $current_version, '<=')))
! 		{
! 			return ($this->compare($current_branch_data['current'], $current_version, '>')) ? $current_branch_data : [];
! 		}
! 
! 		// Sort versions in version ascending order so we can loop from the lowest version to the highest version.
! 		uksort($versions, function($version1, $version2) {
! 			return $this->compare($version1, $version2, '>');
  		});
  
! 		foreach ($versions as $branch => $data)
! 		{
! 			if ($this->compare($branch, $current_branch, '>=') && empty($data['eol'])
! 				&& (empty($data['security']) || $this->compare($data['security'], $data['current'], '<=')))
  			{
! 				if ($this->compare($data['current'], $current_version, '>'))
  				{
! 					return $data;
  				}
  				else
  				{
! 					break;
  				}
  			}
+ 		}
  
! 		return [];
  	}
  
  	/**
***************
*** 276,284 ****
  		$self = $this;
  		$current_version = $this->current_version;
  
! 		// Get current phpBB branch from version, e.g.: 3.2
! 		preg_match('/^(\d+\.\d+).*$/', $this->config['version'], $matches);
! 		$current_branch = $matches[1];
  
  		// Filter out any versions less than the current version
  		$versions = array_filter($versions, function($data) use ($self, $current_version) {
--- 307,313 ----
  		$self = $this;
  		$current_version = $this->current_version;
  
! 		$current_branch = $this->get_branch($this->config['version']);
  
  		// Filter out any versions less than the current version
  		$versions = array_filter($versions, function($data) use ($self, $current_version) {
diff -x images -crNEB release-4.0.0-a1/phpbb/viewonline_helper.php release-4.0.0-a2-dev/phpbb/viewonline_helper.php
*** release-4.0.0-a1/phpbb/viewonline_helper.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/phpbb/viewonline_helper.php	Thu Jan  1 00:00:00 1970
***************
*** 1,98 ****
- <?php
- /**
- *
- * This file is part of the phpBB Forum Software package.
- *
- * @copyright (c) phpBB Limited <https://www.phpbb.com>
- * @license GNU General Public License, version 2 (GPL-2.0)
- *
- * For full copyright and license information, please see
- * the docs/CREDITS.txt file.
- *
- */
- 
- namespace phpbb;
- 
- use phpbb\filesystem\helper as filesystem_helper;
- 
- /**
-  * Class to handle viewonline related tasks
-  */
- class viewonline_helper
- {
- 	/** @var \phpbb\db\driver\driver_interface */
- 	protected $db;
- 
- 	/**
- 	 * @param \phpbb\db\driver\driver_interface $db
- 	 */
- 	public function __construct(\phpbb\db\driver\driver_interface $db)
- 	{
- 		$this->db = $db;
- 	}
- 
- 	/**
- 	 * Get forum IDs for topics
- 	 *
- 	 * Retrieve forum IDs and add the data into the session data array
- 	 * Array structure matches sql_fethrowset() result array
- 	 *
- 	 * @param array $session_data_rowset Users' session data array
- 	 * @return void
- 	 */
- 	public function get_forum_ids(array &$session_data_rowset): void
- 	{
- 		$topic_ids = $match = [];
- 		foreach ($session_data_rowset as $number => $row)
- 		{
- 			if ($row['session_forum_id'] == 0 && preg_match('#t=([0-9]+)#', $row['session_page'], $match))
- 			{
- 				$topic_ids[$number] = (int) $match[1];
- 			}
- 		}
- 
- 		if (count($topic_ids = array_unique($topic_ids)))
- 		{
- 			$sql_ary = [
- 				'SELECT'	=> 't.topic_id, t.forum_id',
- 				'FROM'		=> [
- 					TOPICS_TABLE => 't',
- 				],
- 				'WHERE'		=> $this->db->sql_in_set('t.topic_id', $topic_ids),
- 				'ORDER_BY'	=> 't.topic_id',
- 			];
- 			$result = $this->db->sql_query($this->db->sql_build_query('SELECT', $sql_ary));
- 			$forum_ids_rowset = $this->db->sql_fetchrowset($result);
- 			$this->db->sql_freeresult($result);
- 
- 			foreach ($forum_ids_rowset as $forum_ids_row)
- 			{
- 				$session_data_row_number = array_search((int) $forum_ids_row['topic_id'], $topic_ids);
- 				$session_data_rowset[$session_data_row_number]['session_forum_id'] = (int) $forum_ids_row['forum_id'];
- 			}
- 		}
- 	}
- 
- 	/**
- 	 * Get user page
- 	 *
- 	 * @param string $session_page User's session page
- 	 * @return array Match array filled by preg_match()
- 	 */
- 	public function get_user_page($session_page)
- 	{
- 		$session_page = filesystem_helper::clean_path($session_page);
- 		if (strpos($session_page, './') === 0)
- 		{
- 			$session_page = substr($session_page, 2);
- 		}
- 
- 		preg_match('#^((\.\./)*([a-z0-9/_-]+))#i', $session_page, $on_page);
- 		if (empty($on_page))
- 		{
- 			$on_page[1] = '';
- 		}
- 
- 		return $on_page;
- 	}
- }
--- 0 ----
diff -x images -crNEB release-4.0.0-a1/posting.php release-4.0.0-a2-dev/posting.php
*** release-4.0.0-a1/posting.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/posting.php	Wed Apr  1 17:47:36 2026
***************
*** 41,46 ****
--- 41,49 ----
  $submit = $request->is_set_post('post') && !$refresh && !$preview;
  $mode		= $request->variable('mode', '');
  
+ /** @var \phpbb\controller\helper $controller_helper */
+ $controller_helper = $phpbb_container->get('controller.helper');
+ 
  // Only assign required URL parameters
  $forum_id = 0;
  $topic_id = 0;
***************
*** 178,184 ****
  // Was cancel pressed? If so then redirect to the appropriate page
  if ($cancel)
  {
! 	$redirect = ($post_id) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'p=' . $post_id) . '#p' . $post_id : (($topic_id) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=' . $topic_id) : (($forum_id) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id) : append_sid("{$phpbb_root_path}index.$phpEx")));
  	redirect($redirect);
  }
  
--- 181,187 ----
  // Was cancel pressed? If so then redirect to the appropriate page
  if ($cancel)
  {
! 	$redirect = ($post_id) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'p=' . $post_id) . '#p' . $post_id : (($topic_id) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=' . $topic_id) : (($forum_id) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id) : $controller_helper->route('phpbb_index_controller')));
  	redirect($redirect);
  }
  
***************
*** 306,312 ****
  // Check permissions
  if ($user->data['is_bot'])
  {
! 	redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
  }
  
  // Is the user able to read within this forum?
--- 309,315 ----
  // Check permissions
  if ($user->data['is_bot'])
  {
! 	redirect($controller_helper->route('phpbb_index_controller'));
  }
  
  // Is the user able to read within this forum?
***************
*** 1916,1923 ****
  $form_enctype = (@ini_get('file_uploads') == '0' || strtolower(@ini_get('file_uploads')) == 'off' || !$config['allow_attachments'] || !$auth->acl_get('u_attach') || !$auth->acl_get('f_attach', $forum_id)) ? '' : ' enctype="multipart/form-data"';
  add_form_key('posting');
  
- /** @var \phpbb\controller\helper $controller_helper */
- $controller_helper = $phpbb_container->get('controller.helper');
  
  // Build array of variables for main posting page
  $page_data = array(
--- 1919,1924 ----
diff -x images -crNEB release-4.0.0-a1/search.php release-4.0.0-a2-dev/search.php
*** release-4.0.0-a1/search.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/search.php	Wed Apr  1 17:47:36 2026
***************
*** 19,24 ****
--- 19,27 ----
  $phpEx = substr(strrchr(__FILE__, '.'), 1);
  include($phpbb_root_path . 'common.' . $phpEx);
  
+ /** @var \phpbb\controller\helper $controller_helper */
+ $controller_helper = $phpbb_container->get('controller.helper');
+ 
  // Start session management
  $user->session_begin();
  $auth->acl($user->data);
***************
*** 483,489 ****
  				gen_sort_selects($limit_days, $sort_by_text, $sort_days, $sort_key, $sort_dir, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param);
  				$s_sort_key = $s_sort_dir = $u_sort_param = $s_limit_days = '';
  
! 				$template->assign_var('U_MARK_ALL_READ', ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}index.$phpEx", 'hash=' . generate_link_hash('global') . '&amp;mark=forums&amp;mark_time=' . time()) : '');
  			break;
  
  			case 'newposts':
--- 486,492 ----
  				gen_sort_selects($limit_days, $sort_by_text, $sort_days, $sort_key, $sort_dir, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param);
  				$s_sort_key = $s_sort_dir = $u_sort_param = $s_limit_days = '';
  
! 				$template->assign_var('U_MARK_ALL_READ', ($user->data['is_registered'] || $config['load_anon_lastread']) ? $controller_helper->route('phpbb_index_controller', ['hash' => generate_link_hash('global'), 'mark' => 'forums', 'mark_time' => time()]) : '');
  			break;
  
  			case 'newposts':
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/avatar.html release-4.0.0-a2-dev/styles/prosilver/template/avatar.html
*** release-4.0.0-a1/styles/prosilver/template/avatar.html	Thu Jan  1 00:00:00 1970
--- release-4.0.0-a2-dev/styles/prosilver/template/avatar.html	Wed Apr  1 17:47:36 2026
***************
*** 0 ****
--- 1,17 ----
+ {% if avatar_data.src %}
+ 	{% if avatar_link %}
+ 		<a href="{{ avatar_link }}" class="avatar">{{ avatar(avatar_data, lang('USER_AVATAR')) }}</a>
+ 	{% else %}
+ 		<span class="avatar">{{ avatar(avatar_data, lang('USER_AVATAR')) }}</span>
+ 	{% endif %}
+ {% elseif avatar_data.id %}
+ 	{% set color_classes = ['avatar-color-0', 'avatar-color-1', 'avatar-color-2', 'avatar-color-3', 'avatar-color-4'] %}
+ 	{% set color_class = not(avatar_data.id starts with 'g') ? color_classes[avatar_data.id % 5] : 'avatar-color-default' %}
+ 	{% if avatar_link %}<a href="{{ avatar_link }}" class="avatar">{% endif %}
+ 	<div class="avatar-placeholder {{ color_class }}">
+ 		{% set username = username ?: avatar_data.username %}
+ 		{% set initials = username|striptags|trim(' -+_[]', 'left')|slice(0, 1)|upper %}
+ 		<div class="avatar-initials">{% if initials %}{{ initials }}{% else %}{{ Icon('font', 'user', '', true) }}{% endif %}</div>
+ 	</div>
+ 	{% if avatar_link %}</a>{% endif %}
+ {% endif %}
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/mcp_notes_user.html release-4.0.0-a2-dev/styles/prosilver/template/mcp_notes_user.html
*** release-4.0.0-a1/styles/prosilver/template/mcp_notes_user.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/mcp_notes_user.html	Wed Apr  1 17:47:36 2026
***************
*** 11,17 ****
  
  	<div>
  		<div class="column1">
! 			<!-- IF AVATAR_HTML --><div>{AVATAR_HTML}</div><!-- ENDIF -->
  		</div>
  
  		<div class="column2">
--- 11,17 ----
  
  	<div>
  		<div class="column1">
! 			{% include 'avatar.html' with {'avatar_data': USER_AVATAR} only %}
  		</div>
  
  		<div class="column2">
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/mcp_warn_post.html release-4.0.0-a2-dev/styles/prosilver/template/mcp_warn_post.html
*** release-4.0.0-a1/styles/prosilver/template/mcp_warn_post.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/mcp_warn_post.html	Wed Apr  1 17:47:36 2026
***************
*** 11,17 ****
  
  	<div>
  		<div class="column1">
! 			<!-- IF AVATAR_HTML --><div>{AVATAR_HTML}</div><!-- ENDIF -->
  		</div>
  
  		<div class="column2">
--- 11,17 ----
  
  	<div>
  		<div class="column1">
! 			{% include 'avatar.html' with {'avatar_data': USER_AVATAR} only %}
  		</div>
  
  		<div class="column2">
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/mcp_warn_user.html release-4.0.0-a2-dev/styles/prosilver/template/mcp_warn_user.html
*** release-4.0.0-a1/styles/prosilver/template/mcp_warn_user.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/mcp_warn_user.html	Wed Apr  1 17:47:36 2026
***************
*** 11,17 ****
  
  	<div>
  		<div class="column1">
! 			<!-- IF AVATAR_HTML --><div>{AVATAR_HTML}</div><!-- ENDIF -->
  		</div>
  
  		<div class="column2">
--- 11,17 ----
  
  	<div>
  		<div class="column1">
! 			{% include 'avatar.html' with {'avatar_data': USER_AVATAR} only %}
  		</div>
  
  		<div class="column2">
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/memberlist_body.html release-4.0.0-a2-dev/styles/prosilver/template/memberlist_body.html
*** release-4.0.0-a1/styles/prosilver/template/memberlist_body.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/memberlist_body.html	Wed Apr  1 17:47:36 2026
***************
*** 26,32 ****
  		{% EVENT memberlist_body_group_desc_after %}
  
  		<p>
! 			<!-- IF AVATAR_HTML -->{AVATAR_HTML}<!-- ENDIF -->
  			{% EVENT memberlist_body_group_rank_before %}
  			{% if RANK_IMG %}<span class="rank-img">{{ RANK_IMG }}</span>{% endif %}
  			{% if GROUP_RANK %}
--- 26,32 ----
  		{% EVENT memberlist_body_group_desc_after %}
  
  		<p>
! 			{% include 'avatar.html' with {'avatar_data': AVATAR} only %}
  			{% EVENT memberlist_body_group_rank_before %}
  			{% if RANK_IMG %}<span class="rank-img">{{ RANK_IMG }}</span>{% endif %}
  			{% if GROUP_RANK %}
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/memberlist_view.html release-4.0.0-a2-dev/styles/prosilver/template/memberlist_view.html
*** release-4.0.0-a1/styles/prosilver/template/memberlist_view.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/memberlist_view.html	Wed Apr  1 17:47:36 2026
***************
*** 7,21 ****
  <form method="post" action="{S_PROFILE_ACTION}" id="viewprofile">
  <div class="panel bg1">
  	<div class="inner">
- 
- 	<!-- IF AVATAR_HTML -->
  		<dl class="left-box avatar-rank-container">
! 			<dt class="profile-avatar">{AVATAR_HTML}</dt>
! 			<!-- EVENT memberlist_view_rank_avatar_before -->
! 			<!-- IF RANK_TITLE --><dd class="profile-avatar">{RANK_TITLE}</dd><!-- ENDIF -->
! 			<!-- EVENT memberlist_view_rank_avatar_after -->
  		</dl>
- 	<!-- ENDIF -->
  
  	<dl class="left-box details profile-details">
  		<dt>
--- 7,18 ----
  <form method="post" action="{S_PROFILE_ACTION}" id="viewprofile">
  <div class="panel bg1">
  	<div class="inner">
  		<dl class="left-box avatar-rank-container">
! 			<dt class="profile-avatar">{% include 'avatar.html' with {'avatar_data': AVATAR} only %}</dt>
! 			{% EVENT memberlist_view_rank_avatar_before %}
! 			{% if RANK_TITLE %}<dd class="profile-avatar">{{ RANK_TITLE }}</dd>{% endif %}
! 			{% EVENT memberlist_view_rank_avatar_after %}
  		</dl>
  
  	<dl class="left-box details profile-details">
  		<dt>
***************
*** 28,39 ****
  			<!-- IF U_USER_BAN --> [ <a href="{U_USER_BAN}">{L_USER_BAN}</a> ]<!-- ENDIF -->
  			<!-- IF U_SWITCH_PERMISSIONS --> [ <a href="{U_SWITCH_PERMISSIONS}">{L_USE_PERMISSIONS}</a> ]<!-- ENDIF -->
  		</dd>
- 		<!-- IF not AVATAR_HTML -->
- 			<!-- EVENT memberlist_view_rank_no_avatar_before -->
- 			<!-- IF RANK_TITLE --><dt>{L_RANK}{L_COLON}</dt> <dd>{RANK_TITLE}</dd><!-- ENDIF -->
- 			{% if RANK_IMG %}<dt>{% if RANK_TITLE %}&nbsp;{% else %}{{ lang('RANK') ~ lang('COLON') }}{% endif %}</dt><dd class="rank-img">{{ RANK_IMG }}</dd>{% endif %}
- 			<!-- EVENT memberlist_view_rank_no_avatar_after -->
- 		<!-- ENDIF -->
  		<!-- IF S_USER_INACTIVE --><dt>{L_USER_IS_INACTIVE}{L_COLON}</dt> <dd>{USER_INACTIVE_REASON}</dd><!-- ENDIF -->
  		<!-- IF AGE !== '' --><dt>{L_AGE}{L_COLON}</dt> <dd>{AGE}</dd><!-- ENDIF -->
  		<!-- IF S_GROUP_OPTIONS --><dt>{L_USERGROUPS}{L_COLON}</dt> <dd><select name="g">{S_GROUP_OPTIONS}</select> <input type="submit" name="submit" value="{L_GO}" class="button1 button button-form-bold" /></dd><!-- ENDIF -->
--- 25,30 ----
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/navbar_header.html release-4.0.0-a2-dev/styles/prosilver/template/navbar_header.html
*** release-4.0.0-a1/styles/prosilver/template/navbar_header.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/navbar_header.html	Wed Apr  1 17:47:36 2026
***************
*** 113,125 ****
  
  				{% EVENT navbar_header_user_profile_prepend %}
  
! 				<li id="username_logged_in" class="rightside {% if CURRENT_USER_AVATAR_HTML %} no-bulletin{% endif %}">
  
  					{% EVENT navbar_header_username_prepend %}
  
  					<div class="header-profile dropdown-container">
  						<a href="{{ U_PROFILE }}" class="header-avatar dropdown-trigger">
! 							{% if CURRENT_USER_AVATAR_HTML %}{{ CURRENT_USER_AVATAR_HTML }} {% endif %}
  							{{ CURRENT_USERNAME_SIMPLE }}
  							{{ Icon('font', 'caret-down', '', true, '', {'style': 'color: #' ~ CURRENT_USER_GROUP_COLOR}) }}
  						</a>
--- 113,125 ----
  
  				{% EVENT navbar_header_user_profile_prepend %}
  
! 				<li id="username_logged_in" class="rightside {% if CURRENT_USER_AVATAR %} no-bulletin{% endif %}">
  
  					{% EVENT navbar_header_username_prepend %}
  
  					<div class="header-profile dropdown-container">
  						<a href="{{ U_PROFILE }}" class="header-avatar dropdown-trigger">
! 							{% if CURRENT_USER_AVATAR %}{% include 'avatar.html' with {'avatar_data': CURRENT_USER_AVATAR} only %} {% endif %}
  							{{ CURRENT_USERNAME_SIMPLE }}
  							{{ Icon('font', 'caret-down', '', true, '', {'style': 'color: #' ~ CURRENT_USER_GROUP_COLOR}) }}
  						</a>
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/notification_dropdown.html release-4.0.0-a2-dev/styles/prosilver/template/notification_dropdown.html
*** release-4.0.0-a1/styles/prosilver/template/notification_dropdown.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/notification_dropdown.html	Wed Apr  1 17:47:36 2026
***************
*** 22,28 ****
  					<!-- IF notifications.URL -->
  						<a class="notification-block" href="<!-- IF notifications.UNREAD -->{notifications.U_MARK_READ}" data-real-url="{notifications.URL}<!-- ELSE -->{notifications.URL}<!-- ENDIF -->">
  					<!-- ENDIF -->
! 						<!-- IF notifications.AVATAR_HTML -->{notifications.AVATAR_HTML}<!-- ELSE --><img class="avatar notification-avatar" src="{{ NO_AVATAR_SOURCE }}" alt="" /><!-- ENDIF -->
  						<div class="notification-text">
  							<p class="notification-title">{notifications.FORMATTED_TITLE}</p>
  							<!-- IF notifications.REFERENCE --><p class="notification-reference">{notifications.REFERENCE}</p><!-- ENDIF -->
--- 22,28 ----
  					<!-- IF notifications.URL -->
  						<a class="notification-block" href="<!-- IF notifications.UNREAD -->{notifications.U_MARK_READ}" data-real-url="{notifications.URL}<!-- ELSE -->{notifications.URL}<!-- ENDIF -->">
  					<!-- ENDIF -->
! 						{% include 'avatar.html' with {'avatar_data': notifications.AVATAR} only %}
  						<div class="notification-text">
  							<p class="notification-title">{notifications.FORMATTED_TITLE}</p>
  							<!-- IF notifications.REFERENCE --><p class="notification-reference">{notifications.REFERENCE}</p><!-- ENDIF -->
***************
*** 47,53 ****
  		{% if NOTIFICATIONS_WEBPUSH_ENABLE and notification_types is not defined %}
  			<div class="footer webpush-subscribe">
  				<span class="ellipsis-text">{{ lang('NOTIFY_WEB_PUSH_ENABLE') ~ lang('COLON') }}</span>
! 				<button id="subscribe_webpush" name="subscribe_webpush" class="notification-subscribe-toggle" data-l-err="{{ lang('INFORMATION') }}" data-l-msg="{{ lang('NOTIFY_WEB_PUSH_DENIED') }}">{{ Icon('svg', 'toggle-off', lang('NOTIFY_WEB_PUSH_SUBSCRIBE'), true, 'push-subscribe-toggle-icon toggle-off') }}</button>
  				<button id="unsubscribe_webpush" name="unsubscribe_webpush" class="notification-subscribe-toggle hidden">{{ Icon('svg', 'toggle-on', lang('NOTIFY_WEB_PUSH_SUBSCRIBED'), true, 'push-subscribe-toggle-icon toggle-on') }}</button>
  			</div>
  		{% endif %}
--- 47,53 ----
  		{% if NOTIFICATIONS_WEBPUSH_ENABLE and notification_types is not defined %}
  			<div class="footer webpush-subscribe">
  				<span class="ellipsis-text">{{ lang('NOTIFY_WEB_PUSH_ENABLE') ~ lang('COLON') }}</span>
! 				<button id="subscribe_webpush" name="subscribe_webpush" class="notification-subscribe-toggle" data-l-err="{{ lang('INFORMATION')|e('html_attr') }}" data-l-msg="{{ lang('NOTIFY_WEB_PUSH_DENIED')|e('html_attr') }}" data-disabled-msg="{{ lang('NOTIFY_WEB_PUSH_DISABLED')|e('html_attr') }}">{{ Icon('svg', 'toggle-off', lang('NOTIFY_WEB_PUSH_SUBSCRIBE'), true, 'push-subscribe-toggle-icon toggle-off') }}</button>
  				<button id="unsubscribe_webpush" name="unsubscribe_webpush" class="notification-subscribe-toggle hidden">{{ Icon('svg', 'toggle-on', lang('NOTIFY_WEB_PUSH_SUBSCRIBED'), true, 'push-subscribe-toggle-icon toggle-on') }}</button>
  			</div>
  		{% endif %}
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/overall_header.html release-4.0.0-a2-dev/styles/prosilver/template/overall_header.html
*** release-4.0.0-a1/styles/prosilver/template/overall_header.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/overall_header.html	Wed Apr  1 17:47:36 2026
***************
*** 6,11 ****
--- 6,12 ----
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-title" content="{{ SITE_SHORTNAME }}">
+ <meta name="mobile-web-app-capable" content="yes">
  {META}
  <title><!-- IF UNREAD_NOTIFICATIONS_COUNT -->({UNREAD_NOTIFICATIONS_COUNT}) <!-- ENDIF --><!-- IF not S_VIEWTOPIC and not S_VIEWFORUM -->{SITENAME} - <!-- ENDIF --><!-- IF S_IN_MCP -->{L_MCP} - <!-- ELSEIF S_IN_UCP -->{L_UCP} - <!-- ENDIF -->{PAGE_TITLE}<!-- IF S_VIEWTOPIC or S_VIEWFORUM --> - {SITENAME}<!-- ENDIF --></title>
  
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/posting_buttons.html release-4.0.0-a2-dev/styles/prosilver/template/posting_buttons.html
*** release-4.0.0-a1/styles/prosilver/template/posting_buttons.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/posting_buttons.html	Wed Apr  1 17:47:36 2026
***************
*** 10,15 ****
--- 10,17 ----
  	var bbtags = new Array('[b]','[/b]','[i]','[/i]','[u]','[/u]','[quote]','[/quote]','[code]','[/code]','[list]','[/list]','[list=]','[/list]','[img]','[/img]','[url]','[/url]','[size=]','[/size]'<!-- BEGIN custom_tags -->, {custom_tags.BBCODE_NAME}<!-- END custom_tags -->);
  	var imageTag = false;
  
+ 	var mention_no_match_found = '<li>{{ lang('MENTION_NO_MATCH_FOUND')|e('js') }}</li>';
+ 
  	function change_palette()
  	{
  		phpbb.toggleDisplay('colour_palette');
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/ucp_avatar_options.html release-4.0.0-a2-dev/styles/prosilver/template/ucp_avatar_options.html
*** release-4.0.0-a1/styles/prosilver/template/ucp_avatar_options.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/ucp_avatar_options.html	Wed Apr  1 17:47:36 2026
***************
*** 8,14 ****
  	<!-- IF ERROR --><p class="error">{ERROR}</p><!-- ENDIF -->
  		<dl>
  			<dt><label>{L_CURRENT_IMAGE}{L_COLON}</label><br /><span>{L_AVATAR_EXPLAIN}</span></dt>
! 			<dd class="c-avatar-box"><!-- IF AVATAR -->{AVATAR_HTML}<!-- ELSE --><img src="{{ NO_AVATAR_SOURCE }}" alt="" /><!-- ENDIF --></dd>
  			<dd><label for="avatar_delete"><input type="checkbox" name="avatar_delete" id="avatar_delete" /> {L_DELETE_AVATAR}</label></dd>
  		</dl>
  	</fieldset>
--- 8,14 ----
  	<!-- IF ERROR --><p class="error">{ERROR}</p><!-- ENDIF -->
  		<dl>
  			<dt><label>{L_CURRENT_IMAGE}{L_COLON}</label><br /><span>{L_AVATAR_EXPLAIN}</span></dt>
! 			<dd class="c-avatar-box">{% include 'avatar.html' with {'avatar_data': AVATAR} only %}</dd>
  			<dd><label for="avatar_delete"><input type="checkbox" name="avatar_delete" id="avatar_delete" /> {L_DELETE_AVATAR}</label></dd>
  		</dl>
  	</fieldset>
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/ucp_notifications.html release-4.0.0-a2-dev/styles/prosilver/template/ucp_notifications.html
*** release-4.0.0-a1/styles/prosilver/template/ucp_notifications.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/ucp_notifications.html	Wed Apr  1 17:47:36 2026
***************
*** 40,46 ****
  							<dl>
  								<dt>
  									<div class="list-inner notification-item">
! 										<!-- IF notification_list.AVATAR -->{notification_list.AVATAR_HTML}<!-- ELSE --><img class="avatar notification-avatar" src="{{ NO_AVATAR_SOURCE }}" alt="" /><!-- ENDIF -->
  										<div class="notification-text">
  											<!-- IF notification_list.URL --><a href="<!-- IF notification_list.UNREAD -->{notification_list.U_MARK_READ}<!-- ELSE -->{notification_list.URL}<!-- ENDIF -->"><!-- ENDIF -->
  												<p class="notification-title">{notification_list.FORMATTED_TITLE}<!-- IF notification_list.REFERENCE --> {notification_list.REFERENCE}<!-- ENDIF --></p>
--- 40,46 ----
  							<dl>
  								<dt>
  									<div class="list-inner notification-item">
! 										{% include 'avatar.html' with {'avatar_data': notification_list.AVATAR} only %}
  										<div class="notification-text">
  											<!-- IF notification_list.URL --><a href="<!-- IF notification_list.UNREAD -->{notification_list.U_MARK_READ}<!-- ELSE -->{notification_list.URL}<!-- ENDIF -->"><!-- ENDIF -->
  												<p class="notification-title">{notification_list.FORMATTED_TITLE}<!-- IF notification_list.REFERENCE --> {notification_list.REFERENCE}<!-- ENDIF --></p>
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/ucp_notifications_options.html release-4.0.0-a2-dev/styles/prosilver/template/ucp_notifications_options.html
*** release-4.0.0-a1/styles/prosilver/template/ucp_notifications_options.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/ucp_notifications_options.html	Wed Apr  1 17:47:36 2026
***************
*** 10,16 ****
  					<dl>
  						<dt><label for="subscribe_webpush">{{ lang('NOTIFY_WEBPUSH_ENABLE') ~ lang('COLON') }}</label><br><span>{{ lang('NOTIFY_WEBPUSH_ENABLE_EXPLAIN') }}</span></dt>
  						<dd>
! 							<input id="subscribe_webpush" type="submit" name="subscribe_webpush" value="{{ lang('NOTIFY_WEBPUSH_SUBSCRIBE') }}" class="button1 button button-form" data-l-err="{{ lang('INFORMATION') }}" data-l-msg="{{ lang('NOTIFY_WEB_PUSH_DENIED') }}" data-disabled-msg="{{ lang('NOTIFY_WEB_PUSH_DISABLED') }}">
  							<input id="unsubscribe_webpush" type="submit" name="unsubscribe_webpush" value="{{ lang('NOTIFY_WEBPUSH_UNSUBSCRIBE') }}" class="button1 button button-form hidden">
  						</dd>
  					</dl>
--- 10,16 ----
  					<dl>
  						<dt><label for="subscribe_webpush">{{ lang('NOTIFY_WEBPUSH_ENABLE') ~ lang('COLON') }}</label><br><span>{{ lang('NOTIFY_WEBPUSH_ENABLE_EXPLAIN') }}</span></dt>
  						<dd>
! 							<input id="subscribe_webpush" type="submit" name="subscribe_webpush" value="{{ lang('NOTIFY_WEBPUSH_SUBSCRIBE') }}" class="button1 button button-form" data-l-err="{{ lang('INFORMATION')|e('html_attr') }}" data-l-msg="{{ lang('NOTIFY_WEB_PUSH_DENIED')|e('html_attr') }}" data-disabled-msg="{{ lang('NOTIFY_WEB_PUSH_DISABLED')|e('html_attr') }}">
  							<input id="unsubscribe_webpush" type="submit" name="unsubscribe_webpush" value="{{ lang('NOTIFY_WEBPUSH_UNSUBSCRIBE') }}" class="button1 button button-form hidden">
  						</dd>
  					</dl>
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/ucp_notifications_webpush.html release-4.0.0-a2-dev/styles/prosilver/template/ucp_notifications_webpush.html
*** release-4.0.0-a1/styles/prosilver/template/ucp_notifications_webpush.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/ucp_notifications_webpush.html	Wed Apr  1 17:47:36 2026
***************
*** 3,9 ****
  		serviceWorkerUrl: '{{ U_WEBPUSH_WORKER_URL }}',
  		subscribeUrl: '{{ U_WEBPUSH_SUBSCRIBE }}',
  		unsubscribeUrl: '{{ U_WEBPUSH_UNSUBSCRIBE }}',
! 		ajaxErrorTitle: '{{ lang('AJAX_ERROR_TITLE') }}',
  		vapidPublicKey: '{{ VAPID_PUBLIC_KEY }}',
  		formTokens: {
  			creationTime: '{{ WEBPUSH_FORM_TOKENS.creation_time }}',
--- 3,9 ----
  		serviceWorkerUrl: '{{ U_WEBPUSH_WORKER_URL }}',
  		subscribeUrl: '{{ U_WEBPUSH_SUBSCRIBE }}',
  		unsubscribeUrl: '{{ U_WEBPUSH_UNSUBSCRIBE }}',
! 		ajaxErrorTitle: '{{ lang_js('AJAX_ERROR_TITLE') }}',
  		vapidPublicKey: '{{ VAPID_PUBLIC_KEY }}',
  		formTokens: {
  			creationTime: '{{ WEBPUSH_FORM_TOKENS.creation_time }}',
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/ucp_pm_viewmessage.html release-4.0.0-a2-dev/styles/prosilver/template/ucp_pm_viewmessage.html
*** release-4.0.0-a1/styles/prosilver/template/ucp_pm_viewmessage.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/ucp_pm_viewmessage.html	Wed Apr  1 17:47:36 2026
***************
*** 26,35 ****
  	<div class="inner">
  
  		<dl class="postprofile" id="profile{MESSAGE_ID}">
! 			<dt class="<!-- IF RANK_TITLE or RANK_IMG -->has-profile-rank<!-- ELSE -->no-profile-rank<!-- ENDIF --> <!-- IF AUTHOR_AVATAR_HTML -->has-avatar<!-- ELSE -->no-avatar<!-- ENDIF -->">
  				<div class="avatar-container">
  					<!-- EVENT ucp_pm_viewmessage_avatar_before -->
! 					<!-- IF AUTHOR_AVATAR_HTML --><a href="{U_MESSAGE_AUTHOR}" class="avatar">{AUTHOR_AVATAR_HTML}</a><!-- ENDIF -->
  					<!-- EVENT ucp_pm_viewmessage_avatar_after -->
  				</div>
  				{% apply spaceless %}
--- 26,35 ----
  	<div class="inner">
  
  		<dl class="postprofile" id="profile{MESSAGE_ID}">
! 			<dt class="<!-- IF RANK_TITLE or RANK_IMG -->has-profile-rank<!-- ELSE -->no-profile-rank<!-- ENDIF --> has-avatar">
  				<div class="avatar-container">
  					<!-- EVENT ucp_pm_viewmessage_avatar_before -->
! 					{% include 'avatar.html' with {'avatar_data': AUTHOR_AVATAR} only %}
  					<!-- EVENT ucp_pm_viewmessage_avatar_after -->
  				</div>
  				{% apply spaceless %}
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/template/viewtopic_body.html release-4.0.0-a2-dev/styles/prosilver/template/viewtopic_body.html
*** release-4.0.0-a1/styles/prosilver/template/viewtopic_body.html	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/template/viewtopic_body.html	Wed Apr  1 17:47:36 2026
***************
*** 140,151 ****
  		<div class="inner">
  
  		<dl class="postprofile" id="profile{postrow.POST_ID}"<!-- IF postrow.S_POST_HIDDEN --> style="display: none;"<!-- ENDIF -->>
! 			<dt class="<!-- IF postrow.RANK_TITLE or postrow.RANK_IMG -->has-profile-rank<!-- ELSE -->no-profile-rank<!-- ENDIF --> <!-- IF postrow.POSTER_AVATAR_HTML -->has-avatar<!-- ELSE -->no-avatar<!-- ENDIF -->">
  				<div class="avatar-container">
  					<!-- EVENT viewtopic_body_avatar_before -->
! 					<!-- IF postrow.POSTER_AVATAR_HTML -->
! 						<!-- IF postrow.U_POST_AUTHOR --><a href="{postrow.U_POST_AUTHOR}" class="avatar">{postrow.POSTER_AVATAR_HTML}</a><!-- ELSE --><span class="avatar">{postrow.POSTER_AVATAR}</span><!-- ENDIF -->
! 					<!-- ENDIF -->
  					<!-- EVENT viewtopic_body_avatar_after -->
  				</div>
  				<!-- EVENT viewtopic_body_post_author_before -->
--- 140,152 ----
  		<div class="inner">
  
  		<dl class="postprofile" id="profile{postrow.POST_ID}"<!-- IF postrow.S_POST_HIDDEN --> style="display: none;"<!-- ENDIF -->>
! 			<dt class="<!-- IF postrow.RANK_TITLE or postrow.RANK_IMG -->has-profile-rank<!-- ELSE -->no-profile-rank<!-- ENDIF --> has-avatar">
  				<div class="avatar-container">
  					<!-- EVENT viewtopic_body_avatar_before -->
! 					{% include 'avatar.html' with {
! 						'avatar_data': postrow.POSTER_AVATAR,
! 						'avatar_link': postrow.U_POST_AUTHOR
! 					} only %}
  					<!-- EVENT viewtopic_body_avatar_after -->
  				</div>
  				<!-- EVENT viewtopic_body_post_author_before -->
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/theme/bidi.css release-4.0.0-a2-dev/styles/prosilver/theme/bidi.css
*** release-4.0.0-a1/styles/prosilver/theme/bidi.css	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/theme/bidi.css	Wed Apr  1 17:47:36 2026
***************
*** 143,148 ****
--- 143,154 ----
  	margin-left: 5px;
  }
  
+ .rtl .notification-block .avatar-placeholder,
+ .rtl .notification-list .topiclist .notification-item .avatar-placeholder,
+ .rtl .notification-list .topiclist .notification-item .avatar {
+ 	float: right;
+ }
+ 
  .rtl .notification-menu .notifications {
  	margin-right: 50px;
  	margin-left: 0;
***************
*** 441,451 ****
  	float: right;
  }
  
- .rtl ul.topiclist dfn {
- 	right: -999px;
- 	left: auto;
- }
- 
  .rtl ul.topiclist li.row dt a.subforum {
  	background-position: right;
  	position: static;
--- 447,452 ----
***************
*** 512,518 ****
  
  .rtl .postbody,
  .rtl .postbody h3 {
! 	float: right;
  }
  
  .rtl .has-profile .postbody h3 {
--- 513,519 ----
  
  .rtl .postbody,
  .rtl .postbody h3 {
! 	float: right !important;
  }
  
  .rtl .has-profile .postbody h3 {
***************
*** 656,662 ****
  }
  
  .rtl .postprofile .avatar {
! 	float: right;
  }
  
  .rtl dl.pmlist dd {
--- 657,663 ----
  }
  
  .rtl .postprofile .avatar {
! 	float: left;
  }
  
  .rtl dl.pmlist dd {
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/theme/colours.css release-4.0.0-a2-dev/styles/prosilver/theme/colours.css
*** release-4.0.0-a1/styles/prosilver/theme/colours.css	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/theme/colours.css	Wed Apr  1 17:47:36 2026
***************
*** 128,134 ****
  }
  
  .header-profile {
! 	text-shadow: 0 0 1.75rem #eaf8ff;
  }
  
  .panel {
--- 128,134 ----
  }
  
  .header-profile {
! 	text-shadow: 0 0 0.25rem rgba(0, 0, 0, 0.5);
  }
  
  .panel {
***************
*** 991,996 ****
--- 991,1008 ----
  	background-color: #ededed;
  }
  
+ /* avatar placeholder colours */
+ .avatar-color-default { background-color: hsl(207, 15%, 80%); }
+ .avatar-color-0 { background-color: #d32f2f; }
+ .avatar-color-1 { background-color: #c2185b; }
+ .avatar-color-2 { background-color: #7b1fa2; }
+ .avatar-color-3 { background-color: #512da8; }
+ .avatar-color-4 { background-color: #303f9f; }
+ 
+ .avatar-initials {
+ 	color: #ffffff;
+ }
+ 
  /*	colours and backgrounds for forms.css */
  
  /* general form styles */
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/theme/common.css release-4.0.0-a2-dev/styles/prosilver/theme/common.css
*** release-4.0.0-a1/styles/prosilver/theme/common.css	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/theme/common.css	Wed Apr  1 17:47:36 2026
***************
*** 5,12 ****
  /* stylelint-disable selector-no-qualifying-type */
  
  html {
- 	font-size: 100%;
- 
  	/* Always show a scrollbar for short pages - stops the jump when the scrollbar appears. non-IE browsers */
  	height: 101%;
  }
--- 5,10 ----
***************
*** 154,160 ****
  	border: solid transparent;
  	border-width: 0 1px 1px;
  	border-radius: 0 0 8px 8px;
! 	min-width: 625px;
  	max-width: 1152px;
  	margin: 0 auto;
  	padding: 0 15px 15px;
--- 152,158 ----
  	border: solid transparent;
  	border-width: 0 1px 1px;
  	border-radius: 0 0 8px 8px;
! 	min-width: 320px;
  	max-width: 1152px;
  	margin: 0 auto;
  	padding: 0 15px 15px;
***************
*** 320,325 ****
--- 318,327 ----
  	margin: 0 5px 0 0;
  }
  
+ .nav-footer .responsive-menu {
+ 	display: none;
+ }
+ 
  .hasjs ul.linklist.leftside,
  .hasjs ul.linklist.rightside {
  	max-width: 48%;
***************
*** 384,396 ****
  	text-decoration: none;
  }
  
! a.header-avatar img {
  	vertical-align: middle;
  	width: auto;
  	max-height: 20px;
  	margin-bottom: 2px;
  }
  
  /* Dropdown menu
  ---------------------------------------- */
  .dropdown-container {
--- 386,405 ----
  	text-decoration: none;
  }
  
! a.header-avatar img,
! a.header-avatar .avatar-placeholder {
  	vertical-align: middle;
  	width: auto;
  	max-height: 20px;
  	margin-bottom: 2px;
  }
  
+ a.header-avatar .avatar-placeholder {
+ 	font-size: var(--ps-font-tiny);
+ 	display: inline-flex;
+ 	width: 20px;
+ }
+ 
  /* Dropdown menu
  ---------------------------------------- */
  .dropdown-container {
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/theme/content.css release-4.0.0-a2-dev/styles/prosilver/theme/content.css
*** release-4.0.0-a1/styles/prosilver/theme/content.css	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/theme/content.css	Wed Apr  1 17:47:36 2026
***************
*** 87,95 ****
  
  ul.topiclist dfn {
  	/* Labels for post/view counts */
  	position: absolute;
! 	left: -999px;
! 	width: 990px;
  }
  
  .forum-image {
--- 87,100 ----
  
  ul.topiclist dfn {
  	/* Labels for post/view counts */
+ 	border: 0;
  	position: absolute;
! 	overflow: hidden;
! 	clip: rect(0, 0, 0, 0);
! 	width: 1px;
! 	height: 1px;
! 	margin: -1px;
! 	padding: 0;
  }
  
  .forum-image {
***************
*** 805,811 ****
  }
  
  /* Post-profile avatars */
! .postprofile .has-avatar .avatar-container {
  	overflow: hidden;
  	margin-bottom: 3px;
  }
--- 810,816 ----
  }
  
  /* Post-profile avatars */
! .postprofile .avatar-container {
  	overflow: hidden;
  	margin-bottom: 3px;
  }
***************
*** 822,827 ****
--- 827,863 ----
  	height: auto !important;
  }
  
+ .avatar-placeholder {
+ 	font-size: 36px;
+ 	font-weight: bold;
+ 	border-radius: 50%;
+ 	display: flex;
+ 	justify-content: center;
+ 	align-items: center;
+ 	width: 80px;
+ 	height: 80px;
+ }
+ 
+ .avatar-initials {
+ 	line-height: 1;
+ }
+ 
+ .avatar-initials .o-icon {
+ 	font-size: 36px;
+ 	vertical-align: baseline !important;
+ 	width: auto;
+ 	height: auto;
+ }
+ 
+ .notification-block .avatar-placeholder,
+ .notification-list .topiclist .notification-item .avatar,
+ .notification-list .topiclist .notification-item .avatar-placeholder {
+ 	font-size: 22px;
+ 	float: left;
+ 	width: 50px;
+ 	height: 50px;
+ }
+ 
  .postprofile .profile-posts a {
  	font-weight: normal;
  }
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/theme/links.css release-4.0.0-a2-dev/styles/prosilver/theme/links.css
*** release-4.0.0-a1/styles/prosilver/theme/links.css	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/theme/links.css	Wed Apr  1 17:47:36 2026
***************
*** 140,145 ****
--- 140,149 ----
  	text-decoration: underline;
  }
  
+ .postprofile a.avatar:hover {
+ 	text-decoration: none;
+ }
+ 
  /* Profile searchresults */
  .search .postprofile a {
  	font-weight: normal;
diff -x images -crNEB release-4.0.0-a1/styles/prosilver/theme/responsive.css release-4.0.0-a2-dev/styles/prosilver/theme/responsive.css
*** release-4.0.0-a1/styles/prosilver/theme/responsive.css	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/styles/prosilver/theme/responsive.css	Wed Apr  1 17:47:36 2026
***************
*** 182,189 ****
  	.wrap {
  		border: none;
  		border-radius: 0;
! 		min-width: 390px;
! 		margin: 0 !important;
  		padding: 0 5px;
  	}
  
--- 182,188 ----
  	.wrap {
  		border: none;
  		border-radius: 0;
! 		box-sizing: border-box;
  		padding: 0 5px;
  	}
  
***************
*** 575,580 ****
--- 574,585 ----
  		max-height: 32px;
  	}
  
+ 	.postprofile .avatar-placeholder {
+ 		font-size: 1rem;
+ 		width: 32px;
+ 		height: 32px;
+ 	}
+ 
  	.has-profile .postbody h3 {
  		margin-right: 0 !important;
  		margin-left: 0 !important;
***************
*** 781,787 ****
  		display: block !important;
  	}
  
- 	.nav-footer.linklist .responsive-menu,
  	.nav-main.linklist .responsive-menu .in-menu {
  		display: none !important;
  	}
--- 786,791 ----
***************
*** 863,869 ****
  	}
  }
  
! @media (max-width: 1220px) {
  	.wrap {
  		margin: 0 12px;
  	}
--- 867,873 ----
  	}
  }
  
! @media (min-width: 751px) and (max-width: 1200px) {
  	.wrap {
  		margin: 0 12px;
  	}
diff -x images -crNEB release-4.0.0-a1/ucp.php release-4.0.0-a2-dev/ucp.php
*** release-4.0.0-a1/ucp.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/ucp.php	Wed Apr  1 17:47:36 2026
***************
*** 47,52 ****
--- 47,55 ----
  $module = new p_master();
  $default = false;
  
+ /** @var \phpbb\controller\helper $controller_helper */
+ $controller_helper = $phpbb_container->get('controller.helper');
+ 
  // Basic "global" modes
  switch ($mode)
  {
***************
*** 54,60 ****
  		$module->load('ucp', 'activate');
  		$module->display($user->lang['UCP_ACTIVATE']);
  
! 		redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
  	break;
  
  	case 'resend_act':
--- 57,63 ----
  		$module->load('ucp', 'activate');
  		$module->display($user->lang['UCP_ACTIVATE']);
  
! 		redirect($controller_helper->route('phpbb_index_controller'));
  	break;
  
  	case 'resend_act':
***************
*** 63,78 ****
  	break;
  
  	case 'sendpassword':
- 		/** @var \phpbb\controller\helper $controller_helper */
- 		$controller_helper = $phpbb_container->get('controller.helper');
- 
  		redirect($controller_helper->route('phpbb_ucp_forgot_password_controller'));
  	break;
  
  	case 'register':
  		if ($user->data['is_registered'] || isset($_REQUEST['not_agreed']))
  		{
! 			redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
  		}
  
  		$module->load('ucp', 'register');
--- 66,78 ----
  	break;
  
  	case 'sendpassword':
  		redirect($controller_helper->route('phpbb_ucp_forgot_password_controller'));
  	break;
  
  	case 'register':
  		if ($user->data['is_registered'] || isset($_REQUEST['not_agreed']))
  		{
! 			redirect($controller_helper->route('phpbb_index_controller'));
  		}
  
  		$module->load('ucp', 'register');
***************
*** 86,92 ****
  	case 'login':
  		if ($user->data['is_registered'])
  		{
! 			redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
  		}
  
  		login_box($request->variable('redirect', "index.$phpEx"));
--- 86,92 ----
  	case 'login':
  		if ($user->data['is_registered'])
  		{
! 			redirect($controller_helper->route('phpbb_index_controller'));
  		}
  
  		login_box($request->variable('redirect', "index.$phpEx"));
***************
*** 95,101 ****
  	case 'login_link':
  		if ($user->data['is_registered'])
  		{
! 			redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
  		}
  
  		$module->load('ucp', 'login_link');
--- 95,101 ----
  	case 'login_link':
  		if ($user->data['is_registered'])
  		{
! 			redirect($controller_helper->route('phpbb_index_controller'));
  		}
  
  		$module->load('ucp', 'login_link');
***************
*** 109,121 ****
  		}
  		else if ($user->data['user_id'] != ANONYMOUS)
  		{
! 			meta_refresh(3, append_sid("{$phpbb_root_path}index.$phpEx"));
  
! 			$message = $user->lang['LOGOUT_FAILED'] . '<br /><br />' . sprintf($user->lang['RETURN_INDEX'], '<a href="' . append_sid("{$phpbb_root_path}index.$phpEx") . '">', '</a> ');
  			trigger_error($message);
  		}
  
! 		redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
  	break;
  
  	case 'terms':
--- 109,121 ----
  		}
  		else if ($user->data['user_id'] != ANONYMOUS)
  		{
! 			meta_refresh(3, $controller_helper->route('phpbb_index_controller'));
  
! 			$message = $user->lang['LOGOUT_FAILED'] . '<br /><br />' . sprintf($user->lang['RETURN_INDEX'], '<a href="' . $controller_helper->route('phpbb_index_controller') . '">', '</a> ');
  			trigger_error($message);
  		}
  
! 		redirect($controller_helper->route('phpbb_index_controller'));
  	break;
  
  	case 'terms':
***************
*** 128,134 ****
  		{
  			if ($user->data['is_registered'])
  			{
! 				redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
  			}
  
  			login_box();
--- 128,134 ----
  		{
  			if ($user->data['is_registered'])
  			{
! 				redirect($controller_helper->route('phpbb_index_controller'));
  			}
  
  			login_box();
***************
*** 154,162 ****
  	break;
  
  	case 'delete_cookies':
- 		/** @var \phpbb\controller\helper $controller_helper */
- 		$controller_helper = $phpbb_container->get('controller.helper');
- 
  		// Redirect to controller
  		redirect($controller_helper->route('phpbb_ucp_delete_cookies_controller'));
  	break;
--- 154,159 ----
***************
*** 174,180 ****
  
  		if (!$auth->acl_get('a_switchperm') || !$user_row || $user_id == $user->data['user_id'] || !check_link_hash($request->variable('hash', ''), 'switchperm'))
  		{
! 			redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
  		}
  
  		include($phpbb_root_path . 'includes/acp/auth.' . $phpEx);
--- 170,176 ----
  
  		if (!$auth->acl_get('a_switchperm') || !$user_row || $user_id == $user->data['user_id'] || !check_link_hash($request->variable('hash', ''), 'switchperm'))
  		{
! 			redirect($controller_helper->route('phpbb_index_controller'));
  		}
  
  		include($phpbb_root_path . 'includes/acp/auth.' . $phpEx);
***************
*** 182,193 ****
  		$auth_admin = new auth_admin();
  		if (!$auth_admin->ghost_permissions($user_id, $user->data['user_id']))
  		{
! 			redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
  		}
  
  		$phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ACL_TRANSFER_PERMISSIONS', false, array($user_row['username']));
  
! 		$message = sprintf($user->lang['PERMISSIONS_TRANSFERRED'], $user_row['username']) . '<br /><br />' . sprintf($user->lang['RETURN_INDEX'], '<a href="' . append_sid("{$phpbb_root_path}index.$phpEx") . '">', '</a>');
  
  		/**
  		* Event to run code after permissions are switched
--- 178,189 ----
  		$auth_admin = new auth_admin();
  		if (!$auth_admin->ghost_permissions($user_id, $user->data['user_id']))
  		{
! 			redirect($controller_helper->route('phpbb_index_controller'));
  		}
  
  		$phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ACL_TRANSFER_PERMISSIONS', false, array($user_row['username']));
  
! 		$message = sprintf($user->lang['PERMISSIONS_TRANSFERRED'], $user_row['username']) . '<br /><br />' . sprintf($user->lang['RETURN_INDEX'], '<a href="' . $controller_helper->route('phpbb_index_controller') . '">', '</a>');
  
  		/**
  		* Event to run code after permissions are switched
***************
*** 206,215 ****
  	break;
  
  	case 'restore_perm':
- 
  		if (!$user->data['user_perm_from'] || !$auth->acl_get('a_switchperm'))
  		{
! 			redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
  		}
  
  		$auth->acl_cache($user->data);
--- 202,210 ----
  	break;
  
  	case 'restore_perm':
  		if (!$user->data['user_perm_from'] || !$auth->acl_get('a_switchperm'))
  		{
! 			redirect($controller_helper->route('phpbb_index_controller'));
  		}
  
  		$auth->acl_cache($user->data);
***************
*** 223,229 ****
  
  		$phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ACL_RESTORE_PERMISSIONS', false, array($username));
  
! 		$message = $user->lang['PERMISSIONS_RESTORED'] . '<br /><br />' . sprintf($user->lang['RETURN_INDEX'], '<a href="' . append_sid("{$phpbb_root_path}index.$phpEx") . '">', '</a>');
  
  		/**
  		* Event to run code after permissions are restored
--- 218,224 ----
  
  		$phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ACL_RESTORE_PERMISSIONS', false, array($username));
  
! 		$message = $user->lang['PERMISSIONS_RESTORED'] . '<br /><br />' . sprintf($user->lang['RETURN_INDEX'], '<a href="' . $controller_helper->route('phpbb_index_controller') . '">', '</a>');
  
  		/**
  		* Event to run code after permissions are restored
***************
*** 256,262 ****
  {
  	if ($user->data['is_bot'])
  	{
! 		redirect(append_sid("{$phpbb_root_path}index.$phpEx"));
  	}
  
  	if ($id == 'pm' && $mode == 'view' && isset($_GET['p']))
--- 251,257 ----
  {
  	if ($user->data['is_bot'])
  	{
! 		redirect($controller_helper->route('phpbb_index_controller'));
  	}
  
  	if ($id == 'pm' && $mode == 'view' && isset($_GET['p']))
diff -x images -crNEB release-4.0.0-a1/viewonline.php release-4.0.0-a2-dev/viewonline.php
*** release-4.0.0-a1/viewonline.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/viewonline.php	Wed Apr  1 17:47:36 2026
***************
*** 11,16 ****
--- 11,18 ----
  *
  */
  
+ use Symfony\Component\HttpFoundation\RedirectResponse;
+ 
  /**
  * @ignore
  */
***************
*** 25,488 ****
  $user->setup('memberlist');
  
  // Get and set some variables
! $mode		= $request->variable('mode', '');
! $session_id	= $request->variable('s', '');
! $start		= $request->variable('start', 0);
! $sort_key	= $request->variable('sk', 'b');
! $sort_dir	= $request->variable('sd', 'd');
! $show_guests	= ($config['load_online_guests']) ? $request->variable('sg', 0) : 0;
! 
! // Can this user view profiles/memberlist?
! if (!$auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel'))
! {
! 	if ($user->data['user_id'] != ANONYMOUS)
! 	{
! 		send_status_line(403, 'Forbidden');
! 		trigger_error('NO_VIEW_USERS');
! 	}
! 
! 	login_box('', $user->lang['LOGIN_EXPLAIN_VIEWONLINE']);
! }
! 
! /* @var $pagination \phpbb\pagination */
! $pagination = $phpbb_container->get('pagination');
  
- /* @var $viewonline_helper \phpbb\viewonline_helper */
- $viewonline_helper = $phpbb_container->get('viewonline_helper');
  
! $sort_key_text = array('a' => $user->lang['SORT_USERNAME'], 'b' => $user->lang['SORT_JOINED'], 'c' => $user->lang['SORT_LOCATION']);
! $sort_key_sql = array('a' => 'u.username_clean', 'b' => 's.session_time', 'c' => 's.session_page');
! 
! // Sorting and order
! if (!isset($sort_key_text[$sort_key]))
! {
! 	$sort_key = 'b';
! }
! 
! $order_by = $sort_key_sql[$sort_key] . ' ' . (($sort_dir == 'a') ? 'ASC' : 'DESC');
  
! // Whois requested
! if ($mode == 'whois' && $auth->acl_get('a_') && $session_id)
  {
! 	if (!function_exists('user_get_id_name'))
! 	{
! 		include($phpbb_root_path . 'includes/functions_user.' . $phpEx);
! 	}
! 
! 	$sql = 'SELECT u.user_id, u.username, u.user_type, s.session_ip
! 		FROM ' . USERS_TABLE . ' u, ' . SESSIONS_TABLE . " s
! 		WHERE s.session_id = '" . $db->sql_escape($session_id) . "'
! 			AND	u.user_id = s.session_user_id";
! 	$result = $db->sql_query($sql);
! 
! 	if ($row = $db->sql_fetchrow($result))
! 	{
! 		$template->assign_var('WHOIS', user_ipwhois($row['session_ip']));
! 	}
! 	$db->sql_freeresult($result);
! 
! 	// Output the page
! 	page_header($user->lang['WHO_IS_ONLINE']);
! 
! 	$template->set_filenames(array(
! 		'body' => 'viewonline_whois.html')
  	);
- 	make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"));
- 
- 	page_footer();
  }
! 
! $user->update_session_infos();
! 
! // Forum info
! $sql_ary = array(
! 	'SELECT'	=> 'f.forum_id, f.forum_name, f.parent_id, f.forum_type, f.left_id, f.right_id',
! 	'FROM'		=> array(
! 		FORUMS_TABLE	=> 'f',
! 	),
! 	'ORDER_BY'	=> 'f.left_id ASC',
! );
! 
! /**
! * Modify the forum data SQL query for getting additional fields if needed
! *
! * @event core.viewonline_modify_forum_data_sql
! * @var	array	sql_ary			The SQL array
! * @since 3.1.5-RC1
! */
! $vars = array('sql_ary');
! extract($phpbb_dispatcher->trigger_event('core.viewonline_modify_forum_data_sql', compact($vars)));
! 
! $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary), 600);
! unset($sql_ary);
! 
! $forum_data = array();
! while ($row = $db->sql_fetchrow($result))
! {
! 	$forum_data[$row['forum_id']] = $row;
! }
! $db->sql_freeresult($result);
! 
! $guest_counter = 0;
! 
! // Get number of online guests (if we do not display them)
! if (!$show_guests)
  {
! 	switch ($db->get_sql_layer())
! 	{
! 		case 'sqlite3':
! 			$sql = 'SELECT COUNT(session_ip) as num_guests
! 				FROM (
! 					SELECT DISTINCT session_ip
! 						FROM ' . SESSIONS_TABLE . '
! 						WHERE session_user_id = ' . ANONYMOUS . '
! 							AND session_time >= ' . (time() - ($config['load_online_time'] * 60)) .
! 				')';
! 		break;
! 
! 		default:
! 			$sql = 'SELECT COUNT(DISTINCT session_ip) as num_guests
! 				FROM ' . SESSIONS_TABLE . '
! 				WHERE session_user_id = ' . ANONYMOUS . '
! 					AND session_time >= ' . (time() - ($config['load_online_time'] * 60));
! 		break;
! 	}
! 	$result = $db->sql_query($sql);
! 	$guest_counter = (int) $db->sql_fetchfield('num_guests');
! 	$db->sql_freeresult($result);
! }
! 
! // Get user list
! $sql_ary = array(
! 	'SELECT'	=> 'u.user_id, u.username, u.username_clean, u.user_type, u.user_colour, s.session_id, s.session_time, s.session_page, s.session_ip, s.session_browser, s.session_viewonline, s.session_forum_id',
! 	'FROM'		=> array(
! 		USERS_TABLE		=> 'u',
! 		SESSIONS_TABLE	=> 's',
! 	),
! 	'WHERE'		=> 'u.user_id = s.session_user_id
! 		AND s.session_time >= ' . (time() - ($config['load_online_time'] * 60)) .
! 		((!$show_guests) ? ' AND s.session_user_id <> ' . ANONYMOUS : ''),
! 	'ORDER_BY'	=> $order_by,
! );
! 
! /**
! * Modify the SQL query for getting the user data to display viewonline list
! *
! * @event core.viewonline_modify_sql
! * @var	array	sql_ary			The SQL array
! * @var	bool	show_guests		Do we display guests in the list
! * @var	int		guest_counter	Number of guests displayed
! * @var	array	forum_data		Array with forum data
! * @since 3.1.0-a1
! * @changed 3.1.0-a2 Added vars guest_counter and forum_data
! */
! $vars = array('sql_ary', 'show_guests', 'guest_counter', 'forum_data');
! extract($phpbb_dispatcher->trigger_event('core.viewonline_modify_sql', compact($vars)));
! 
! $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary));
! $session_data_rowset = $db->sql_fetchrowset($result);
! $db->sql_freeresult($result);
! 
! $prev_id = $prev_ip = $user_list = array();
! $logged_visible_online = $logged_hidden_online = $counter = 0;
! 
! /** @var \phpbb\controller\helper $controller_helper */
! $controller_helper = $phpbb_container->get('controller.helper');
! 
! /** @var \phpbb\group\helper $group_helper */
! $group_helper = $phpbb_container->get('group_helper');
! 
! // Get forum IDs for session pages which have only 't' parameter
! $viewonline_helper->get_forum_ids($session_data_rowset);
! 
! foreach ($session_data_rowset as $row)
! {
! 	if ($row['user_id'] != ANONYMOUS && !isset($prev_id[$row['user_id']]))
! 	{
! 		$view_online = $s_user_hidden = false;
! 		$user_colour = ($row['user_colour']) ? ' style="color:#' . $row['user_colour'] . '" class="username-coloured"' : '';
! 
! 		$username_full = ($row['user_type'] != USER_IGNORE) ? get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']) : '<span' . $user_colour . '>' . $row['username'] . '</span>';
! 
! 		if (!$row['session_viewonline'])
! 		{
! 			$view_online = ($auth->acl_get('u_viewonline') || $row['user_id'] === $user->data['user_id']) ? true : false;
! 			$logged_hidden_online++;
! 
! 			$username_full = '<em>' . $username_full . '</em>';
! 			$s_user_hidden = true;
! 		}
! 		else
! 		{
! 			$view_online = true;
! 			$logged_visible_online++;
! 		}
! 
! 		$prev_id[$row['user_id']] = 1;
! 
! 		if ($view_online)
! 		{
! 			$counter++;
! 		}
! 
! 		if (!$view_online || $counter > $start + $config['topics_per_page'] || $counter <= $start)
! 		{
! 			continue;
! 		}
! 	}
! 	else if ($show_guests && $row['user_id'] == ANONYMOUS && !isset($prev_ip[$row['session_ip']]))
! 	{
! 		$prev_ip[$row['session_ip']] = 1;
! 		$guest_counter++;
! 		$counter++;
! 
! 		if ($counter > $start + $config['topics_per_page'] || $counter <= $start)
! 		{
! 			continue;
! 		}
! 
! 		$s_user_hidden = false;
! 		$username_full = get_username_string('full', $row['user_id'], $user->lang['GUEST']);
! 	}
! 	else
! 	{
! 		continue;
! 	}
! 
! 	$on_page = $viewonline_helper->get_user_page($row['session_page']);
! 
! 	switch ($on_page[1])
! 	{
! 		case 'index':
! 			$location = $user->lang['INDEX'];
! 			$location_url = append_sid("{$phpbb_root_path}index.$phpEx");
! 		break;
! 
! 		case $phpbb_adm_relative_path . 'index':
! 			$location = $user->lang['ACP'];
! 			$location_url = append_sid("{$phpbb_root_path}index.$phpEx");
! 		break;
! 
! 		case 'posting':
! 		case 'viewforum':
! 		case 'viewtopic':
! 			$forum_id = $row['session_forum_id'];
! 
! 			if ($forum_id && $auth->acl_get('f_list', $forum_id))
! 			{
! 				$location = '';
! 				$location_url = append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id);
! 
! 				if ($forum_data[$forum_id]['forum_type'] == FORUM_LINK)
! 				{
! 					$location = sprintf($user->lang['READING_LINK'], $forum_data[$forum_id]['forum_name']);
! 					break;
! 				}
! 
! 				switch ($on_page[1])
! 				{
! 					case 'posting':
! 						preg_match('#mode=([a-z]+)#', $row['session_page'], $on_page);
! 						$posting_mode = (!empty($on_page[1])) ? $on_page[1] : '';
! 
! 						switch ($posting_mode)
! 						{
! 							case 'reply':
! 							case 'quote':
! 								$location = sprintf($user->lang['REPLYING_MESSAGE'], $forum_data[$forum_id]['forum_name']);
! 							break;
! 
! 							default:
! 								$location = sprintf($user->lang['POSTING_MESSAGE'], $forum_data[$forum_id]['forum_name']);
! 							break;
! 						}
! 					break;
! 
! 					case 'viewtopic':
! 						$location = sprintf($user->lang['READING_TOPIC'], $forum_data[$forum_id]['forum_name']);
! 					break;
! 
! 					case 'viewforum':
! 						$location = sprintf($user->lang['READING_FORUM'], $forum_data[$forum_id]['forum_name']);
! 					break;
! 				}
! 			}
! 			else
! 			{
! 				$location = $user->lang['INDEX'];
! 				$location_url = append_sid("{$phpbb_root_path}index.$phpEx");
! 			}
! 		break;
! 
! 		case 'search':
! 			$location = $user->lang['SEARCHING_FORUMS'];
! 			$location_url = append_sid("{$phpbb_root_path}search.$phpEx");
! 		break;
! 
! 		case 'viewonline':
! 			$location = $user->lang['VIEWING_ONLINE'];
! 			$location_url = append_sid("{$phpbb_root_path}viewonline.$phpEx");
! 		break;
! 
! 		case 'memberlist':
! 			$location_url = append_sid("{$phpbb_root_path}memberlist.$phpEx");
! 
! 			if (strpos($row['session_page'], 'mode=viewprofile') !== false)
! 			{
! 				$location = $user->lang['VIEWING_MEMBER_PROFILE'];
! 			}
! 			else if (strpos($row['session_page'], 'mode=contactadmin') !== false)
! 			{
! 				$location = $user->lang['VIEWING_CONTACT_ADMIN'];
! 				$location_url = append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin');
! 			}
! 			else
! 			{
! 				$location = $user->lang['VIEWING_MEMBERS'];
! 			}
! 		break;
! 
! 		case 'mcp':
! 			$location = $user->lang['VIEWING_MCP'];
! 			$location_url = append_sid("{$phpbb_root_path}index.$phpEx");
! 		break;
! 
! 		case 'ucp':
! 			$location = $user->lang['VIEWING_UCP'];
! 
! 			// Grab some common modules
! 			$url_params = array(
! 				'mode=register'		=> 'VIEWING_REGISTER',
! 				'i=pm&mode=compose'	=> 'POSTING_PRIVATE_MESSAGE',
! 				'i=pm&'				=> 'VIEWING_PRIVATE_MESSAGES',
! 				'i=profile&'		=> 'CHANGING_PROFILE',
! 				'i=prefs&'			=> 'CHANGING_PREFERENCES',
! 			);
! 
! 			foreach ($url_params as $param => $lang)
! 			{
! 				if (strpos($row['session_page'], $param) !== false)
! 				{
! 					$location = $user->lang[$lang];
! 					break;
! 				}
! 			}
! 
! 			$location_url = append_sid("{$phpbb_root_path}index.$phpEx");
! 		break;
! 
! 		case 'download/file':
! 			$location = $user->lang['DOWNLOADING_FILE'];
! 			$location_url = append_sid("{$phpbb_root_path}index.$phpEx");
! 		break;
! 
! 		case 'report':
! 			$location = $user->lang['REPORTING_POST'];
! 			$location_url = append_sid("{$phpbb_root_path}index.$phpEx");
! 		break;
! 
! 		default:
! 			$location = $user->lang['INDEX'];
! 			$location_url = append_sid("{$phpbb_root_path}index.$phpEx");
! 
! 			if ($row['session_page'] === 'app.' . $phpEx . '/help/faq' ||
! 				$row['session_page'] === 'app.' . $phpEx . '/help/bbcode')
! 			{
! 				$location = $user->lang['VIEWING_FAQ'];
! 				$location_url = $controller_helper->route('phpbb_help_faq_controller');
! 			}
! 		break;
! 	}
! 
! 	/**
! 	* Overwrite the location's name and URL, which are displayed in the list
! 	*
! 	* @event core.viewonline_overwrite_location
! 	* @var	array	on_page			File name and query string
! 	* @var	array	row				Array with the users sql row
! 	* @var	string	location		Page name to displayed in the list
! 	* @var	string	location_url	Page url to displayed in the list
! 	* @var	array	forum_data		Array with forum data
! 	* @since 3.1.0-a1
! 	* @changed 3.1.0-a2 Added var forum_data
! 	*/
! 	$vars = array('on_page', 'row', 'location', 'location_url', 'forum_data');
! 	extract($phpbb_dispatcher->trigger_event('core.viewonline_overwrite_location', compact($vars)));
! 
! 	$template_row = array(
! 		'USERNAME' 			=> $row['username'],
! 		'USERNAME_COLOUR'	=> $row['user_colour'],
! 		'USERNAME_FULL'		=> $username_full,
! 		'LASTUPDATE'		=> $user->format_date($row['session_time']),
! 		'FORUM_LOCATION'	=> $location,
! 		'USER_IP'			=> ($auth->acl_get('a_')) ? (($mode == 'lookup' && $session_id == $row['session_id']) ? gethostbyaddr($row['session_ip']) : $row['session_ip']) : '',
! 		'USER_BROWSER'		=> ($auth->acl_get('a_user')) ? $row['session_browser'] : '',
! 
! 		'U_USER_PROFILE'	=> ($row['user_type'] != USER_IGNORE) ? get_username_string('profile', $row['user_id'], '') : '',
! 		'U_USER_IP'			=> append_sid("{$phpbb_root_path}viewonline.$phpEx", 'mode=lookup' . (($mode != 'lookup' || $row['session_id'] != $session_id) ? '&amp;s=' . $row['session_id'] : '') . "&amp;sg=$show_guests&amp;start=$start&amp;sk=$sort_key&amp;sd=$sort_dir"),
! 		'U_WHOIS'			=> append_sid("{$phpbb_root_path}viewonline.$phpEx", 'mode=whois&amp;s=' . $row['session_id']),
! 		'U_FORUM_LOCATION'	=> $location_url,
! 
! 		'S_USER_HIDDEN'		=> $s_user_hidden,
! 		'S_GUEST'			=> ($row['user_id'] == ANONYMOUS) ? true : false,
! 		'S_USER_TYPE'		=> $row['user_type'],
  	);
- 
- 	/**
- 	* Modify viewonline template data before it is displayed in the list
- 	*
- 	* @event core.viewonline_modify_user_row
- 	* @var	array	on_page			File name and query string
- 	* @var	array	row				Array with the users sql row
- 	* @var	array	forum_data		Array with forum data
- 	* @var	array	template_row	Array with template variables for the user row
- 	* @since 3.1.0-RC4
- 	*/
- 	$vars = array('on_page', 'row', 'forum_data', 'template_row');
- 	extract($phpbb_dispatcher->trigger_event('core.viewonline_modify_user_row', compact($vars)));
- 
- 	$template->assign_block_vars('user_row', $template_row);
  }
! unset($prev_id, $prev_ip);
! 
! $group_helper->display_legend($db, $template);
! 
! // Refreshing the page every 60 seconds...
! meta_refresh(60, append_sid("{$phpbb_root_path}viewonline.$phpEx", "sg=$show_guests&amp;sk=$sort_key&amp;sd=$sort_dir&amp;start=$start"));
! 
! $start = $pagination->validate_start($start, $config['topics_per_page'], $counter);
! $base_url = append_sid("{$phpbb_root_path}viewonline.$phpEx", "sg=$show_guests&amp;sk=$sort_key&amp;sd=$sort_dir");
! $pagination->generate_template_pagination($base_url, 'pagination', 'start', $counter, $config['topics_per_page'], $start);
! 
! $template->assign_block_vars('navlinks', array(
! 	'BREADCRUMB_NAME'	=> $user->lang('WHO_IS_ONLINE'),
! 	'U_BREADCRUMB'		=> append_sid("{$phpbb_root_path}viewonline.$phpEx"),
! ));
! 
! // Send data to template
! $template->assign_vars(array(
! 	'TOTAL_REGISTERED_USERS_ONLINE'	=> $user->lang('REG_USERS_ONLINE', (int) $logged_visible_online, $user->lang('HIDDEN_USERS_ONLINE', (int) $logged_hidden_online)),
! 	'TOTAL_GUEST_USERS_ONLINE'		=> $user->lang('GUEST_USERS_ONLINE', (int) $guest_counter),
! 
! 	'U_SORT_USERNAME'		=> append_sid("{$phpbb_root_path}viewonline.$phpEx", 'sk=a&amp;sd=' . (($sort_key == 'a' && $sort_dir == 'a') ? 'd' : 'a') . '&amp;sg=' . ((int) $show_guests)),
! 	'U_SORT_UPDATED'		=> append_sid("{$phpbb_root_path}viewonline.$phpEx", 'sk=b&amp;sd=' . (($sort_key == 'b' && $sort_dir == 'a') ? 'd' : 'a') . '&amp;sg=' . ((int) $show_guests)),
! 	'U_SORT_LOCATION'		=> append_sid("{$phpbb_root_path}viewonline.$phpEx", 'sk=c&amp;sd=' . (($sort_key == 'c' && $sort_dir == 'a') ? 'd' : 'a') . '&amp;sg=' . ((int) $show_guests)),
! 
! 	'U_SWITCH_GUEST_DISPLAY'	=> append_sid("{$phpbb_root_path}viewonline.$phpEx", 'sg=' . ((int) !$show_guests)),
! 	'L_SWITCH_GUEST_DISPLAY'	=> ($show_guests) ? $user->lang['HIDE_GUESTS'] : $user->lang['DISPLAY_GUESTS'],
! 	'S_SWITCH_GUEST_DISPLAY'	=> ($config['load_online_guests']) ? true : false,
! 	'S_VIEWONLINE'				=> true,
! ));
! 
! // We do not need to load the who is online box here. ;)
! $config['load_online'] = false;
! 
! // Output the page
! page_header($user->lang['WHO_IS_ONLINE']);
! 
! $template->set_filenames(array(
! 	'body' => 'viewonline_body.html')
! );
! make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"));
! 
! page_footer();
--- 27,64 ----
  $user->setup('memberlist');
  
  // Get and set some variables
! $mode		= $request->variable('mode', null);
! $session_id	= $request->variable('s', null);
! $start		= $request->variable('start', null);
! $sort_key	= $request->variable('sk', null);
! $sort_dir	= $request->variable('sd', null);
! $show_guests	= ($config['load_online_guests']) ? $request->variable('sg', null) : null;
  
  
! /** @var \phpbb\controller\helper $controller_helper */
! $controller_helper = $phpbb_container->get('controller.helper');
  
! if ($mode == 'whois')
  {
! 	$response = new RedirectResponse(
! 		$controller_helper->route('phpbb_members_online_whois', array(
! 			's'	=> $session_id,
! 		), false),
! 		301
  	);
  }
! else
  {
! 	$response = new RedirectResponse(
! 		$controller_helper->route('phpbb_members_online', array(
! 			'mode' => $mode,
! 			's' => $session_id,
! 			'start' => $start,
! 			'sk' => $sort_key,
! 			'sd' => $sort_dir,
! 			'sg' => $show_guests,
! 		), false),
! 		301
  	);
  }
! $response->send();
diff -x images -crNEB release-4.0.0-a1/viewtopic.php release-4.0.0-a2-dev/viewtopic.php
*** release-4.0.0-a1/viewtopic.php	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/viewtopic.php	Wed Apr  1 17:47:36 2026
***************
*** 1925,1930 ****
--- 1925,1931 ----
  	}
  
  	$force_edit_allowed = $force_delete_allowed = $force_softdelete_allowed = false;
+ 	$warn_allowed = true;
  
  	$s_cannot_edit = !$auth->acl_get('f_edit', $forum_id) || $user->data['user_id'] != $poster_id;
  	$s_cannot_edit_time = $config['edit_time'] && $row['post_time'] <= time() - ($config['edit_time'] * 60);
***************
*** 1955,1962 ****
--- 1956,1965 ----
  	* @var	bool	s_cannot_delete_locked		User can not delete the post because it's locked
  	* @var	bool	s_cannot_delete_time		User can not delete the post because edit_time has passed
  	* @var	bool	force_softdelete_allowed	Allow the user to ыoftdelete the post (all permissions and conditions are ignored)
+ 	* @var	bool	warn_allowed				Controls whether warning is allowed (default: true)
  	* @since 3.1.0-b4
  	* @changed 3.1.11-RC1 Added force_softdelete_allowed var
+ 	* @changed 3.3.16-RC1 Added warn_allowed var
  	*/
  	$vars = array(
  		'row',
***************
*** 1971,1976 ****
--- 1974,1980 ----
  		's_cannot_delete_locked',
  		's_cannot_delete_time',
  		'force_softdelete_allowed',
+ 		'warn_allowed',
  	);
  	extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_post_action_conditions', compact($vars)));
  
***************
*** 2076,2082 ****
  		'U_NEXT_POST_ID'	=> ($i < $i_total && isset($rowset[$post_list[$i + 1]])) ? $rowset[$post_list[$i + 1]]['post_id'] : '',
  		'U_PREV_POST_ID'	=> $prev_post_id,
  		'U_NOTES'			=> ($auth->acl_getf_global('m_')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&amp;mode=user_notes&amp;u=' . $poster_id) : '',
! 		'U_WARN'			=> ($auth->acl_get('m_warn') && $poster_id != $user->data['user_id'] && $poster_id != ANONYMOUS) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=warn&amp;mode=warn_post&amp;p=' . $row['post_id']) : '',
  
  		'POST_ID'			=> $row['post_id'],
  		'POST_NUMBER'		=> $i + $start + 1,
--- 2080,2086 ----
  		'U_NEXT_POST_ID'	=> ($i < $i_total && isset($rowset[$post_list[$i + 1]])) ? $rowset[$post_list[$i + 1]]['post_id'] : '',
  		'U_PREV_POST_ID'	=> $prev_post_id,
  		'U_NOTES'			=> ($auth->acl_getf_global('m_')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&amp;mode=user_notes&amp;u=' . $poster_id) : '',
! 		'U_WARN'			=> ($warn_allowed && $auth->acl_get('m_warn') && $poster_id != $user->data['user_id'] && $poster_id != ANONYMOUS) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=warn&amp;mode=warn_post&amp;p=' . $row['post_id']) : '',
  
  		'POST_ID'			=> $row['post_id'],
  		'POST_NUMBER'		=> $i + $start + 1,
diff -x images -crNEB release-4.0.0-a1/web.config release-4.0.0-a2-dev/web.config
*** release-4.0.0-a1/web.config	Sat Sep 27 08:11:06 2025
--- release-4.0.0-a2-dev/web.config	Wed Apr  1 17:47:36 2026
***************
*** 9,15 ****
  						<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
  						<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
  					</conditions>
! 					<action type="Rewrite" url="app.php" appendQueryString="true" />
  				</rule>
  			</rules>
  		</rewrite>
--- 9,15 ----
  						<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
  						<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
  					</conditions>
! 					<action type="Rewrite" url="index.php" appendQueryString="true" />
  				</rule>
  			</rules>
  		</rewrite>
