Fix fill news & product on email template (#36756)

* Fix fill news & product on email template

* fix CI

* Update mailtemplate.php

---------

Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
This commit is contained in:
Lucas Marcouiller
2026-01-06 00:09:37 +01:00
committed by GitHub
parent ac36cda432
commit e0f3ee85ca
5 changed files with 113 additions and 150 deletions

View File

@@ -1,58 +0,0 @@
<?php
/* Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
*/
// Just for display errors in editor
ini_set('display_errors', 1);
if (!defined('NOTOKENRENEWAL')) {
define('NOTOKENRENEWAL', '1'); // Disables token renewal
}
if (!defined('NOREQUIREMENU')) {
define('NOREQUIREMENU', '1');
}
if (!defined('NOREQUIREHTML')) {
define('NOREQUIREHTML', '1');
}
if (!defined('NOREQUIREAJAX')) {
define('NOREQUIREAJAX', '1');
}
if (!defined('NOREQUIRESOC')) {
define('NOREQUIRESOC', '1');
}
require_once '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/website/class/websitepage.class.php';
/**
* @var Conf $conf
* @var DoliDB $db
* @var HookManager $hookmanager
* @var Translate $langs
* @var User $user
*/
top_httphead();
if ($_SERVER['REQUEST_METHOD'] == 'POST' && GETPOSTISSET('selectedIds')) {
$selectedIds = json_decode(GETPOST('selectedIds'), true);
$websitepage = new WebsitePage($db);
$selectedPosts = array();
foreach ($selectedIds as $id) {
$blog = new WebsitePage($db);
$blog->fetch($id);
$selectedPosts[] = array(
'id' => $blog->id,
'title' => $blog->title,
'description' => $blog->description,
'date_creation' => $blog->date_creation,
'image' => $blog->image,
);
}
print json_encode($selectedPosts);
} else {
print json_encode(array('error' => 'Invalid request'));
}

View File

@@ -52,6 +52,7 @@ require_once '../lib/files.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/website.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
$langs->load("mails");
@@ -156,10 +157,11 @@ if (GETPOSTISSET('template')) {
$content);
$template = GETPOST('template', 'alpha');
// Get list of selected news or products
$selectedPostsStr = GETPOST('selectedPosts', 'alpha');
$selectedIdsStr = GETPOST('selectedPosts', 'alpha');
//$selectedPosts = array();
$selectedPosts = json_decode($selectedPostsStr);
$selectedIds = json_decode($selectedIdsStr);
/*if (is_array($selectedPostsStr)) {
$selectedPosts = explode(',', $selectedPostsStr);
}*/
@@ -172,28 +174,43 @@ if (GETPOSTISSET('template')) {
}
} */
if (is_array($selectedPosts) && !empty($selectedPosts)) {
if (!empty($selectedIds) && !empty($template) && is_array($selectedIds) ) {
$newsList = '';
$productList = '';
foreach ($selectedIds as $Id) {
if ($template == "news") {
$post = getNewsDetailsById($Id);
foreach ($selectedPosts as $postId) {
$post = getNewsDetailsById($postId);
$newsList .= '<div style="display: flex; align-items: flex-start; justify-content: flex-start; width: 100%; max-width: 800px; margin-top: 20px;margin-bottom: 50px; padding: 20px;">
<div style="flex-grow: 1; margin-right: 30px; max-width: 600px; margin-left: 100px;">
<h2 style="margin: 0; font-size: 1.5em;">' . (empty($post['title']) ? '' : dol_htmlentitiesbr($post['title'])) . '</h2>
<p style="margin: 10px 0; color: #555;">' . (empty($post['description']) ? '' : dol_htmlentitiesbr($post['description'])) . '</p>
<span style="display: block; margin-bottom: 5px; color: #888;">Created By: <strong>' . dol_htmlentitiesbr(empty($post['user_fullname']) ? '' : $post['user_fullname']) . '</strong></span>
<br>
<span style="display: block; color: #888;">' . dol_print_date((empty($post['date_creation']) ? dol_now() : $post['date_creation']), 'daytext', 'tzserver', $langs) . '</span>
</div>
<div style="flex-shrink: 0; margin-left: 100px; float: right;">
' . (!empty($post['image']) ? '<img alt="Image" width="130px" height="130px" style="border-radius: 10px;" src="' . DOL_URL_ROOT . '/viewimage.php?modulepart=medias&file=' . dol_htmlentitiesbr($post['image']) . '">' : '<img alt="Gray rectangle" width="130px" height="130px" style="border-radius: 10px;" src="__GRAY_RECTANGLE__">') . '
</div>
</div>';
} elseif ($template == "product") {
$product = getProductForEmailTemplate($Id);
$newsList .= '<div style="display: flex; align-items: flex-start; justify-content: flex-start; width: 100%; max-width: 800px; margin-top: 20px;margin-bottom: 50px; padding: 20px;">
<div style="flex-grow: 1; margin-right: 30px; max-width: 600px; margin-left: 100px;">
<h2 style="margin: 0; font-size: 1.5em;">' . htmlentities(empty($post['title']) ? '' : $post['title']) . '</h2>
<p style="margin: 10px 0; color: #555;">' . htmlentities(empty($post['description']) ? '' : $post['description']) . '</p>
<span style="display: block; margin-bottom: 5px; color: #888;">Created By: <strong>' . htmlentities(empty($post['user_fullname']) ? '' : $post['user_fullname']) . '</strong></span>
<br>
<span style="display: block; color: #888;">' . dol_print_date((empty($post['date_creation']) ? dol_now() : $post['date_creation']), 'daytext', 'tzserver', $langs) . '</span>
</div>
<div style="flex-shrink: 0; margin-left: 100px; float: right;">
' . (!empty($post['image']) ? '<img alt="Image" width="130px" height="130px" style="border-radius: 10px;" src="' . DOL_URL_ROOT . '/viewimage.php?modulepart=medias&file=' . htmlentities($post['image']) . '">' : '<img alt="Gray rectangle" width="130px" height="130px" style="border-radius: 10px;" src="__GRAY_RECTANGLE__">') . '
</div>
</div>';
$productList .= '<div style="width:100%; padding: 20px;">
<div>
' . (!empty($product['image']) ? dol_htmlentitiesbr($product['image']) : '<img alt="Gray rectangle" width="130px" height="130px" style="border-radius: 10px;" src="__GRAY_RECTANGLE__">') . '
</div>
<div>
<h2>'.(dol_htmlentitiesbr($product["ref"])).(empty($product["label"]) ? '' : ' - '.dol_htmlentitiesbr($product["label"])).'</h2>
<p style="margin: 10px 0; color: #555;">'. (empty($product['description']) ? '' : dol_htmlentitiesbr($product['description'])) .'</p>
</div>
</div>
';
}
}
$content = str_replace('__NEWS_LIST__', $newsList, $content);
$content = str_replace('__PRODUCT_SELECTED__', $newsList, $content);
$content = str_replace('__PRODUCT_SELECTED__', $productList, $content);
} else {
$content = str_replace('__NEWS_LIST__', $langs->trans("SelectSomeArticlesOrEnterYourOwnContent"), $content);
$content = str_replace('__PRODUCT_SELECTED__', $langs->trans("SelectOneArticleOrEnterYourOwnContent"), $content);

View File

@@ -1578,11 +1578,28 @@ class FormMail extends Form
}
}
// Fetch Product / Services
$productArray = array();
if (isModEnabled('product') || isModEnabled('service')) {
include_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
$form = new Form($this->db);
$arrayofproduct = $form->select_produits_list(0, 'product-select', '', 0, 0, '', 1, 2, 1);
if (!empty($arrayofproduct)) {
foreach ($arrayofproduct as $product) {
$productArray[$product["key"]] = array(
'id' => $product["key"],
'label' => $product["value"].' - '.dol_trunc($product["label2"], 40),
'labelhtml' => $product["value"].' - '.dol_trunc($product["label2"], 40),
);
}
}
}
// Use the multiselect array function to create the dropdown
$out .= '<div id="post-dropdown-container" class="email-layout-container hidden" style="height: 32px; display:none;">';
$out .= '<label for="blogpost-select">Select Posts: </label>';
$out .= '<!-- select component for selection of blog posts -->'."\n";
$out .= self::multiselectarray('blogpost-select', $blogArray, array(), 0, 0, 'minwidth200');
$out .= self::multiselectarray('blogpost-select', $blogArray, array(), 0, 0, 'minwidth200 select-template');
$out .= ' <input type="submit" class="smallpaddingimp button" name="submit" id="post-submit" value="'.dolPrintHTMLForAttribute($langs->trans("Select")).'">';
$out .= '</div>';
@@ -1592,7 +1609,7 @@ class FormMail extends Form
$out .= '<div id="product-dropdown-container" class="email-layout-container hidden" style="height: 32px; display:none;">';
$out .= '<label for="product-select">'.img_picto('', 'product', 'class="pictofixedwidth"').$langs->trans("Product").' : </label>';
$out .= '<!-- select component for selection of product -->'."\n";
$out .= $form->select_produits(0, 'product-select', '', 0, 0, 1, 2, '', 0, array(), 0, '1', 0, '', 0, '', null, 1);
$out .= self::multiselectarray('product-select', $productArray, array(), 0, 0, 'minwidth200 select-template');
$out .= ' <input type="submit" class="smallpaddingimp button" name="submit" id="product-submit" value="'.dolPrintHTMLForAttribute($langs->trans("Select")).'">';
$out .= '</div>';
}
@@ -1612,6 +1629,7 @@ class FormMail extends Form
$(".template-option").removeClass("selected");
$(this).addClass("selected");
$(".select-template").val("").trigger("change");
if (template === "news") {
$("#post-dropdown-container").show();
@@ -1659,46 +1677,38 @@ class FormMail extends Form
updateSelectedPostsContent(contentHtml, selectedIds);
});
$("#product-select").change(function() {
var selectedIds = $(this).val();
var contentHtml = $(".template-option.selected").data("content");
updateSelectedPostsContent(contentHtml, selectedIds);
});
function updateSelectedPostsContent(contentHtml, selectedIds) {
var csrfToken = "' .newToken().'";
template = $(".template-option.selected").data("template");
var subject = $("#subject").val();
$.ajax({
type: "POST",
url: "'.dol_buildpath('/core/ajax/getnews.php', 1).'",
url: "'.dol_buildpath('/core/ajax/mailtemplate.php', 1).'",
data: {
selectedIds: JSON.stringify(selectedIds),
token : csrfToken
token: csrfToken,
template: template,
subject: subject,
selectedPosts: JSON.stringify(selectedIds)
},
success: function(response) {
var selectedPosts = JSON.parse(response);
var subject = $("#subject").val();
contentHtml = contentHtml.replace(/__SUBJECT__/g, subject);
template = $(".template-option.selected").data("template");
$.ajax({
type: "POST",
url: "'.dol_buildpath('/core/ajax/mailtemplate.php', 1).'",
data: {
token: csrfToken,
template: template,
subject: subject,
selectedPosts: JSON.stringify(selectedIds)
},
success: function(response) {
jQuery("#'.$htmlContent.'").val(response);
var editorInstance = CKEDITOR.instances["'.$htmlContent.'"];
if (editorInstance) {
editorInstance.setData(response);
}
},
error: function(xhr, status, error) {
console.error("An error occurred: " + xhr.responseText);
}
});
jQuery("#'.$htmlContent.'").val(response);
var editorInstance = CKEDITOR.instances["'.$htmlContent.'"];
if (editorInstance) {
editorInstance.setData(response);
}
},
error: function(xhr, status, error) {
console.error("An error occurred: " + xhr.responseText);
}
});
}
});
</script>';

