From cb13a7a5b43fe695ba05e712fa1cea86da1b3ba2 Mon Sep 17 00:00:00 2001 From: v-dumas Date: Fri, 23 May 2025 17:32:11 +0200 Subject: [PATCH 1/8] =?UTF-8?q?N=C2=B02583=20-=20Audit,=20User,=20Query=20?= =?UTF-8?q?classes=20can=20be=20Import=20with=20bulk=5Fmodify=20right?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/csvimport.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/csvimport.php b/pages/csvimport.php index 093fe4195..17616eee4 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -89,9 +89,9 @@ try { $oOption = SelectOptionUIBlockFactory::MakeForSelectOption("", Dict::S('UI:CSVImport:ClassesSelectOne'), false); $oSelectBlock->AddSubBlock($oOption); $aValidClasses = array(); - $aClassCategories = array('bizmodel', 'addon/authentication'); + $aClassCategories = array('bizmodel', 'addon/authentication', 'grant_by_profile'); if (UserRights::IsAdministrator()) { - $aClassCategories = array('bizmodel', 'application', 'addon/authentication'); + $aClassCategories = array('bizmodel', 'application', 'addon/authentication', 'grant_by_profile'); } foreach ($aClassCategories as $sClassCategory) { foreach (MetaModel::GetClasses($sClassCategory) as $sClassName) { From 7ae49e2cf48322dcfcb19f96b5834e3df1bb3994 Mon Sep 17 00:00:00 2001 From: Stephen Abello Date: Mon, 26 May 2025 11:25:08 +0200 Subject: [PATCH 2/8] =?UTF-8?q?=20N=C2=B08076=20-=20Fix=20cancel=20and=20c?= =?UTF-8?q?lose=20buttons=20in=20a=20modal=20blocking=20all=20buttons=20fo?= =?UTF-8?q?r=20the=20underlying=20object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/cmdbabstract.class.inc.php | 2 +- js/extkeywidget.js | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 8772d598a..8a2ed6bfa 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -3079,7 +3079,7 @@ JS } else { $sCancelButtonOnClickScript .= "function() { BackToDetails('$sClass', $iKey, '$sDefaultUrl', $sJSToken)};"; } - $sCancelButtonOnClickScript .= "$('#form_{$this->m_iFormId} button.cancel').on('click', fOnClick{$this->m_iFormId}CancelButton);"; + $sCancelButtonOnClickScript .= "$('#form_{$this->m_iFormId} button.cancel').on('click.navigation.itop', fOnClick{$this->m_iFormId}CancelButton);"; $oPage->add_ready_script($sCancelButtonOnClickScript); $iFieldsCount = count($aFieldsMap); diff --git a/js/extkeywidget.js b/js/extkeywidget.js index e09210165..ab8c3f5fe 100644 --- a/js/extkeywidget.js +++ b/js/extkeywidget.js @@ -717,8 +717,11 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper window[sPromiseId].then(function () { $('#ac_create_'+me.id).dialog('open'); $('#ac_create_'+me.id).dialog("option", "close", me.OnCloseCreateObject); - // Modify the action of the cancel button - $('#ac_create_'+me.id+' button.cancel').off('click').on('click', me.CloseCreateObject); + // Modify the action of the cancel button and the close button + $('#ac_create_'+me.id+' button.cancel').off('click.navigation.itop').on('click.navigation.itop', me.CloseCreateObject); + $('.ui-dialog-titlebar:has(+ #ac_create_'+me.id+') button.ui-dialog-titlebar-close').off('click').on('click', function() { + $('#ac_create_'+me.id+' button.cancel').trigger('click'); + }); me.ajax_request = null; me.sTargetClass = sLocalTargetClass; // Adjust the dialog's size to fit into the screen From 9723cde24cbddb0c99729186f1a3760b65ea0c7b Mon Sep 17 00:00:00 2001 From: Stephen Abello Date: Mon, 26 May 2025 15:41:25 +0200 Subject: [PATCH 3/8] =?UTF-8?q?N=C2=B07960=20-=20Add=20a=20wrapper=20to=20?= =?UTF-8?q?vsprintf=20to=20handle=20argument=20number=20disparities=20(#71?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/ui.extkeywidget.class.inc.php | 2 +- application/utils.inc.php | 121 ++++++++++++++++++ core/dict.class.inc.php | 2 +- core/modelreflection.class.inc.php | 2 +- core/valuesetdef.class.inc.php | 2 +- sources/Service/Base/ObjectRepository.php | 2 +- .../unitary-tests/application/utilsTest.php | 90 +++++++++++++ 7 files changed, 216 insertions(+), 5 deletions(-) diff --git a/application/ui.extkeywidget.class.inc.php b/application/ui.extkeywidget.class.inc.php index 7a35cf2cb..ed74e86f9 100644 --- a/application/ui.extkeywidget.class.inc.php +++ b/application/ui.extkeywidget.class.inc.php @@ -250,7 +250,7 @@ class UIExtKeyWidget foreach ($aAdditionalField as $sAdditionalField) { array_push($aArguments, $oObj->Get($sAdditionalField)); } - $aOption['additional_field'] = utils::HtmlEntities(vsprintf($sFormatAdditionalField, $aArguments)); + $aOption['additional_field'] = utils::HtmlEntities(utils::VSprintf($sFormatAdditionalField, $aArguments)); } if (!empty($sObjectImageAttCode)) { // Try to retrieve image for contact diff --git a/application/utils.inc.php b/application/utils.inc.php index c212881df..9233ca932 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -2075,6 +2075,127 @@ SQL; ); } + /** + * Format a string using vsprintf with safety checks to avoid ValueError + * + * This method fills missing arguments with their original format specifiers, + * then calls vsprintf with the complete array. + * + * @param string $sFormat The format string + * @param array $aArgs The arguments to format + * @param bool $bLogErrors Whether to log errors (defaults to true) + * + * @return string The formatted string + * @since 3.2.2 + */ + public static function VSprintf(string $sFormat, array $aArgs, bool $bLogErrors = true): string + { + // Extract all format specifiers + $sPattern = '/%(?:(?:[1-9][0-9]*)\$)?[-+\'0# ]*(?:[0-9]*|\*)?(?:\.(?:[0-9]*|\*))?(?:[hlL])?[diouxXeEfFgGcrs%]/'; + preg_match_all($sPattern, $sFormat, $aMatches, PREG_OFFSET_CAPTURE); + + // Process matches, keeping track of their positions and excluding escaped percent signs (%%) + $aSpecifierMatches = []; + foreach ($aMatches[0] as $sMatch) { + if ($sMatch[0] !== '%%') { + $aSpecifierMatches[] = $sMatch; + } + } + + // Check for positional specifiers and build position map + $bHasPositional = false; + $iMaxPosition = 0; + $aPositions = []; + $aUniquePositions = []; + + foreach ($aSpecifierMatches as $index => $match) { + $sSpec = $match[0]; + if (preg_match('/^%([1-9][0-9]*)\$/', $sSpec, $posMatch)) { + $bHasPositional = true; + $iPosition = (int)$posMatch[1] - 1; // Convert to 0-based + $aPositions[$index] = $iPosition; + $aUniquePositions[$iPosition] = true; + $iMaxPosition = max($iMaxPosition, $iPosition + 1); + } else { + $aPositions[$index] = $index; + $aUniquePositions[$index] = true; + $iMaxPosition = max($iMaxPosition, $index + 1); + } + } + + // Count unique positions, this tells us how many arguments we actually need + $iExpectedCount = count($aUniquePositions); + $iActualCount = count($aArgs); + + // If we have enough arguments, just use vsprintf + if ($iActualCount >= $iExpectedCount) { + return vsprintf($sFormat, $aArgs); + } + // else log the error if needed + if ($bLogErrors) { + IssueLog::Warning("Format string requires $iExpectedCount arguments, but only $iActualCount provided. Format: '$sFormat'" ); + } + + // Create a replacement map + if ($bHasPositional) { + // For positional, we need to handle the exact positions + $aReplacements = array_fill(0, $iMaxPosition, null); + + // Fill in the real arguments first + foreach ($aArgs as $index => $sValue) { + if ($index < $iMaxPosition) { + $aReplacements[$index] = $sValue; + } + } + + // For null values in the replacement map, use the original specifier + foreach ($aSpecifierMatches as $index => $sMatch) { + $iPosition = $aPositions[$index]; + if ($aReplacements[$iPosition] === null) { + // Use the original format specifier when we don't have an argument + $aReplacements[$iPosition] = $sMatch[0]; + } + } + + // Remove any remaining nulls (for positions that weren't referenced) + $aReplacements = array_filter($aReplacements, static function($val) { return $val !== null; }); + } else { + // For non-positional, we need to map each position + $aReplacements = []; + $iUsed = 0; + + // Create a map of what values to use for each position + $aPositionValues = []; + for ($i = 0; $i < $iMaxPosition; $i++) { + if (isset($aUniquePositions[$i])) { + if ($iUsed < $iActualCount) { + // We have an actual argument for this position + $aPositionValues[$i] = $aArgs[$iUsed++]; + } else { + // Mark this position to use the original specifier + $aPositionValues[$i] = null; + } + } + } + + // Build the replacements array preserving the original order + foreach ($aSpecifierMatches as $index => $sMatch) { + $iPosition = $aPositions[$index]; + if (isset($aPositionValues[$iPosition])) { + $aReplacements[] = $aPositionValues[$iPosition]; + } else { + // Use the original format specifier when we don't have an argument + $aReplacements[] = $sMatch[0]; + // Mark this position as used, so if it appears again, it gets the same replacement + $aPositionValues[$iPosition] = $sMatch[0]; + } + } + } + + // Process the format string with our filled-in arguments + return vsprintf($sFormat, $aReplacements); + } + /** * Convert a string containing some (valid) HTML markup to plain text * diff --git a/core/dict.class.inc.php b/core/dict.class.inc.php index 8fbbbe21d..002ca3c4b 100644 --- a/core/dict.class.inc.php +++ b/core/dict.class.inc.php @@ -206,7 +206,7 @@ class Dict } try{ - return vsprintf($sLocalizedFormat, $aArguments); + return utils::VSprintf($sLocalizedFormat, $aArguments); } catch(\Throwable $e){ \IssueLog::Error("Cannot format dict key", null, ["sFormatCode" => $sFormatCode, "sLangCode" => $sLangCode, 'exception_msg' => $e->getMessage() ]); return $sFormatCode.' - '.implode(', ', $aArguments); diff --git a/core/modelreflection.class.inc.php b/core/modelreflection.class.inc.php index ee8debde8..3ab1ed7cf 100644 --- a/core/modelreflection.class.inc.php +++ b/core/modelreflection.class.inc.php @@ -77,7 +77,7 @@ abstract class ModelReflection return $sFormatCode.' - '.implode(', ', $aArguments); } - return vsprintf($sLocalizedFormat, $aArguments); + return utils::VSprintf($sLocalizedFormat, $aArguments); } /** diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index b7c574e0e..47c1ac43a 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -435,7 +435,7 @@ class ValueSetObjects extends ValueSetDefinition foreach ($aAdditionalField as $sAdditionalField) { array_push($aArguments, $oObject->Get($sAdditionalField)); } - $aData['additional_field'] = vsprintf($sFormatAdditionalField, $aArguments); + $aData['additional_field'] = utils::VSprintf($sFormatAdditionalField, $aArguments); } else { $aData['additional_field'] = ''; } diff --git a/sources/Service/Base/ObjectRepository.php b/sources/Service/Base/ObjectRepository.php index 14a8b1637..1058ed3fa 100644 --- a/sources/Service/Base/ObjectRepository.php +++ b/sources/Service/Base/ObjectRepository.php @@ -213,7 +213,7 @@ class ObjectRepository foreach ($aComplementAttributeSpec[1] as $sAdditionalField) { $aArguments[] = $oDbObject->Get($sAdditionalField); } - $aData['additional_field'] = vsprintf($aComplementAttributeSpec[0], $aArguments); + $aData['additional_field'] = utils::VSprintf($aComplementAttributeSpec[0], $aArguments); $sAdditionalFieldForHtml = utils::EscapeHtml($aData['additional_field']); $aData['full_description'] = "{$sFriendlyNameForHtml}
{$sAdditionalFieldForHtml}"; } else { diff --git a/tests/php-unit-tests/unitary-tests/application/utilsTest.php b/tests/php-unit-tests/unitary-tests/application/utilsTest.php index 030b51a1e..c488f4f80 100644 --- a/tests/php-unit-tests/unitary-tests/application/utilsTest.php +++ b/tests/php-unit-tests/unitary-tests/application/utilsTest.php @@ -907,4 +907,94 @@ HTML, $this->assertFalse(utils::GetDocumentFromSelfURL($sURL)); } + /** + * @dataProvider VSprintfProvider + */ + public function testVSprintf($sFormat, $aArgs, $sExpected) + { + $sTested = utils::VSprintf($sFormat, $aArgs, false); + $this->assertEquals($sExpected, $sTested); + } + + public function VSprintfProvider() + { + return [ + // Basic positional specifier tests + 'Basic positional with enough args' => [ + 'Format: %1$s, %2$d, %3$s', + ['Hello', 42, 'World'], + 'Format: Hello, 42, World' + ], + 'Basic positional with args in different order' => [ + 'Format: %2$s, %1$d, %3$s', + [42, 'Hello', 'World'], + 'Format: Hello, 42, World' + ], + 'Positional with reused specifiers' => [ + 'Format: %1$s, %2$d, %1$s again', + ['Hello', 42], + 'Format: Hello, 42, Hello again' + ], + + // Missing arguments tests + 'Missing one positional arg' => [ + 'Format: %1$s, %2$d, %3$s', + ['Hello', 42], + 'Format: Hello, 42, %3$s' + ], + 'Missing multiple positional args' => [ + 'Format: %1$s, %2$s, %3$s, %4$s', + ['Hello'], + 'Format: Hello, %2$s, %3$s, %4$s' + ], + 'Missing first positional arg' => [ + 'Format: %1$s, %2$s, %3$s', + [], + 'Format: %1$s, %2$s, %3$s' + ], + + // Edge cases + 'Positional with larger numbers' => [ + 'Format: %2$s, %1$d, %3$s, %2$s again', + [123456, 'Hello', 'World'], + 'Format: Hello, 123456, World, Hello again' + ], + 'Positional specifiers with non-sequential indexes' => [ + 'Format: %3$s then %1$s and %5$d', + ['first', 'second', 'third', 'fourth', 42], + 'Format: third then first and 42' + ], + + // More complex format specifiers + 'Positional with format modifiers' => [ + 'Format: %1$\'*10s, %2$04d', + ['Hello', 42], + 'Format: *****Hello, 0042' + ], + 'Positional with various types' => [ + 'Format: String: %1$s, Integer: %2$d, Char: %3$c', + ['Hello', 42, 65], + 'Format: String: Hello, Integer: 42, Char: A' + ], + + // Testing with non-Latin characters + 'Positional with UTF-8 characters' => [ + 'Format: %1$s %2$s %3$s', + ['こんにちは', 'Здравствуйте', '你好'], + 'Format: こんにちは Здравствуйте 你好' + ], + + // Mixed formats + 'Mixed positional with complex specifiers' => [ + 'Format: %1$-10s | %2$+d', + ['Hello', 42], + 'Format: Hello | +42' + ], + 'Reused positional indexes with some missing' => [ + 'Format: %1$s %2$d %1$s %3$s %2$d', + ['Hello', 42], + 'Format: Hello 42 Hello %3$s 42' + ] + ]; + } } From cb382eab4e9d0e994107a4faa779d1ad5b55a45b Mon Sep 17 00:00:00 2001 From: Stephen Abello Date: Mon, 26 May 2025 16:23:38 +0200 Subject: [PATCH 4/8] Fix CI following dict behavior change --- .../integration-tests/DictionariesConsistencyAfterSetupTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/php-unit-tests/integration-tests/DictionariesConsistencyAfterSetupTest.php b/tests/php-unit-tests/integration-tests/DictionariesConsistencyAfterSetupTest.php index 6e854fb13..e69d5cb65 100644 --- a/tests/php-unit-tests/integration-tests/DictionariesConsistencyAfterSetupTest.php +++ b/tests/php-unit-tests/integration-tests/DictionariesConsistencyAfterSetupTest.php @@ -41,7 +41,7 @@ class DictionariesConsistencyAfterSetupTest extends ItopTestCase ], 'traduction that breaks expected nb of arguments' => [ 'sTemplate' => 'toto %1$s titi %2$s', - 'sExpectedTraduction' => 'ITOP::DICT:FORMAT:BROKEN:KEY - 1', + 'sExpectedTraduction' => 'toto 1 titi %2$s', ], 'traduction ok' => [ 'sTemplate' => 'toto %1$s titi', From b15ca2fbc9665fb28de559c1bcb490415c5775ae Mon Sep 17 00:00:00 2001 From: jf-cbd Date: Tue, 27 May 2025 10:41:05 +0200 Subject: [PATCH 5/8] =?UTF-8?q?N=C2=B08260=20-=20Change=20format=20of=20RE?= =?UTF-8?q?ST=20logs=20when=20they=20are=20close=20to=20the=20SQL=20field?= =?UTF-8?q?=20size=20limit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webservices/rest.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/webservices/rest.php b/webservices/rest.php index e0cbfc081..a629a4bdb 100644 --- a/webservices/rest.php +++ b/webservices/rest.php @@ -302,9 +302,17 @@ if (MetaModel::GetConfig()->Get('log_rest_service')) $iUnescapeSlashAndUnicode = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; $sJsonOuputWithPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode | JSON_PRETTY_PRINT); $sJsonOutputWithoutPrettyPrinting = json_encode($oResult, $iUnescapeSlashAndUnicode); - !$oLog->StringFitsInField('json_output', $sJsonOuputWithPrettyPrinting) ? + !StringFitsInLogField( $sJsonOuputWithPrettyPrinting) ? $oLog->SetTrim('json_output', $sJsonOutputWithoutPrettyPrinting) : // too long, we don't make it pretty $oLog->SetTrim('json_output', $sJsonOuputWithPrettyPrinting); $oLog->DBInsertNoReload(); +} + +/** + * @deprecated - will be removed in 3.3.0 + */ +function StringFitsInLogField(string $sLog): bool +{ + return mb_strlen($sLog) <= 16383; // hardcoded value, see N°8260 } \ No newline at end of file From 5ae5221f6fde43e48249971d0f0dea12c3d52b07 Mon Sep 17 00:00:00 2001 From: Anne-Catherine <57360138+accognet@users.noreply.github.com> Date: Wed, 28 May 2025 10:59:50 +0200 Subject: [PATCH 6/8] =?UTF-8?q?N=C2=B06925=20-=20Enable=20on=20any=20searc?= =?UTF-8?q?h=20result=20edition=20of=20OQL=20(#711)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/utils.inc.php | 5 +++++ js/utils.js | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/application/utils.inc.php b/application/utils.inc.php index 9233ca932..5803a3bc8 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -1555,6 +1555,11 @@ class utils } $aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL', '$sContext')"); $aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')"); + if (ApplicationMenu::IsMenuIdEnabled('RunQueriesMenu')) { + $oMenuItemPlay = new JSPopupMenuItem('UI:Menu:OpenOQL', Dict::S('UI:Edit:TestQuery'), "OpenOql('$sOQL')"); + $oMenuItemPlay->SetIconClass('fas fa-play'); + $aResult[] = $oMenuItemPlay; + } break; diff --git a/js/utils.js b/js/utils.js index 987358fdb..8bbda0ccf 100644 --- a/js/utils.js +++ b/js/utils.js @@ -369,6 +369,24 @@ function DashletCreationDlg(sOQL, sContext) { return false; } +function OpenOql(sOQL) { + sBaseUrl = GetAbsoluteUrlAppRoot() + 'pages/run_query.php'; + var form = document.createElement("form"); + form.setAttribute("method", "post"); + form.setAttribute("action", sBaseUrl); + form.setAttribute("target", '_blank'); + form.setAttribute("id", 'run_query_form'); + var input = document.createElement('input'); + input.type = 'hidden'; + input.name = 'expression'; + input.value = sOQL; + form.appendChild(input); + document.body.appendChild(form); + // form.submit() is blocked by the browser + $('#run_query_form').submit(); + document.body.removeChild(form); +} + function ShortcutListDlg(sOQL, sDataTableId, sContext) { var sDataTableName = 'datatable_'+sDataTableId; var oTableSettings = { From 657fc912bf4394066aacaefb1fcc580125d56c63 Mon Sep 17 00:00:00 2001 From: jf-cbd <121934370+jf-cbd@users.noreply.github.com> Date: Mon, 2 Jun 2025 16:09:29 +0200 Subject: [PATCH 7/8] =?UTF-8?q?N=C2=B08198=20-=20ModuleInstallation=20now?= =?UTF-8?q?=20extends=20DBObject=20+=20better=20exception=20message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup/moduleinstallation.class.inc.php | 2 +- sources/Controller/Base/Layout/ObjectController.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/setup/moduleinstallation.class.inc.php b/setup/moduleinstallation.class.inc.php index 81b68a55a..535f22daf 100644 --- a/setup/moduleinstallation.class.inc.php +++ b/setup/moduleinstallation.class.inc.php @@ -25,7 +25,7 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ -class ModuleInstallation extends cmdbAbstractObject +class ModuleInstallation extends DBObject { public static function Init() { diff --git a/sources/Controller/Base/Layout/ObjectController.php b/sources/Controller/Base/Layout/ObjectController.php index 137d7827a..84db6f59e 100644 --- a/sources/Controller/Base/Layout/ObjectController.php +++ b/sources/Controller/Base/Layout/ObjectController.php @@ -82,7 +82,9 @@ class ObjectController extends AbstractController { throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); } - + if (!is_subclass_of($sClass, cmdbAbstractObject::class)) { + throw new SecurityException('The class "'.$sClass.'" is not a subclass of cmdbAbstractObject so it can\'t be created by the user'); + } // If the specified class has subclasses, ask the user an instance of which class to create $aSubClasses = MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself $aPossibleClasses = array(); From 3ff3dcba54e897a047e506ef8057e7c4ff54cc01 Mon Sep 17 00:00:00 2001 From: bdalsass Date: Mon, 2 Jun 2025 17:15:51 +0200 Subject: [PATCH 8/8] =?UTF-8?q?N=C2=B08308=20-=20Broken=20URL=20in=20sendi?= =?UTF-8?q?ng=20mail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sources/Core/Email/EmailLaminas.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Core/Email/EmailLaminas.php b/sources/Core/Email/EmailLaminas.php index afe9fc9df..4f5cfbd6d 100644 --- a/sources/Core/Email/EmailLaminas.php +++ b/sources/Core/Email/EmailLaminas.php @@ -435,7 +435,7 @@ class EMailLaminas extends Email // Add body content to as a new part $oNewPart = new Part($sBody); - $oNewPart->encoding = Mime::ENCODING_8BIT; + $oNewPart->encoding = Mime::ENCODING_BASE64; $oNewPart->type = $sMimeType; $oNewPart->charset = 'UTF-8'; $oBody->addPart($oNewPart); @@ -463,7 +463,7 @@ class EMailLaminas extends Email } $this->m_aData['parts'][] = array('text' => $sText, 'mimeType' => $sMimeType); $oNewPart = new Part($sText); - $oNewPart->encoding = Mime::ENCODING_8BIT; + $oNewPart->encoding = Mime::ENCODING_BASE64; $oNewPart->type = $sMimeType; // setBody called only to refresh Content-Type to multipart/mixed