diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 2aef623709..594df5182d 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -4344,7 +4344,9 @@ public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) } else { $sValue = self::RenderWikiHtml($sValue, true /* wiki only */); - return "
".InlineImage::FixUrls($sValue).'
'; + $sImageHtml = UserRights::IsLoggedIn() ? InlineImage::FixUrls($sValue) : InlineImage::ReplaceInlineImagesWithBase64Representation($sValue); + + return "
".$sImageHtml.'
'; } } diff --git a/core/inlineimage.class.inc.php b/core/inlineimage.class.inc.php index fe4d4a7294..c5d9c76980 100644 --- a/core/inlineimage.class.inc.php +++ b/core/inlineimage.class.inc.php @@ -296,6 +296,46 @@ public static function FixUrls($sHtml) return $sHtml; } + /** + * Replace tags with a data-img-id attribute by the actual image in base64 representation + * so that the image can be displayed even if the download URL is not accessible (e.g. in unauthenticated approval templates) + * + * @param string $sHtml The HTML fragment to process + * + * @return String The modified HTML + * @since 3.2.3 + */ + public static function ReplaceInlineImagesWithBase64Representation(string $sHtml): String + { + return preg_replace_callback( + '/]*'.static::DOM_ATTR_ID.'="(\d+)"[^>]*>/i', + function ($matches) { + + // Extract inline image ID from the tag + $id = $matches[1]; + + try { + // Retrieve inline image + $oInline = MetaModel::GetObject(InlineImage::class, $id, true, true); + $oOrmDocument = $oInline->Get('contents'); + + // Replace src image by the base64 representation + $sInlineImageAsBase64 = base64_encode($oOrmDocument->GetData()); + $sDataUri = 'data:'.$oOrmDocument->GetMimeType().';base64,'.$sInlineImageAsBase64; + $sImage = preg_replace('/src=["\'][^"\']+["\']/', 'src="'.$sDataUri.'"', $matches[0]); + + // Remove sensitive information (the image ID and secret) from the tag + $sImage = preg_replace('/'.static::DOM_ATTR_ID.'="\d+"\s+'.static::DOM_ATTR_SECRET.'="\w+"/', '', $sImage); + } catch (Exception $e) { + $sImage = ''.Dict::S('UI:MissingInlineImage').''; + } + + return $sImage; + }, + $sHtml + ); + } + /** * Add an extra attribute data-img-id for images which are based on an actual InlineImage * so that we can later reconstruct the full "src" URL when needed diff --git a/dictionaries/cs.dictionary.itop.ui.php b/dictionaries/cs.dictionary.itop.ui.php index f2417a4de9..5944af7d5c 100755 --- a/dictionaries/cs.dictionary.itop.ui.php +++ b/dictionaries/cs.dictionary.itop.ui.php @@ -1394,6 +1394,7 @@ 'UI:SelectInlineImageToUpload' => 'Vyberte obrázek', 'UI:AvailableInlineImagesLegend' => 'Dostupné obrázky', 'UI:NoInlineImage' => 'Na serveru není dostupný žádný obrázek. Nahrajte nějaký pomocí tlačítka výše.', + 'UI:MissingInlineImage' => 'Chybějící obrázek', 'UI:ToggleFullScreen' => 'Přepnout zobrazení', 'UI:Button:ResetImage' => 'Obnovit původní obrázek', 'UI:Button:RemoveImage' => 'Odebrat obrázek', diff --git a/dictionaries/da.dictionary.itop.ui.php b/dictionaries/da.dictionary.itop.ui.php index ca3069cd35..905ff33f7d 100644 --- a/dictionaries/da.dictionary.itop.ui.php +++ b/dictionaries/da.dictionary.itop.ui.php @@ -1397,6 +1397,7 @@ 'UI:SelectInlineImageToUpload' => 'Select the image to upload~~', 'UI:AvailableInlineImagesLegend' => 'Available images~~', 'UI:NoInlineImage' => 'There is no image available on the server. Use the "Browse" button above to select an image from your computer and upload it to the server.~~', + 'UI:MissingInlineImage' => 'Manglende billede', 'UI:ToggleFullScreen' => 'Toggle Maximize / Minimize~~', 'UI:Button:ResetImage' => 'Recover the previous image~~', 'UI:Button:RemoveImage' => 'Remove the image~~', diff --git a/dictionaries/de.dictionary.itop.ui.php b/dictionaries/de.dictionary.itop.ui.php index 707516b8c0..d1a6fa67ba 100644 --- a/dictionaries/de.dictionary.itop.ui.php +++ b/dictionaries/de.dictionary.itop.ui.php @@ -1394,6 +1394,7 @@ 'UI:SelectInlineImageToUpload' => 'Wähle das Bild für den Upload aus', 'UI:AvailableInlineImagesLegend' => 'Verfügbare Bilder', 'UI:NoInlineImage' => 'Es sind keine Bilder auf dem Server verfügbar. Nutze den "Durchsuchen" Button oben, um ein Bild vom Computer hochzuladen.', + 'UI:MissingInlineImage' => 'Bild fehlt', 'UI:ToggleFullScreen' => 'Maximieren / Minimieren', 'UI:Button:ResetImage' => 'Vorheriges Bild wiederherstellen', 'UI:Button:RemoveImage' => 'Bild löschen', diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php index 4d182100cd..b971c07f2c 100644 --- a/dictionaries/en.dictionary.itop.ui.php +++ b/dictionaries/en.dictionary.itop.ui.php @@ -1471,6 +1471,7 @@ 'UI:SelectInlineImageToUpload' => 'Select the image to upload', 'UI:AvailableInlineImagesLegend' => 'Available images', 'UI:NoInlineImage' => 'There is no image available on the server. Use the "Browse" button above to select an image from your computer and upload it to the server.', + 'UI:MissingInlineImage' => 'Missing image', 'UI:ToggleFullScreen' => 'Toggle Maximize / Minimize', 'UI:Button:ResetImage' => 'Recover the previous image', diff --git a/dictionaries/en_gb.dictionary.itop.ui.php b/dictionaries/en_gb.dictionary.itop.ui.php index 1a0ca806e0..c1a2966c3e 100644 --- a/dictionaries/en_gb.dictionary.itop.ui.php +++ b/dictionaries/en_gb.dictionary.itop.ui.php @@ -1471,6 +1471,7 @@ 'UI:SelectInlineImageToUpload' => 'Select the image to upload', 'UI:AvailableInlineImagesLegend' => 'Available images', 'UI:NoInlineImage' => 'There is no image available on the server. Use the "Browse" button above to select an image from your computer and upload it to the server.', + 'UI:MissingInlineImage' => 'Missing image', 'UI:ToggleFullScreen' => 'Toggle Maximise / Minimise', 'UI:Button:ResetImage' => 'Recover the previous image', diff --git a/dictionaries/es_cr.dictionary.itop.ui.php b/dictionaries/es_cr.dictionary.itop.ui.php index 5972321447..ebfc001e13 100644 --- a/dictionaries/es_cr.dictionary.itop.ui.php +++ b/dictionaries/es_cr.dictionary.itop.ui.php @@ -1397,6 +1397,7 @@ 'UI:SelectInlineImageToUpload' => 'Seleccione la imágen a subir', 'UI:AvailableInlineImagesLegend' => 'Imágenes disponibles', 'UI:NoInlineImage' => 'No hay imágenes disponibles en el servidor. Use el botón "Seleccionar archivo" para seleccionar una imágen de su equipo local y subirla al servidor.', + 'UI:MissingInlineImage' => 'Imagen faltante', 'UI:ToggleFullScreen' => 'Cambiar Maximizar / Minimizar', 'UI:Button:ResetImage' => 'Recuperar imágen previa', 'UI:Button:RemoveImage' => 'Remover imágen', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 84ceb09e6c..e7ab4198d8 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -1398,6 +1398,7 @@ 'UI:SelectInlineImageToUpload' => 'Sélectionnez l\'image à ajouter', 'UI:AvailableInlineImagesLegend' => 'Images disponibles', 'UI:NoInlineImage' => 'Il n\'y a aucune image de disponible sur le serveur. Utilisez le bouton "Parcourir" (ci-dessus) pour sélectionner une image sur votre ordinateur et la télécharger sur le serveur.', + 'UI:MissingInlineImage' => 'Image introuvable', 'UI:ToggleFullScreen' => 'Agrandir / Minimiser', 'UI:Button:ResetImage' => 'Récupérer l\'image initiale', 'UI:Button:RemoveImage' => 'Supprimer l\'image', diff --git a/dictionaries/hu.dictionary.itop.ui.php b/dictionaries/hu.dictionary.itop.ui.php index 095c71735b..1ced02d6cb 100755 --- a/dictionaries/hu.dictionary.itop.ui.php +++ b/dictionaries/hu.dictionary.itop.ui.php @@ -1400,6 +1400,7 @@ 'UI:SelectInlineImageToUpload' => 'Válasszon egy képet', 'UI:AvailableInlineImagesLegend' => 'Elérhető képek', 'UI:NoInlineImage' => 'A szerveren nincs elérhető kép. Használja a fenti "Tallózás" gombot egy kép kiválasztásához a számítógépéről, és töltse fel a szerverre.', + 'UI:MissingInlineImage' => 'Hiányzó kép', 'UI:ToggleFullScreen' => 'Maximalizálás / Minimalizálás', 'UI:Button:ResetImage' => 'Az előző kép visszaállítása', 'UI:Button:RemoveImage' => 'Kép eltávolítása', diff --git a/dictionaries/it.dictionary.itop.ui.php b/dictionaries/it.dictionary.itop.ui.php index c32db15896..9be05503e8 100644 --- a/dictionaries/it.dictionary.itop.ui.php +++ b/dictionaries/it.dictionary.itop.ui.php @@ -1399,6 +1399,7 @@ 'UI:SelectInlineImageToUpload' => 'Seleziona l\'immagine da caricare', 'UI:AvailableInlineImagesLegend' => 'Immagini disponibili', 'UI:NoInlineImage' => 'Non ci sono immagini disponibili sul server. Utilizza il pulsante "Sfoglia" sopra per selezionare un\'immagine dal tuo computer e caricarla sul server.', + 'UI:MissingInlineImage' => 'Immagine mancante', 'UI:ToggleFullScreen' => 'Attiva/Disattiva a schermo intero', 'UI:Button:ResetImage' => 'Ripristina l\'immagine precedente', 'UI:Button:RemoveImage' => 'Rimuovi l\'immagine', diff --git a/dictionaries/ja.dictionary.itop.ui.php b/dictionaries/ja.dictionary.itop.ui.php index 3a9012db39..a3a05189ab 100644 --- a/dictionaries/ja.dictionary.itop.ui.php +++ b/dictionaries/ja.dictionary.itop.ui.php @@ -1401,6 +1401,7 @@ 'UI:SelectInlineImageToUpload' => 'Select the image to upload~~', 'UI:AvailableInlineImagesLegend' => 'Available images~~', 'UI:NoInlineImage' => 'There is no image available on the server. Use the "Browse" button above to select an image from your computer and upload it to the server.~~', + 'UI:MissingInlineImage' => 'Missing image~~', 'UI:ToggleFullScreen' => 'Toggle Maximize / Minimize~~', 'UI:Button:ResetImage' => 'Recover the previous image~~', 'UI:Button:RemoveImage' => 'Remove the image~~', diff --git a/dictionaries/nl.dictionary.itop.ui.php b/dictionaries/nl.dictionary.itop.ui.php index 74bddf2992..fa9ea2b33a 100644 --- a/dictionaries/nl.dictionary.itop.ui.php +++ b/dictionaries/nl.dictionary.itop.ui.php @@ -1400,6 +1400,7 @@ 'UI:SelectInlineImageToUpload' => 'Selecteer een afbeelding om te uploaden', 'UI:AvailableInlineImagesLegend' => 'Beschikbare afbeeldingen', 'UI:NoInlineImage' => 'Er is geen afbeelding beschikbaar op de server. Gebruik de "Afbeeldingen doorbladeren..." knop hierboven om een afbeelding te kiezen op je toestel.', + 'UI:MissingInlineImage' => 'Ontbrekende afbeelding', 'UI:ToggleFullScreen' => 'Minimaliseren / Maximaliseren', 'UI:Button:ResetImage' => 'Vorige afbeelding herstellen', 'UI:Button:RemoveImage' => 'Afbeelding verwijderen', diff --git a/dictionaries/pl.dictionary.itop.ui.php b/dictionaries/pl.dictionary.itop.ui.php index 1d1a87cd31..0a933331c8 100644 --- a/dictionaries/pl.dictionary.itop.ui.php +++ b/dictionaries/pl.dictionary.itop.ui.php @@ -1408,6 +1408,7 @@ 'UI:SelectInlineImageToUpload' => 'Wybierz obraz do przesłania', 'UI:AvailableInlineImagesLegend' => 'Dostępne obrazy', 'UI:NoInlineImage' => 'Na serwerze nie ma obrazu. Użyj przycisku "Przeglądaj" powyżej, aby wybrać obraz ze swojego komputera i przesłać go na serwer.', + 'UI:MissingInlineImage' => 'Brakujący obraz', 'UI:ToggleFullScreen' => 'Przełącz Maksymalizuj / Minimalizuj', 'UI:Button:ResetImage' => 'Odzyskaj poprzedni obraz', 'UI:Button:RemoveImage' => 'Usuń obraz', diff --git a/dictionaries/pt_br.dictionary.itop.ui.php b/dictionaries/pt_br.dictionary.itop.ui.php index 3fb4e2169b..57fad9b407 100644 --- a/dictionaries/pt_br.dictionary.itop.ui.php +++ b/dictionaries/pt_br.dictionary.itop.ui.php @@ -1393,6 +1393,7 @@ 'UI:SelectInlineImageToUpload' => 'Selecione a imagem para enviar', 'UI:AvailableInlineImagesLegend' => 'Imagens disponíveis', 'UI:NoInlineImage' => 'Não há imagem disponível no servidor. Use o botão "Escolher arquivo" acima para selecionar uma imagem do seu computador e fazer o upload para o servidor', + 'UI:MissingInlineImage' => 'Imagem ausente', 'UI:ToggleFullScreen' => 'Alternancia Maximizar / Minimizar', 'UI:Button:ResetImage' => 'Recupere a imagem anterior', 'UI:Button:RemoveImage' => 'Remover a imagem', diff --git a/dictionaries/ru.dictionary.itop.ui.php b/dictionaries/ru.dictionary.itop.ui.php index 041cfaa4c1..6f918e5b3c 100644 --- a/dictionaries/ru.dictionary.itop.ui.php +++ b/dictionaries/ru.dictionary.itop.ui.php @@ -1397,6 +1397,7 @@ 'UI:SelectInlineImageToUpload' => 'Выберите изображение для загрузки', 'UI:AvailableInlineImagesLegend' => 'Доступные изображения', 'UI:NoInlineImage' => 'На сервере нет доступных изображений. С помощью кнопки "Обзор..." выше выберите изображение на вашем компьютере, чтобы загрузить его на сервер.', + 'UI:MissingInlineImage' => 'Отсутствует изображение', 'UI:ToggleFullScreen' => 'Развернуть / Свернуть', 'UI:Button:ResetImage' => 'Восстановить предыдущее изображение', 'UI:Button:RemoveImage' => 'Удалить изображение', diff --git a/dictionaries/sk.dictionary.itop.ui.php b/dictionaries/sk.dictionary.itop.ui.php index 7623385723..e738ce09fc 100644 --- a/dictionaries/sk.dictionary.itop.ui.php +++ b/dictionaries/sk.dictionary.itop.ui.php @@ -1398,6 +1398,7 @@ 'UI:SelectInlineImageToUpload' => 'Select the image to upload~~', 'UI:AvailableInlineImagesLegend' => 'Available images~~', 'UI:NoInlineImage' => 'There is no image available on the server. Use the "Browse" button above to select an image from your computer and upload it to the server.~~', + 'UI:MissingInlineImage' => 'Missing image~~', 'UI:ToggleFullScreen' => 'Toggle Maximize / Minimize~~', 'UI:Button:ResetImage' => 'Recover the previous image~~', 'UI:Button:RemoveImage' => 'Remove the image~~', diff --git a/dictionaries/tr.dictionary.itop.ui.php b/dictionaries/tr.dictionary.itop.ui.php index 2a68afd1e9..cd5b17cfe9 100644 --- a/dictionaries/tr.dictionary.itop.ui.php +++ b/dictionaries/tr.dictionary.itop.ui.php @@ -1401,6 +1401,7 @@ 'UI:SelectInlineImageToUpload' => 'Select the image to upload~~', 'UI:AvailableInlineImagesLegend' => 'Available images~~', 'UI:NoInlineImage' => 'There is no image available on the server. Use the "Browse" button above to select an image from your computer and upload it to the server.~~', + 'UI:MissingInlineImage' => 'Missing image~~', 'UI:ToggleFullScreen' => 'Toggle Maximize / Minimize~~', 'UI:Button:ResetImage' => 'Recover the previous image~~', 'UI:Button:RemoveImage' => 'Remove the image~~', diff --git a/dictionaries/zh_cn.dictionary.itop.ui.php b/dictionaries/zh_cn.dictionary.itop.ui.php index 701053dbeb..74deb5a21e 100644 --- a/dictionaries/zh_cn.dictionary.itop.ui.php +++ b/dictionaries/zh_cn.dictionary.itop.ui.php @@ -1398,6 +1398,7 @@ 'UI:SelectInlineImageToUpload' => '选择要上传的图片', 'UI:AvailableInlineImagesLegend' => '可用的图片', 'UI:NoInlineImage' => '服务器上没有图片. 使用上面的 "浏览" 按钮, 从您的电脑上选择并上传到服务器.', + 'UI:MissingInlineImage' => '缺少图片', 'UI:ToggleFullScreen' => '切换最大化/最小化', 'UI:Button:ResetImage' => '恢复之前的图片', 'UI:Button:RemoveImage' => '移除图片', diff --git a/pages/ajax.document.php b/pages/ajax.document.php index 3320c298ff..69c9d0a03d 100644 --- a/pages/ajax.document.php +++ b/pages/ajax.document.php @@ -34,6 +34,8 @@ require_once(APPROOT.'/application/startup.inc.php'); require_once(APPROOT.'/application/loginwebpage.class.inc.php'); + LoginWebPage::DoLoginEx(); + IssueLog::Trace('----- Request: '.utils::GetRequestUri(), LogChannels::WEB_REQUEST); $oPage = new DownloadPage(""); @@ -43,7 +45,6 @@ switch ($operation) { case 'download_document': - LoginWebPage::DoLoginEx('backoffice', false); $id = utils::ReadParam('id', ''); $sField = utils::ReadParam('field', ''); if ($sClass == 'Attachment') { @@ -63,8 +64,6 @@ break; case 'download_inlineimage': - // No login is required because the "secret" protects us - // Benefit: the inline image can be inserted into any HTML (templating = $this->html(public_log)$) $id = utils::ReadParam('id', ''); $sSecret = utils::ReadParam('s', ''); $iCacheSec = 31556926; // One year ahead: an inline image cannot change diff --git a/tests/php-unit-tests/unitary-tests/core/InlineImageTest.php b/tests/php-unit-tests/unitary-tests/core/InlineImageTest.php index 1818c97e8c..ae2146d6e4 100644 --- a/tests/php-unit-tests/unitary-tests/core/InlineImageTest.php +++ b/tests/php-unit-tests/unitary-tests/core/InlineImageTest.php @@ -9,6 +9,7 @@ use Combodo\iTop\Test\UnitTest\ItopDataTestCase; use InlineImage; +use ormDocument; class InlineImageTest extends ItopDataTestCase { @@ -98,4 +99,36 @@ public function testFixUrls_shouldReplaceImagesSrcWithCurrentAppRootUrlAndSecret $this->assertStringContainsString(\utils::EscapeHtml(\utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL.'123&s=abc'), $sResult); $this->assertStringContainsString(\utils::EscapeHtml(\utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL.'456&s=def'), $sResult); } + + /** + * @covers InlineImage::ReplaceInlineImagesWithBase64Representation + */ + public function testReplaceInlineImagesWithBase64Representation() + { + // create an inline image in the database + $oInlineImage = $this->createObject(InlineImage::class, [ + 'expire' => (new \DateTime('+1 day'))->format('Y-m-d H:i:s'), + 'item_class' => 'UserRequest', + 'item_id' => 999, + 'item_org_id' => 1, + 'contents' => new ormDocument('0x89504E470D0A1A0A0000000D494844520000000E0000000E08060000001F482DD1000000017352474200AECE1CE90000000467414D410000B18F0BFC6105000000097048597300000EC300000EC301C76FA8640000001E49444154384F63782BA3F29F1CCC802E402C1ED588078F6AC483E9AF11008B8BA9C08A7A3F290000000049454E44AE426082', 'image/png', 'square_red.png'), + 'secret' => 'a94bff3ea6a872bdbc359a1704cdddb3', + ]); + $sInlineImageId = $oInlineImage->GetKey(); + $sInlineImageSecret = $oInlineImage->Get('secret'); + + // HTML with inline image + $sHtml = << +HTML; + + // expected HTML with base64 representation of the image + $sExpected = << +HTML; + + // test the method + $sResult = InlineImage::ReplaceInlineImagesWithBase64Representation($sHtml); + $this->assertEquals($sExpected, $sResult); + } }