View File

@@ -1024,3 +1024,42 @@ function measuring_units_cubed($unit)
$measuring_units[99] = 89; // inch -> inch3
return $measuring_units[$unit];
}
/**
* Retrieve and return product for mail template.
*
* @param int $id The ID of the product to retrieve.
* @return array<string,mixed>|int<-1,-1> Return array if OK, -1 if KO
*/
function getProductForEmailTemplate($id)
{
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
global $db, $conf;
$productarray = array();
$sql = "SELECT p.rowid as id, p.ref, p.label, p.description, p.entity";
$sql .= " FROM ".MAIN_DB_PREFIX."product as p";
$sql .= " WHERE p.entity IN (".getEntity('product').")";
$sql .= " AND p.rowid = ".((int) $id);
$resql = $db->query($sql);
if ($resql) {
$productarray = $db->fetch_array($resql);
} else {
dol_print_error($db);
return -1;
}
$object = new Product($db);
$result = $object->fetch($id);
if ($result < 0) {
dol_print_error($db, $object->error, $object->errors);
}
$entity = (empty($object->entity) ? $conf->entity : $object->entity);
$productarray["image"] = $object->show_photos('product', $conf->product->multidir_output[$entity], 1, 1, 0, 0, 0, 120, 160, 1, '');
if ($object->nbphoto <= 0) {
$productarray["image"] = "";
}
return $productarray;
}

View File

@@ -7313,51 +7313,6 @@ class Product extends CommonObject
$return .= '</div>';
return $return;
}
/**
* Retrieve and display products.
*
* @param int $limit The maximum number of results to return.
* @return array<int, array<string, mixed>>|int return array if OK, -1 if KO
*/
public function getProductsToPreviewInEmail($limit)
{
if (!is_numeric($limit)) {
return -1;
}
$sql = "SELECT p.rowid, p.ref, p.label, p.description, p.entity, ef.filename
FROM ".MAIN_DB_PREFIX."product AS p
JOIN ".MAIN_DB_PREFIX."ecm_files AS ef ON p.rowid = ef.src_object_id
WHERE ef.entity IN (".getEntity('product').")
AND (ef.filename LIKE '%.png' OR ef.filename LIKE '%.jpeg' OR ef.filename LIKE '%.svg')
GROUP BY p.rowid, p.ref, p.label, p.description, p.entity, ef.filename
ORDER BY p.datec ASC
LIMIT " . ((int) $limit);
$resql = $this->db->query($sql);
$products = array();
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$products[] = array(
'rowid' => $obj->rowid,
'ref' => $obj->ref,
'label' => $obj->label,
'description' => $obj->description,
'entity' => $obj->entity,
'filename' => $obj->filename
);
}
} else {
dol_print_error($this->db);
}
if (empty($products)) {
return -1;
}
return $products;
}
}
/**