From 8150cc615986cea0ffe7ed4c0e2658b1993bad51 Mon Sep 17 00:00:00 2001 From: Regis Houssin Date: Thu, 13 Feb 2025 18:20:47 +0100 Subject: [PATCH 01/26] FIX wrong ODT path for multicompany --- htdocs/core/modules/modAdherent.class.php | 6 +++--- htdocs/core/modules/modBom.class.php | 4 ++-- htdocs/core/modules/modContrat.class.php | 4 ++-- htdocs/core/modules/modExpedition.class.php | 6 +++--- htdocs/core/modules/modFournisseur.class.php | 8 ++++---- htdocs/core/modules/modHoliday.class.php | 4 ++-- htdocs/core/modules/modMrp.class.php | 4 ++-- htdocs/core/modules/modProjet.class.php | 8 ++++---- htdocs/core/modules/modReception.class.php | 4 ++-- htdocs/core/modules/modSociete.class.php | 4 ++-- htdocs/core/modules/modStock.class.php | 6 +++--- htdocs/core/modules/modSupplierProposal.class.php | 4 ++-- htdocs/core/modules/modTicket.class.php | 4 ++-- .../template/core/modules/modMyModule.class.php | 2 +- 14 files changed, 34 insertions(+), 34 deletions(-) diff --git a/htdocs/core/modules/modAdherent.class.php b/htdocs/core/modules/modAdherent.class.php index 51dd74cc117..2445000cee1 100644 --- a/htdocs/core/modules/modAdherent.class.php +++ b/htdocs/core/modules/modAdherent.class.php @@ -182,7 +182,7 @@ class modAdherent extends DolibarrModules $this->const[$r][0] = "MEMBER_ADDON_PDF_ODT_PATH"; $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT/doctemplates/members"; + $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/members"; $this->const[$r][3] = ""; $this->const[$r][4] = 0; $r++; @@ -438,8 +438,8 @@ class modAdherent extends DolibarrModules // ODT template /* - $src=DOL_DOCUMENT_ROOT.'/install/doctemplates/orders/template_order.odt'; - $dirodt=DOL_DATA_ROOT.'/doctemplates/orders'; + $src=DOL_DOCUMENT_ROOT.'/install/doctemplates/members/template_member.odt'; + $dirodt=DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/members'; $dest=$dirodt.'/template_order.odt'; if (file_exists($src) && ! file_exists($dest)) { diff --git a/htdocs/core/modules/modBom.class.php b/htdocs/core/modules/modBom.class.php index c08828442c9..748332a744c 100644 --- a/htdocs/core/modules/modBom.class.php +++ b/htdocs/core/modules/modBom.class.php @@ -127,7 +127,7 @@ class modBom extends DolibarrModules $this->const = array( 1 => array('BOM_ADDON_PDF', 'chaine', 'generic_bom_odt', 'Name of PDF model of BOM', 0), 2 => array('BOM_ADDON', 'chaine', 'mod_bom_standard', 'Name of numbering rules of BOM', 0), - 3 => array('BOM_ADDON_PDF_ODT_PATH', 'chaine', 'DOL_DATA_ROOT/doctemplates/boms', '', 0) + 3 => array('BOM_ADDON_PDF_ODT_PATH', 'chaine', 'DOL_DATA_ROOT'.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/boms', '', 0) ); // Some keys to add into the overwriting translation tables @@ -479,7 +479,7 @@ class modBom extends DolibarrModules // ODT template $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/boms/template_bom.odt'; - $dirodt = DOL_DATA_ROOT.'/doctemplates/boms'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/boms'; $dest = $dirodt.'/template_bom.odt'; if (file_exists($src) && !file_exists($dest)) { diff --git a/htdocs/core/modules/modContrat.class.php b/htdocs/core/modules/modContrat.class.php index 8154c3f8902..a6bab80e3be 100644 --- a/htdocs/core/modules/modContrat.class.php +++ b/htdocs/core/modules/modContrat.class.php @@ -87,7 +87,7 @@ class modContrat extends DolibarrModules $this->const[$r][0] = "CONTRACT_ADDON_PDF_ODT_PATH"; $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT/doctemplates/contracts"; + $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/contracts"; $this->const[$r][3] = ""; $this->const[$r][4] = 0; $r++; @@ -230,7 +230,7 @@ class modContrat extends DolibarrModules //ODT template $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/contracts/template_contract.odt'; - $dirodt = DOL_DATA_ROOT.'/doctemplates/contracts'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/contracts'; $dest = $dirodt.'/template_contract.odt'; if (file_exists($src) && !file_exists($dest)) { diff --git a/htdocs/core/modules/modExpedition.class.php b/htdocs/core/modules/modExpedition.class.php index 0448e1a8f11..aadfda78a18 100644 --- a/htdocs/core/modules/modExpedition.class.php +++ b/htdocs/core/modules/modExpedition.class.php @@ -99,7 +99,7 @@ class modExpedition extends DolibarrModules $this->const[$r][0] = "EXPEDITION_ADDON_PDF_ODT_PATH"; $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT/doctemplates/shipments"; + $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/shipments"; $this->const[$r][3] = ""; $this->const[$r][4] = 0; $r++; @@ -120,7 +120,7 @@ class modExpedition extends DolibarrModules $this->const[$r][0] = "DELIVERY_ADDON_PDF_ODT_PATH"; $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT/doctemplates/deliveries"; + $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/deliveries"; $this->const[$r][3] = ""; $this->const[$r][4] = 0; $r++; @@ -343,7 +343,7 @@ class modExpedition extends DolibarrModules //ODT template $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/shipments/template_shipment.odt'; - $dirodt = DOL_DATA_ROOT.'/doctemplates/shipments'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/shipments'; $dest = $dirodt.'/template_shipment.odt'; if (file_exists($src) && !file_exists($dest)) { diff --git a/htdocs/core/modules/modFournisseur.class.php b/htdocs/core/modules/modFournisseur.class.php index 5e7a31b3d77..6a2dacb6bcc 100644 --- a/htdocs/core/modules/modFournisseur.class.php +++ b/htdocs/core/modules/modFournisseur.class.php @@ -117,7 +117,7 @@ class modFournisseur extends DolibarrModules // Add ability ODT for Supplier orders $this->const[$r][0] = "SUPPLIER_ORDER_ADDON_PDF_ODT_PATH"; $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT/doctemplates/supplier_orders"; + $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/supplier_orders"; $this->const[$r][3] = ''; $this->const[$r][4] = 0; $r++; @@ -125,7 +125,7 @@ class modFournisseur extends DolibarrModules // Add ability ODT for Supplier Invoices $this->const[$r][0] = "SUPPLIER_INVOICE_ADDON_PDF_ODT_PATH"; $this->const[$r][1] = "chaine"; - $this->const[$r][2] = ""; + $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/supplier_invoices"; $this->const[$r][3] = ""; $this->const[$r][4] = 0; $r++; @@ -972,7 +972,7 @@ class modFournisseur extends DolibarrModules //ODT template for Supplier Orders $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/supplier_orders/template_supplier_order.odt'; - $dirodt = DOL_DATA_ROOT.'/doctemplates/supplier_orders'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/supplier_orders'; $dest = $dirodt.'/template_supplier_order.odt'; if (file_exists($src) && !file_exists($dest)) { @@ -993,7 +993,7 @@ class modFournisseur extends DolibarrModules //ODT template for Supplier Invoice $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/supplier_invoices/template_supplier_invoices.odt'; - $dirodt = DOL_DATA_ROOT.'/doctemplates/supplier_invoices'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/supplier_invoices'; $dest = $dirodt.'/template_supplier_invoices.odt'; if (file_exists($src) && !file_exists($dest)) { diff --git a/htdocs/core/modules/modHoliday.class.php b/htdocs/core/modules/modHoliday.class.php index 5e4002b26a4..453bb966a06 100644 --- a/htdocs/core/modules/modHoliday.class.php +++ b/htdocs/core/modules/modHoliday.class.php @@ -111,7 +111,7 @@ class modHoliday extends DolibarrModules $this->const[$r][0] = "HOLIDAY_ADDON_PDF_ODT_PATH"; $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT/doctemplates/holiday"; + $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/holiday"; $this->const[$r][3] = ""; $this->const[$r][4] = 0; $r++; @@ -308,7 +308,7 @@ class modHoliday extends DolibarrModules //ODT template /*$src=DOL_DOCUMENT_ROOT.'/install/doctemplates/holiday/template_holiday.odt'; - $dirodt=DOL_DATA_ROOT.'/doctemplates/holiday'; + $dirodt=DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/holiday'; $dest=$dirodt.'/template_order.odt'; if (file_exists($src) && ! file_exists($dest)) diff --git a/htdocs/core/modules/modMrp.class.php b/htdocs/core/modules/modMrp.class.php index 08456a9f99c..5b6289c8f10 100644 --- a/htdocs/core/modules/modMrp.class.php +++ b/htdocs/core/modules/modMrp.class.php @@ -138,7 +138,7 @@ class modMrp extends DolibarrModules $this->const = array( //1=>array('MRP_MO_ADDON_PDF', 'chaine', 'vinci', 'Name of default PDF model of MO', 0), 2=>array('MRP_MO_ADDON', 'chaine', 'mod_mo_standard', 'Name of numbering rules of MO', 0), - 3=>array('MRP_MO_ADDON_PDF_ODT_PATH', 'chaine', 'DOL_DATA_ROOT/doctemplates/mrps', '', 0) + 3=>array('MRP_MO_ADDON_PDF_ODT_PATH', 'chaine', 'DOL_DATA_ROOT'.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/mrps', '', 0) ); // Some keys to add into the overwriting translation tables @@ -442,7 +442,7 @@ class modMrp extends DolibarrModules // ODT template $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/mrps/template_mo.odt'; - $dirodt = DOL_DATA_ROOT.'/doctemplates/mrps'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/mrps'; $dest = $dirodt.'/template_mo.odt'; if (file_exists($src) && !file_exists($dest)) { diff --git a/htdocs/core/modules/modProjet.class.php b/htdocs/core/modules/modProjet.class.php index bed2e414cc6..0508a540ce8 100644 --- a/htdocs/core/modules/modProjet.class.php +++ b/htdocs/core/modules/modProjet.class.php @@ -92,7 +92,7 @@ class modProjet extends DolibarrModules $this->const[$r][0] = "PROJECT_ADDON_PDF_ODT_PATH"; $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT/doctemplates/projects"; + $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/projects"; $this->const[$r][3] = ""; $this->const[$r][4] = 0; $r++; @@ -113,7 +113,7 @@ class modProjet extends DolibarrModules $this->const[$r][0] = "PROJECT_TASK_ADDON_PDF_ODT_PATH"; $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT/doctemplates/tasks"; + $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/tasks"; $this->const[$r][3] = ""; $this->const[$r][4] = 0; $r++; @@ -379,7 +379,7 @@ class modProjet extends DolibarrModules //ODT template for project $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/projects/template_project.odt'; - $dirodt = DOL_DATA_ROOT.'/doctemplates/projects'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/projects'; $dest = $dirodt.'/template_project.odt'; if (file_exists($src) && !file_exists($dest)) { @@ -395,7 +395,7 @@ class modProjet extends DolibarrModules //ODT template for tasks $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/tasks/template_task_summary.odt'; - $dirodt = DOL_DATA_ROOT.'/doctemplates/tasks'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/tasks'; $dest = $dirodt.'/template_task_summary.odt'; if (file_exists($src) && !file_exists($dest)) { diff --git a/htdocs/core/modules/modReception.class.php b/htdocs/core/modules/modReception.class.php index e424325734e..e76ce8b8b78 100644 --- a/htdocs/core/modules/modReception.class.php +++ b/htdocs/core/modules/modReception.class.php @@ -91,7 +91,7 @@ class modReception extends DolibarrModules $this->const[$r][0] = "RECEPTION_ADDON_PDF_ODT_PATH"; $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT/doctemplates/receptions"; + $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/receptions"; $this->const[$r][3] = ""; $this->const[$r][4] = 0; $r++; @@ -266,7 +266,7 @@ class modReception extends DolibarrModules //ODT template $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/reception/template_reception.odt'; - $dirodt = DOL_DATA_ROOT.'/doctemplates/reception'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/reception'; $dest = $dirodt.'/template_reception.odt'; if (file_exists($src) && !file_exists($dest)) { diff --git a/htdocs/core/modules/modSociete.class.php b/htdocs/core/modules/modSociete.class.php index 0c075298d3a..43d8a9ac94b 100644 --- a/htdocs/core/modules/modSociete.class.php +++ b/htdocs/core/modules/modSociete.class.php @@ -101,7 +101,7 @@ class modSociete extends DolibarrModules $this->const[$r][0] = "COMPANY_ADDON_PDF_ODT_PATH"; $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT/doctemplates/thirdparties"; + $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/thirdparties"; $this->const[$r][3] = ""; $this->const[$r][4] = 0; $r++; @@ -947,7 +947,7 @@ class modSociete extends DolibarrModules //ODT template $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/thirdparties/template_thirdparty.odt'; - $dirodt = DOL_DATA_ROOT.'/doctemplates/thirdparties'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/thirdparties'; $dest = $dirodt.'/template_thirdparty.odt'; if (file_exists($src) && !file_exists($dest)) { diff --git a/htdocs/core/modules/modStock.class.php b/htdocs/core/modules/modStock.class.php index c55ad96883c..640300b9479 100644 --- a/htdocs/core/modules/modStock.class.php +++ b/htdocs/core/modules/modStock.class.php @@ -96,14 +96,14 @@ class modStock extends DolibarrModules $r++; $this->const[$r][0] = "STOCK_ADDON_PDF_ODT_PATH"; $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT/doctemplates/stocks"; + $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/stocks"; $this->const[$r][3] = ""; $this->const[$r][4] = 0; $r++; $this->const[$r][0] = "MOUVEMENT_ADDON_PDF_ODT_PATH"; $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT/doctemplates/stocks/movements"; + $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/stocks/movements"; $this->const[$r][3] = ""; $this->const[$r][4] = 0; @@ -538,7 +538,7 @@ class modStock extends DolibarrModules //ODT template $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/stocks/template_warehouse.odt'; - $dirodt = DOL_DATA_ROOT.'/doctemplates/stocks'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/stocks'; $dest = $dirodt.'/template_warehouse.odt'; if (file_exists($src) && !file_exists($dest)) { diff --git a/htdocs/core/modules/modSupplierProposal.class.php b/htdocs/core/modules/modSupplierProposal.class.php index d7c7941d08b..4dacfc3ed09 100644 --- a/htdocs/core/modules/modSupplierProposal.class.php +++ b/htdocs/core/modules/modSupplierProposal.class.php @@ -92,7 +92,7 @@ class modSupplierProposal extends DolibarrModules $this->const[$r][0] = "SUPPLIER_PROPOSAL_ADDON_PDF_ODT_PATH"; $this->const[$r][1] = "chaine"; - $this->const[$r][2] = "DOL_DATA_ROOT/doctemplates/supplier_proposals"; + $this->const[$r][2] = "DOL_DATA_ROOT".($conf->entity > 1 ? '/'.$conf->entity : '')."/doctemplates/supplier_proposals"; $this->const[$r][3] = ""; $this->const[$r][4] = 0; @@ -163,7 +163,7 @@ class modSupplierProposal extends DolibarrModules //ODT template $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/supplier_proposals/template_supplier_proposal.odt'; - $dirodt = DOL_DATA_ROOT.'/doctemplates/supplier_proposals'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/supplier_proposals'; $dest = $dirodt.'/template_supplier_proposal.odt'; if (file_exists($src) && !file_exists($dest)) { diff --git a/htdocs/core/modules/modTicket.class.php b/htdocs/core/modules/modTicket.class.php index 17e6ecc9430..4fd1a842ed6 100644 --- a/htdocs/core/modules/modTicket.class.php +++ b/htdocs/core/modules/modTicket.class.php @@ -110,7 +110,7 @@ class modTicket extends DolibarrModules $this->const = array( 1 => array('TICKET_ENABLE_PUBLIC_INTERFACE', 'chaine', '0', 'Enable ticket public interface', 0), 2 => array('TICKET_ADDON', 'chaine', 'mod_ticket_simple', 'Ticket ref module', 0), - 3 => array('TICKET_ADDON_PDF_ODT_PATH', 'chaine', 'DOL_DATA_ROOT/doctemplates/tickets', 'Ticket templates ODT/ODS directory for templates', 0), + 3 => array('TICKET_ADDON_PDF_ODT_PATH', 'chaine', 'DOL_DATA_ROOT'.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/tickets', 'Ticket templates ODT/ODS directory for templates', 0), 4 => array('TICKET_AUTO_READ_WHEN_CREATED_FROM_BACKEND', 'chaine', 0, 'Automatically mark ticket as read when created from backend', 0), 5 => array('TICKET_DELAY_BEFORE_FIRST_RESPONSE', 'chaine', '0', 'Maximum wanted elapsed time before a first answer to a ticket (in hours). Display a warning in tickets list if not respected.', 0), 6 => array('TICKET_DELAY_SINCE_LAST_RESPONSE', 'chaine', '0', 'Maximum wanted elapsed time between two answers on the same ticket (in hours). Display a warning in tickets list if not respected.', 0), @@ -414,7 +414,7 @@ class modTicket extends DolibarrModules //ODT template $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/tickets/template_ticket.odt'; - $dirodt = DOL_DATA_ROOT.'/doctemplates/tickets'; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/tickets'; $dest = $dirodt.'/template_ticket.odt'; if (file_exists($src) && !file_exists($dest)) { diff --git a/htdocs/modulebuilder/template/core/modules/modMyModule.class.php b/htdocs/modulebuilder/template/core/modules/modMyModule.class.php index c79c226fc6f..f7f6805d64a 100644 --- a/htdocs/modulebuilder/template/core/modules/modMyModule.class.php +++ b/htdocs/modulebuilder/template/core/modules/modMyModule.class.php @@ -496,7 +496,7 @@ class modMyModule extends DolibarrModules } if ($myTmpObjectArray['includerefgeneration']) { $src = DOL_DOCUMENT_ROOT.'/install/doctemplates/'.$moduledir.'/template_myobjects.odt'; - $dirodt = DOL_DATA_ROOT.'/doctemplates/'.$moduledir; + $dirodt = DOL_DATA_ROOT.($conf->entity > 1 ? '/'.$conf->entity : '').'/doctemplates/'.$moduledir; $dest = $dirodt.'/template_myobjects.odt'; if (file_exists($src) && !file_exists($dest)) { From f3eec990407d80579be8eb8c38d984ffe4d91c8b Mon Sep 17 00:00:00 2001 From: ldestailleur Date: Fri, 14 Feb 2025 23:22:40 +0100 Subject: [PATCH 02/26] Fix #33048 warning --- htdocs/user/virtualcard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/user/virtualcard.php b/htdocs/user/virtualcard.php index d6262bcbe3e..7b2205f7824 100644 --- a/htdocs/user/virtualcard.php +++ b/htdocs/user/virtualcard.php @@ -52,7 +52,7 @@ if (empty($id) && empty($ref)) { $id = $user->id; } -$expand = $_COOKIE['virtualcard_expand']; +$expand = empty($_COOKIE['virtualcard_expand']) ? '' : $_COOKIE['virtualcard_expand']; $object = new User($db); if ($id > 0 || !empty($ref)) { From 2a2584b765fa0b8b1fadf1b834305df643e93104 Mon Sep 17 00:00:00 2001 From: "Laurent Destailleur (aka Eldy)" Date: Fri, 14 Feb 2025 23:54:43 +0100 Subject: [PATCH 03/26] Debug v21 --- htdocs/product/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/product/index.php b/htdocs/product/index.php index fdca1c4133a..9f08e92aa35 100644 --- a/htdocs/product/index.php +++ b/htdocs/product/index.php @@ -655,7 +655,7 @@ if (isModEnabled('stock') && $user->hasRight('stock', 'mouvement', 'read')) { $db->free($resql); if (empty($num)) { - $colspan = 4; + $colspan = 5; if (isModEnabled('productbatch')) { $colspan++; } From ed87ce34bf39c118b42fb5bc30e48b309046bf42 Mon Sep 17 00:00:00 2001 From: "Laurent Destailleur (aka Eldy)" Date: Sat, 15 Feb 2025 00:09:10 +0100 Subject: [PATCH 04/26] Debug v21 --- htdocs/core/ajax/box.php | 7 ++----- htdocs/core/class/infobox.class.php | 14 +++++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/htdocs/core/ajax/box.php b/htdocs/core/ajax/box.php index 775f1465def..d1e2e1a4990 100644 --- a/htdocs/core/ajax/box.php +++ b/htdocs/core/ajax/box.php @@ -53,10 +53,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/infobox.class.php'; $boxid = GETPOSTINT('boxid'); $boxorder = GETPOST('boxorder'); -$zone = GETPOST('zone'); // Can be key for zone -if ($zone !== '') { - $zone = (int) $zone; -} +$zone = GETPOST('zone'); // Can be '0' or '1' or 'pagename'... $userid = GETPOSTINT('userid'); // Security check @@ -91,7 +88,7 @@ if ($boxorder && $zone != '' && $userid > 0) { // boxorder value is the target order: "A:idboxA1,idboxA2,A-B:idboxB1,idboxB2,B" dol_syslog("AjaxBox boxorder=".$boxorder." zone=".$zone." userid=".$userid, LOG_DEBUG); - $result = InfoBox::saveboxorder($db, (int) $zone, $boxorder, $userid); + $result = InfoBox::saveboxorder($db, $zone, $boxorder, $userid); if ($result > 0) { $langs->load("boxes"); if (!GETPOST('closing')) { diff --git a/htdocs/core/class/infobox.class.php b/htdocs/core/class/infobox.class.php index cfec6d48773..68340e873d2 100644 --- a/htdocs/core/class/infobox.class.php +++ b/htdocs/core/class/infobox.class.php @@ -217,11 +217,11 @@ class InfoBox /** * Save order of boxes for area and user * - * @param DoliDB $dbs Database handler - * @param int $zone Key of area (0 for Homepage, ...) - * @param string $boxorder List of boxes with correct order 'A:123,456,...-B:789,321...' - * @param int $userid Id of user - * @return int Return integer <0 if KO, 0=Nothing done, > 0 if OK + * @param DoliDB $dbs Database handler + * @param int|string $zone Key of area ('0' for Homepage, '1', 'pagename', ...) + * @param string $boxorder List of boxes with correct order 'A:123,456,...-B:789,321...' + * @param int $userid Id of user + * @return int Return integer <0 if KO, 0=Nothing done, > 0 if OK */ public static function saveboxorder($dbs, $zone, $boxorder, $userid = 0) { @@ -252,6 +252,10 @@ class InfoBox return -3; } + if (!is_numeric($zone)) { + $zone = '0'; // Force $zone to a numeric value string + } + // Delete all lines $sql = "DELETE FROM ".$dbs->prefix()."boxes"; $sql .= " WHERE entity = ".$conf->entity; From 45321f938a87eb07dcfe231d2703e0a934d12cd4 Mon Sep 17 00:00:00 2001 From: "Laurent Destailleur (aka Eldy)" Date: Sat, 15 Feb 2025 18:10:41 +0100 Subject: [PATCH 05/26] Debug v21 --- htdocs/core/lib/geturl.lib.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/htdocs/core/lib/geturl.lib.php b/htdocs/core/lib/geturl.lib.php index 70ba14f40b1..bc105b30330 100644 --- a/htdocs/core/lib/geturl.lib.php +++ b/htdocs/core/lib/geturl.lib.php @@ -51,6 +51,10 @@ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = dol_syslog("getURLContent postorget=".$postorget." URL=".$url." param=".$param); + if (!function_exists('curl_init')) { + return array('curl_error_no' => 'PHP curl lib not available', 'curl_error_msg' => 'PHP curl library must be installed'); + } + //setting the curl parameters. $ch = curl_init(); From 2f409f0065008d6e205ed029f5a1c6a5a0b0b925 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 16 Feb 2025 14:00:52 +0100 Subject: [PATCH 06/26] Update README.md --- dev/setup/pre-commit/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dev/setup/pre-commit/README.md b/dev/setup/pre-commit/README.md index 71318703b91..72e877d9a81 100644 --- a/dev/setup/pre-commit/README.md +++ b/dev/setup/pre-commit/README.md @@ -44,8 +44,22 @@ the project: `pre-commit-config.yaml`. The good file redirects output to the error channel so your IDE will be able to catch the error. +### Troubleshooting + +# If you get error "ModuleNotFoundError: No module named 'platformdirs'" + +Install the python package with +`pip3 install platformdirs` or `pip3 install platformdirs --break-system-packages` + +# if yoy get error "ERROR: PHP_CodeSniffer requires the tokenizer, xmlwriter and SimpleXML extensions to be enabled. Please enable xmlwriter and SimpleXML." + +Install the PHP package xml +`sudo apt install php-simplexml` + + ### Tips + After installing `pre-commit` onto your local git clone, pre-commit will run on every commit. The first time, all tools required by pre-commit will be installed into ~/.cache/pre-commit From aec8d675455510e6febe02d6a0f5f60d5a88b4bd Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 16 Feb 2025 14:17:21 +0100 Subject: [PATCH 07/26] Update README.md --- dev/setup/pre-commit/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/setup/pre-commit/README.md b/dev/setup/pre-commit/README.md index 72e877d9a81..4c32111e5e9 100644 --- a/dev/setup/pre-commit/README.md +++ b/dev/setup/pre-commit/README.md @@ -46,12 +46,12 @@ the project: `pre-commit-config.yaml`. ### Troubleshooting -# If you get error "ModuleNotFoundError: No module named 'platformdirs'" +* If you get error "ModuleNotFoundError: No module named 'platformdirs'" Install the python package with `pip3 install platformdirs` or `pip3 install platformdirs --break-system-packages` -# if yoy get error "ERROR: PHP_CodeSniffer requires the tokenizer, xmlwriter and SimpleXML extensions to be enabled. Please enable xmlwriter and SimpleXML." +* If you get error "ERROR: PHP_CodeSniffer requires the tokenizer, xmlwriter and SimpleXML extensions to be enabled. Please enable xmlwriter and SimpleXML." Install the PHP package xml `sudo apt install php-simplexml` From cb18bebb87d06dc3ecbbf367ad645171ad85c70f Mon Sep 17 00:00:00 2001 From: ldestailleur Date: Sun, 16 Feb 2025 14:23:16 +0100 Subject: [PATCH 08/26] Fix warning on date --- htdocs/core/class/commonobject.class.php | 7 ++++--- htdocs/projet/class/task.class.php | 5 ++--- htdocs/projet/tasks/time.php | 4 +++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index 32796db2147..200c6222a3e 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -10059,9 +10059,9 @@ abstract class CommonObject /** * Create object in the database * - * @param User $user User that creates - * @param int<0,1> $notrigger 0=launch triggers after, 1=disable triggers - * @return int<-1,max> Return integer <0 if KO, Id of created object if OK + * @param User $user User that creates + * @param int<0,1> $notrigger 0=launch triggers after, 1=disable triggers + * @return int<-1,max> Return integer <0 if KO, Id of created object if OK */ public function createCommon(User $user, $notrigger = 0) { @@ -10080,6 +10080,7 @@ abstract class CommonObject if (array_key_exists('date_creation', $fieldvalues) && empty($fieldvalues['date_creation'])) { $fieldvalues['date_creation'] = $this->db->idate($now); } + // For backward compatibility, if a property ->fk_user_creat exists and not filled. if (array_key_exists('fk_user_creat', $fieldvalues) && !($fieldvalues['fk_user_creat'] > 0)) { $fieldvalues['fk_user_creat'] = $user->id; $this->fk_user_creat = $user->id; diff --git a/htdocs/projet/class/task.class.php b/htdocs/projet/class/task.class.php index 83b9b1e55a8..9a32ee96f96 100644 --- a/htdocs/projet/class/task.class.php +++ b/htdocs/projet/class/task.class.php @@ -139,7 +139,7 @@ class Task extends CommonObjectLine public $timespent_old_duration; public $timespent_date; public $timespent_datehour; // More accurate start date (same than timespent_date but includes hours, minutes and seconds) - public $timespent_withhour; // 1 = we entered also start hours for timesheet line + public $timespent_withhour; // 0 or 1 = we have entered also start hours for timesheet line public $timespent_fk_user; public $timespent_thm; public $timespent_note; @@ -1467,10 +1467,9 @@ class Task extends CommonObjectLine $timespent->fk_user = $this->timespent_fk_user; $timespent->fk_product = $this->timespent_fk_product; $timespent->note = $this->timespent_note; - $timespent->datec = $this->db->idate($now); + $timespent->datec = $now; $result = $timespent->create($user); - if ($result > 0) { $ret = $result; $this->timespent_id = $result; diff --git a/htdocs/projet/tasks/time.php b/htdocs/projet/tasks/time.php index 4f7541724fc..00c4a8e8053 100644 --- a/htdocs/projet/tasks/time.php +++ b/htdocs/projet/tasks/time.php @@ -175,7 +175,6 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x' $search_year = ''; $search_note = ''; $search_duration = ''; - $search_value = ''; $search_date_startday = ''; $search_date_startmonth = ''; $search_date_startyear = ''; @@ -249,6 +248,7 @@ if ($action == 'addtimespent' && $user->hasRight('projet', 'time')) { $object->timespent_withhour = 1; } else { $object->timespent_date = dol_mktime(12, 0, 0, GETPOSTINT("timemonth"), GETPOSTINT("timeday"), GETPOSTINT("timeyear")); + $object->timespent_withhour = 0; } $object->timespent_fk_user = GETPOSTINT("userid"); $object->timespent_fk_product = GETPOSTINT("fk_product"); @@ -299,6 +299,7 @@ if (($action == 'updateline' || $action == 'updatesplitline') && !$cancel && $us $object->timespent_withhour = 1; } else { $object->timespent_date = dol_mktime(12, 0, 0, GETPOST("timelinemonth"), GETPOST("timelineday"), GETPOST("timelineyear")); + $object->timespent_withhour = 0; } $object->timespent_fk_user = GETPOSTINT("userid_line"); $object->timespent_fk_product = GETPOSTINT("fk_product"); @@ -328,6 +329,7 @@ if (($action == 'updateline' || $action == 'updatesplitline') && !$cancel && $us $object->timespent_withhour = 1; } else { $object->timespent_date = dol_mktime(12, 0, 0, GETPOSTINT("timelinemonth"), GETPOSTINT("timelineday"), GETPOSTINT("timelineyear")); + $object->timespent_withhour = 0; } $object->timespent_fk_user = GETPOSTINT("userid_line"); $object->timespent_fk_product = GETPOSTINT("fk_product"); From 7a23905a1d4911acb5e161487f785376bba33392 Mon Sep 17 00:00:00 2001 From: "Laurent Destailleur (aka Eldy)" Date: Sun, 16 Feb 2025 15:03:30 +0100 Subject: [PATCH 09/26] More generic example --- build/makepack-dolibarr.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/makepack-dolibarr.pl b/build/makepack-dolibarr.pl index 020caba8398..2e4652f4970 100755 --- a/build/makepack-dolibarr.pl +++ b/build/makepack-dolibarr.pl @@ -85,8 +85,8 @@ if (! $ENV{"DESTIBETARC"} || ! $ENV{"DESTISTABLE"}) print "set DESTIBETARC=c:/tmp\n"; print "set DESTISTABLE=c:/tmp\n"; print "\n"; - print "Example: DESTIBETARC='/media/HDDATA1_LD/Mes Sites/Web/Dolibarr/dolibarr.org/files/lastbuild'\n"; - print "Example: DESTISTABLE='/media/HDDATA1_LD/Mes Sites/Web/Dolibarr/dolibarr.org/files/stable'\n"; + print "Example: DESTIBETARC='/media/HDDATA1_LD/Mes Archives/Doli/dolibarr/files/lastbuild'\n"; + print "Example: DESTISTABLE='/media/HDDATA1_LD/Mes Archives/Doli/dolibarr/files/stable'\n"; sleep 2; exit 1; } From eead065990752a744746ea77421f3c256b2bbfd4 Mon Sep 17 00:00:00 2001 From: ldestailleur Date: Sun, 16 Feb 2025 15:10:34 +0100 Subject: [PATCH 10/26] More generic example --- build/makepack-dolibarr.pl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build/makepack-dolibarr.pl b/build/makepack-dolibarr.pl index 2e4652f4970..d31e27a223a 100755 --- a/build/makepack-dolibarr.pl +++ b/build/makepack-dolibarr.pl @@ -85,8 +85,9 @@ if (! $ENV{"DESTIBETARC"} || ! $ENV{"DESTISTABLE"}) print "set DESTIBETARC=c:/tmp\n"; print "set DESTISTABLE=c:/tmp\n"; print "\n"; - print "Example: DESTIBETARC='/media/HDDATA1_LD/Mes Archives/Doli/dolibarr/files/lastbuild'\n"; - print "Example: DESTISTABLE='/media/HDDATA1_LD/Mes Archives/Doli/dolibarr/files/stable'\n"; + print "Example in .bashrc:\n"; + print "export DESTIBETARC='/mnt/HDDATA1_LD/Mes Archives/Doli/dolibarr/lastbuild'\n"; + print "export DESTISTABLE='/mnt/HDDATA1_LD/Mes Archives/Doli/dolibarr/stable'\n"; sleep 2; exit 1; } From 7b9dddcfc95ae4032f99f355c1b38f4b04e52bc8 Mon Sep 17 00:00:00 2001 From: ldestailleur Date: Sun, 16 Feb 2025 15:17:06 +0100 Subject: [PATCH 11/26] Prepare 20.0.4 --- ChangeLog | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/ChangeLog b/ChangeLog index b6d139108ee..823aa8cb42f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,109 @@ English Dolibarr ChangeLog -------------------------------------------------------------- +***** ChangeLog for 20.0.4 compared to 20.0.2 ***** +FIX: $this->origin_object can not be instance of CommandeFournisseur if it is already an instanceof CommonObject +FIX: 17.0 API endpoints "PUT": prevent overwriting all extrafields if only some are supplied in the request cf. PR #29237 +FIX: 17.0 - collisions in cache for dol_getIdFromCode +FIX: #18713 +FIX: 20.0 - PHP8 fatal when creating a reception unless corresponding PDF model is enabled +FIX: #21294 Stock import sql query +FIX: #26250 fatal error on kit +FIX: #28702 +FIX: #29624 - substitution of __DATE_DELIVERY__ +FIX: #32113 +FIX: #32186 +FIX: #32339 Delete a loan settlement is partial +FIX: #32387 +FIX: #32477 Loan - Insurance amount need decimals +FIX: #32611 +FIX: #32736 + avoid php warning +FIX: #32743 +FIX: #32765 JS Error: Uncaught TypeError +FIX: #32801 VAT type is inverted in form VAT selector +FIX: #32840 +FIX: #32843 +FIX: #32880 - Tags are using a special rendering. +FIX: add other fields +FIX: autofill price with multicurrency on supplier doc +FIX: avoid phan error +FIX: avoid php8 warnings +FIX: avoid warning with the new Dolistore website +FIX: backport from develop to avoid php warning +FIX: Bad calculation of the theoretical stock. Did not take into account +FIX: bad dispatched quantities for batches on shipment card +FIX: Brian is in the kitchen +FIX: broken feature, compatibility with "Default search filters" +FIX: broken feature with check $pa_ht_isemptystring +FIX: Bug on select user on time.php (all project list) +FIX: can not delete files in task card +FIX: Check "$search_sale" only if it's an internal user +FIX: clean unique extrafields when create product combination +FIX: code not visible correctly into view of dictionary +FIX: compatibility between next_prev_filter and hook return +FIX: compatibility with multicompany +FIX: Complete path was started in #17243 for pdf_cannelle +FIX: Continue for eagle_proforma +FIX: country id is not saved when we provide country_code only +FIX: #CVE-2024-34051 +FIX: delete supplier order when at least one line linked to customer order line +FIX: display error when loan can't be deleted +FIX: display full tree on shipment card when a kit contains a same component in other sub-kit +FIX: DROP INDEX IF EXISTS is not possible ! +FIX: extrafields lost during creation from rec invoice +FIX: FEC import +FIX: Fiscal year - missing translation on status +FIX: Fix return value of hook sendMail when hook return -1 who must be return false in sendfile() function +FIX: GETPOST('private_message') +FIX: glob is better for search files with wildcard + avoid warning +FIX: if $force_entity = 0 ($force_entity != 'default') = false +FIX: Loan - Insurance amount need decimals +FIX: Many status on invoice linked object block +FIX: merge problem +FIX: missing company name if dontaion is linked to third party +FIX: missing default values if $objsrc or $soc fields are empty +FIX: missing edit extrafields inline for member card +FIX: missing quick edit for extrafields +FIX: more bugs and warnings +FIX: Multilangs : PDF lines description +FIX: Must not have both thirdparty and member. +FIX: ODT substitution when many HTML tags in string +FIX: on the road again +FIX: pdf_cannelle (supplier_invoice) add background - Complete #17243 +FIX: Prices didn't update when clone a propale with update prices +FIX: product variants copy: also copy multiprice variations +FIX: refactorize (maybe broken feature for not received completely) +FIX: remove debug trace +FIX: remove socid when cloning a project without third parties +FIX: removes traces of << Date: Sun, 16 Feb 2025 15:25:18 +0100 Subject: [PATCH 12/26] Add missing dpendency to po2debconf --- build/makepack-dolibarr.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/makepack-dolibarr.pl b/build/makepack-dolibarr.pl index d31e27a223a..83e1c693d1e 100755 --- a/build/makepack-dolibarr.pl +++ b/build/makepack-dolibarr.pl @@ -36,7 +36,7 @@ $PUBLISHBETARC="dolibarr\@vmprod1.dolibarr.org:/home/dolibarr/asso.dolibarr.org/ "RPM_FEDORA"=>"rpmbuild", "RPM_MANDRIVA"=>"rpmbuild", "RPM_OPENSUSE"=>"rpmbuild", -"DEB"=>"dpkg", +"DEB"=>"dpkg,po2debconf", "FLATPACK"=>"flatpack", "EXEDOLIWAMP"=>"ISCC.exe", "SNAPSHOT"=>"tar" From 030357e87be3d076eefea2a8f9999bc0afa0ee47 Mon Sep 17 00:00:00 2001 From: ldestailleur Date: Sun, 16 Feb 2025 15:29:01 +0100 Subject: [PATCH 13/26] Fix missing dependency --- build/makepack-dolibarr.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/makepack-dolibarr.pl b/build/makepack-dolibarr.pl index 83e1c693d1e..c15e79b5947 100755 --- a/build/makepack-dolibarr.pl +++ b/build/makepack-dolibarr.pl @@ -36,7 +36,7 @@ $PUBLISHBETARC="dolibarr\@vmprod1.dolibarr.org:/home/dolibarr/asso.dolibarr.org/ "RPM_FEDORA"=>"rpmbuild", "RPM_MANDRIVA"=>"rpmbuild", "RPM_OPENSUSE"=>"rpmbuild", -"DEB"=>"dpkg,po2debconf", +"DEB"=>"dpkg,po2debconf", # need also debhelper "FLATPACK"=>"flatpack", "EXEDOLIWAMP"=>"ISCC.exe", "SNAPSHOT"=>"tar" From 320dbe483e65aff7144606d68aa3429ee03b6d01 Mon Sep 17 00:00:00 2001 From: ldestailleur Date: Sun, 16 Feb 2025 19:51:53 +0100 Subject: [PATCH 14/26] Fix js syntax error. --- htdocs/ai/admin/custom_prompt.php | 19 ++---- htdocs/core/class/html.formmail.class.php | 71 ++++++++++++----------- 2 files changed, 42 insertions(+), 48 deletions(-) diff --git a/htdocs/ai/admin/custom_prompt.php b/htdocs/ai/admin/custom_prompt.php index c127674c744..8acc7721ba0 100644 --- a/htdocs/ai/admin/custom_prompt.php +++ b/htdocs/ai/admin/custom_prompt.php @@ -27,7 +27,7 @@ // Load Dolibarr environment require '../../main.inc.php'; require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php"; -require_once '../lib/ai.lib.php'; +require_once DOL_DOCUMENT_ROOT."/ai/lib/ai.lib.php"; /** * @var Conf $conf @@ -39,6 +39,8 @@ require_once '../lib/ai.lib.php'; $langs->loadLangs(array("admin", "website", "other")); +$arrayofaifeatures = getLitOfAIFeatures(); + // Parameters $action = GETPOST('action', 'aZ09'); $backtopage = GETPOST('backtopage', 'alpha'); @@ -81,18 +83,6 @@ $setupnotempty += count($formSetup->items); $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']); -// List of AI features -$arrayofaifeatures = array( - 'textgenerationemail' => array('label' => $langs->trans('TextGeneration').' ('.$langs->trans("EmailContent").')', 'picto'=>'', 'status'=>'development'), - 'textgenerationwebpage' => array('label' => $langs->trans('TextGeneration').' ('.$langs->trans("WebsitePage").')', 'picto'=>'', 'status'=>'development'), - 'textgeneration' => array('label' => $langs->trans('TextGeneration').' ('.$langs->trans("Other").')', 'picto'=>'', 'status'=>'notused'), - 'imagegeneration' => array('label' => 'ImageGeneration', 'picto'=>'', 'status'=>'notused'), - 'videogeneration' => array('label' => 'VideoGeneration', 'picto'=>'', 'status'=>'notused'), - 'audiogeneration' => array('label' => 'AudioGeneration', 'picto'=>'', 'status'=>'notused'), - 'transcription' => array('label' => 'Transcription', 'picto'=>'', 'status'=>'notused'), - 'translation' => array('label' => 'Translation', 'picto'=>'', 'status'=>'notused') -); - /* * Actions @@ -279,6 +269,7 @@ if ($action == 'edit' || $action == 'deleteproperty') { $out .= ''; $out .= ''; + $out .= ''; $out .= ''; $out .= ''; @@ -383,6 +374,8 @@ if ($action == 'edit' || $action == 'create' || $action == 'deleteproperty') { $formmail = new FormMail($db); $htmlname = $key; + $out .= '

'; + // Fill $out include DOL_DOCUMENT_ROOT.'/core/tpl/formlayoutai.tpl.php'; diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index 1fdd72ab371..6eac18f33e0 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -1589,49 +1589,50 @@ class FormMail extends Form CKEDITOR.instances.".$htmlContent.".setReadOnly(1); } - $.ajax({ - url: '". DOL_URL_ROOT."/ai/ajax/generate_content.php?token=".currentToken()."', - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({ - 'format': '".dol_escape_js($format)."', /* the format for output */ - 'function': '".dol_escape_js($function)."', /* the AI feature to call */ - 'instructions': instructions, /* the prompt string */ - }), - success: function(response) { - console.log('Add response into field \'#".$htmlContent."\': '+response); + $.ajax({ + url: '". DOL_URL_ROOT."/ai/ajax/generate_content.php?token=".currentToken()."', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ + 'format': '".dol_escape_js($format)."', /* the format for output */ + 'function': '".dol_escape_js($function)."', /* the AI feature to call */ + 'instructions': instructions, /* the prompt string */ + }), + success: function(response) { + console.log('Add response into field \'#".$htmlContent."\': '+response); - jQuery('#".$htmlContent."').val(response); // If #htmlcontent is a input name or textarea - jQuery('#".$htmlContent."').html(response); // If #htmlContent is a div - //jQuery('#".$htmlContent."preview').val(response); + jQuery('#".$htmlContent."').val(response); // If #htmlcontent is a input name or textarea + jQuery('#".$htmlContent."').html(response); // If #htmlContent is a div + //jQuery('#".$htmlContent."preview').val(response); - if (CKEDITOR.instances) { - var editorInstance = CKEDITOR.instances.".$htmlContent."; - if (editorInstance) { - editorInstance.setReadOnly(0); - editorInstance.setData(response); + if (CKEDITOR.instances) { + var editorInstance = CKEDITOR.instances.".$htmlContent."; + if (editorInstance) { + editorInstance.setReadOnly(0); + editorInstance.setData(response); + } + //var editorInstancepreview = CKEDITOR.instances.".$htmlContent."preview; + //if (editorInstancepreview) { + // editorInstancepreview.setData(response); + //} } - //var editorInstancepreview = CKEDITOR.instances.".$htmlContent."preview; - //if (editorInstancepreview) { - // editorInstancepreview.setData(response); - //} - } - // remove readonly - $('#ai_instructions".$htmlContent."').val(''); + // remove readonly + $('#ai_instructions".$htmlContent."').val(''); - apicallfinished = 1; - if (timeoutfinished) { + apicallfinished = 1; + if (timeoutfinished) { + $('#ai_status_message".$htmlContent."').hide(); + } + }, + error: function(xhr, status, error) { + alert(error); + console.error('error ajax', status, error); $('#ai_status_message".$htmlContent."').hide(); } - }, - error: function(xhr, status, error) { - alert(error); - console.error('error ajax', status, error); - $('#ai_status_message".$htmlContent."').hide(); - } - }); + }); + } }); }); From 31c2aa5c6380bb27b69014ab545034a808dd6573 Mon Sep 17 00:00:00 2001 From: ldestailleur Date: Sun, 16 Feb 2025 19:54:58 +0100 Subject: [PATCH 15/26] Fix js syntax error --- htdocs/core/class/html.formmail.class.php | 71 ++++++++++++----------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index 21f064ba8ca..114c01ec5e1 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -1579,49 +1579,50 @@ class FormMail extends Form CKEDITOR.instances.".$htmlContent.".setReadOnly(1); } - $.ajax({ - url: '". DOL_URL_ROOT."/ai/ajax/generate_content.php?token=".currentToken()."', - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({ - 'format': '".dol_escape_js($format)."', /* the format for output */ - 'function': '".dol_escape_js($function)."', /* the AI feature to call */ - 'instructions': instructions, /* the prompt string */ - }), - success: function(response) { - console.log('Add response into field \'#".$htmlContent."\': '+response); + $.ajax({ + url: '". DOL_URL_ROOT."/ai/ajax/generate_content.php?token=".currentToken()."', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ + 'format': '".dol_escape_js($format)."', /* the format for output */ + 'function': '".dol_escape_js($function)."', /* the AI feature to call */ + 'instructions': instructions, /* the prompt string */ + }), + success: function(response) { + console.log('Add response into field \'#".$htmlContent."\': '+response); - jQuery('#".$htmlContent."').val(response); // If #htmlcontent is a input name or textarea - jQuery('#".$htmlContent."').html(response); // If #htmlContent is a div - //jQuery('#".$htmlContent."preview').val(response); + jQuery('#".$htmlContent."').val(response); // If #htmlcontent is a input name or textarea + jQuery('#".$htmlContent."').html(response); // If #htmlContent is a div + //jQuery('#".$htmlContent."preview').val(response); - if (CKEDITOR.instances) { - var editorInstance = CKEDITOR.instances.".$htmlContent."; - if (editorInstance) { - editorInstance.setReadOnly(0); - editorInstance.setData(response); + if (CKEDITOR.instances) { + var editorInstance = CKEDITOR.instances.".$htmlContent."; + if (editorInstance) { + editorInstance.setReadOnly(0); + editorInstance.setData(response); + } + //var editorInstancepreview = CKEDITOR.instances.".$htmlContent."preview; + //if (editorInstancepreview) { + // editorInstancepreview.setData(response); + //} } - //var editorInstancepreview = CKEDITOR.instances.".$htmlContent."preview; - //if (editorInstancepreview) { - // editorInstancepreview.setData(response); - //} - } - // remove readonly - $('#ai_instructions".$htmlContent."').val(''); + // remove readonly + $('#ai_instructions".$htmlContent."').val(''); - apicallfinished = 1; - if (timeoutfinished) { + apicallfinished = 1; + if (timeoutfinished) { + $('#ai_status_message".$htmlContent."').hide(); + } + }, + error: function(xhr, status, error) { + alert(error); + console.error('error ajax', status, error); $('#ai_status_message".$htmlContent."').hide(); } - }, - error: function(xhr, status, error) { - alert(error); - console.error('error ajax', status, error); - $('#ai_status_message".$htmlContent."').hide(); - } - }); + }); + } }); }); From bc1dbae6b034a8752feadfd8072931dfc52bbfc4 Mon Sep 17 00:00:00 2001 From: ldestailleur Date: Sun, 16 Feb 2025 19:56:03 +0100 Subject: [PATCH 16/26] Enhance test of AI --- htdocs/ai/admin/setup.php | 62 +++++++++++++++++++++++++--- htdocs/ai/lib/ai.lib.php | 24 +++++++++++ htdocs/core/tpl/formlayoutai.tpl.php | 33 +++++++-------- 3 files changed, 95 insertions(+), 24 deletions(-) diff --git a/htdocs/ai/admin/setup.php b/htdocs/ai/admin/setup.php index 8df3b7bd9e2..cd3eec1c01b 100644 --- a/htdocs/ai/admin/setup.php +++ b/htdocs/ai/admin/setup.php @@ -28,7 +28,8 @@ // Load Dolibarr environment require '../../main.inc.php'; require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php"; -require_once '../lib/ai.lib.php'; +require_once DOL_DOCUMENT_ROOT."/core/class/doleditor.class.php"; +require_once DOL_DOCUMENT_ROOT."/ai/lib/ai.lib.php"; /** * @var Conf $conf @@ -38,7 +39,9 @@ require_once '../lib/ai.lib.php'; * @var User $user */ -$langs->loadLangs(array("admin")); +$langs->loadLangs(array("admin", "website", "other")); + +$arrayofaifeatures = getLitOfAIFeatures(); // Parameters $action = GETPOST('action', 'aZ09'); @@ -49,10 +52,7 @@ if (empty($action)) { $action = 'edit'; } -$value = GETPOST('value', 'alpha'); -$label = GETPOST('label', 'alpha'); -$scandir = GETPOST('scan_dir', 'alpha'); -$type = 'myobject'; +$content = GETPOST('content'); $error = 0; $setupnotempty = 0; @@ -163,5 +163,55 @@ if (empty($setupnotempty)) { // Page end print dol_get_fiche_end(); + +// Section to test +print '
'; +print ''; +print ''; +print ''; + +$functioncode = GETPOST('functioncode'); +$out = ''; + +if ($functioncode) { + $labeloffeature = empty($arrayofaifeatures[GETPOST('functioncode')]['label']) ? 'Undefined' : $arrayofaifeatures[GETPOST('functioncode')]['label']; + + $out .= $langs->trans("Test").' '.$labeloffeature.'...

'; + + if (GETPOST('functioncode') == 'textgenerationemail') { + $showlinktoai = 1; + $showlinktolayout = 0; + + + $neweditor = new DolEditor('aicontenttotest', $content); + $out .= $neweditor->Create(1); + + $out .= $form->buttonsSaveCancel("Test"); + } else { + $out .= $langs->trans("FeatureNotYetAvailable").'

'; + $functioncode = ''; + } +} + +if (!$functioncode) { + // Combo list of AI features + $out .= ''; + $out .= ajax_combobox("functioncode"); + + $out .= ''; +} +print $out; + +print '
'; + llxFooter(); $db->close(); diff --git a/htdocs/ai/lib/ai.lib.php b/htdocs/ai/lib/ai.lib.php index fe8c6232db0..25aa3482626 100644 --- a/htdocs/ai/lib/ai.lib.php +++ b/htdocs/ai/lib/ai.lib.php @@ -23,6 +23,30 @@ * \brief Library files with common functions for Ai */ + +/** + * Prepare admin pages header + * + * @return array>}> + */ +function getLitOfAIFeatures() +{ + global $langs; + + $arrayofaifeatures = array( + 'textgenerationemail' => array('label' => $langs->trans('TextGeneration').' ('.$langs->trans("EmailContent").')', 'picto'=>'', 'status'=>'dolibarr'), + 'textgenerationwebpage' => array('label' => $langs->trans('TextGeneration').' ('.$langs->trans("WebsitePage").')', 'picto'=>'', 'status'=>'dolibarr'), + 'textgeneration' => array('label' => $langs->trans('TextGeneration').' ('.$langs->trans("Other").')', 'picto'=>'', 'status'=>'notused'), + 'imagegeneration' => array('label' => 'ImageGeneration', 'picto'=>'', 'status'=>'notused'), + 'videogeneration' => array('label' => 'VideoGeneration', 'picto'=>'', 'status'=>'notused'), + 'audiogeneration' => array('label' => 'AudioGeneration', 'picto'=>'', 'status'=>'notused'), + 'transcription' => array('label' => 'Transcription', 'picto'=>'', 'status'=>'notused'), + 'translation' => array('label' => 'Translation', 'picto'=>'', 'status'=>'notused') + ); + + return $arrayofaifeatures; +} + /** * Prepare admin pages header * diff --git a/htdocs/core/tpl/formlayoutai.tpl.php b/htdocs/core/tpl/formlayoutai.tpl.php index 944f6d5722b..bff80825dc9 100644 --- a/htdocs/core/tpl/formlayoutai.tpl.php +++ b/htdocs/core/tpl/formlayoutai.tpl.php @@ -15,26 +15,22 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * - * Need to have the following variables defined: - * $conf - * $formmail - * $formwebsite (optional) - * $showlinktolayout='emailing', 'email', 'websitepage', ... - * $showlinktolayoutlabel='...' - * $showlinktoai ('' or 'textgeneration', 'textgenerationemail', 'textgenerationwebpage', ...) - * $showlinktoailabel='...' - * $htmlname */ -/** + + /** * @var Conf $conf - * @var ?FormMail $formmail - * @var ?FormWebsite $formwebsite - * @var string $htmlname - * @var string $showlinktolayout - * @var string $showlinktolayoutlabel + * @var ?FormMail $formmail + * @var ?FormWebsite $formwebsite + * @var string $htmlname + * @var string $showlinktolayout 'emailing', 'email', 'websitepage', ... + * @var string $showlinktolayoutlabel '...' + * @var string $showlinktoai '' or 'textgeneration', 'textgenerationemail', 'textgenerationwebpage', ... + * @var string $showlinktoailabel '...' + * @var string $htmlname + * @var ?string $out */ -// Protection to avoid direct call of template + +//Protection to avoid direct call of template if (empty($conf) || !is_object($conf)) { print "Error, template page can't be called as URL"; exit(1); @@ -59,9 +55,10 @@ if (empty($htmlname)) { @phan-var-force ?string $out '; -if (!isset($out)) { +if (!isset($out)) { // Init to empty string if not defined $out = ''; } + // Add link to add layout if ($showlinktolayout) { $out .= ''; From 47d59c79382c400234fff6543e5462485330b4c5 Mon Sep 17 00:00:00 2001 From: ldestailleur Date: Sun, 16 Feb 2025 22:45:14 +0100 Subject: [PATCH 17/26] NEW Add a test mode for AI setup --- htdocs/ai/admin/custom_prompt.php | 3 +- htdocs/ai/admin/setup.php | 17 ++++--- htdocs/ai/ajax/generate_content.php | 8 +-- htdocs/ai/class/ai.class.php | 62 +++++++++++++++++++---- htdocs/core/class/CMailFile.class.php | 34 +++++++------ htdocs/core/class/html.formmail.class.php | 7 +-- htdocs/core/tpl/formlayoutai.tpl.php | 14 ++--- htdocs/langs/en_US/admin.lang | 1 + 8 files changed, 99 insertions(+), 47 deletions(-) diff --git a/htdocs/ai/admin/custom_prompt.php b/htdocs/ai/admin/custom_prompt.php index 8acc7721ba0..b562a322008 100644 --- a/htdocs/ai/admin/custom_prompt.php +++ b/htdocs/ai/admin/custom_prompt.php @@ -305,6 +305,7 @@ if ($action == 'edit' || $action == 'deleteproperty') { $out .= $form->buttonsSaveCancel("Add", ""); $out .= ''; + $out .= '


'; print $out; @@ -365,7 +366,7 @@ if ($action == 'edit' || $action == 'create' || $action == 'deleteproperty') { $out .= ''; $out .= ''; $out .= ''; - $out .= ''; + $out .= ''; $out .= '   '; include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php'; diff --git a/htdocs/ai/admin/setup.php b/htdocs/ai/admin/setup.php index cd3eec1c01b..cdd54cb957f 100644 --- a/htdocs/ai/admin/setup.php +++ b/htdocs/ai/admin/setup.php @@ -176,17 +176,22 @@ $out = ''; if ($functioncode) { $labeloffeature = empty($arrayofaifeatures[GETPOST('functioncode')]['label']) ? 'Undefined' : $arrayofaifeatures[GETPOST('functioncode')]['label']; - $out .= $langs->trans("Test").' '.$labeloffeature.'...

'; + //$out .= $langs->trans("Test").' '.$labeloffeature.'...

'; if (GETPOST('functioncode') == 'textgenerationemail') { - $showlinktoai = 1; + $key = 'textgenerationemail'; // The HTML ID of field to fill + + include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php'; + $showlinktoai = $key; // 'textgeneration', 'imagegeneration', ... + $showlinktoailabel = $langs->trans("Test").' '.$labeloffeature; $showlinktolayout = 0; + $formmail = new FormMail($db); + $htmlname = $key; + // Fill $out + include DOL_DOCUMENT_ROOT.'/core/tpl/formlayoutai.tpl.php'; - $neweditor = new DolEditor('aicontenttotest', $content); - $out .= $neweditor->Create(1); - - $out .= $form->buttonsSaveCancel("Test"); + $out .= '
'; } else { $out .= $langs->trans("FeatureNotYetAvailable").'

'; $functioncode = ''; diff --git a/htdocs/ai/ajax/generate_content.php b/htdocs/ai/ajax/generate_content.php index 0f1e1f76a92..0cd6a125970 100644 --- a/htdocs/ai/ajax/generate_content.php +++ b/htdocs/ai/ajax/generate_content.php @@ -81,15 +81,17 @@ $format = empty($jsonData['format']) ? '' : $jsonData['format']; $generatedContent = $ai->generateContent($instructions, 'auto', $function, $format); -if (is_array($generatedContent) && $generatedContent['error']) { +if (is_null($generatedContent) || (is_array($generatedContent) && $generatedContent['error'])) { // Output error if (!empty($generatedContent['code']) && $generatedContent['code'] == 429) { print "Quota or allowed period exceeded. Retry Later !"; - } elseif ($generatedContent['code'] >= 400) { + } elseif (!empty($generatedContent['code']) && $generatedContent['code'] >= 400) { print "Error : " . $generatedContent['message']; print '
'.$langs->trans('ErrorGoToModuleSetup').''; - } else { + } elseif (!empty($generatedContent['message'])) { print "Error returned by API call: " . $generatedContent['message']; + } else { + print "Error API returned no answer"; } } else { if ($function == 'textgenerationemail' || $function == 'textgenerationwebpage') { diff --git a/htdocs/ai/class/ai.class.php b/htdocs/ai/class/ai.class.php index 922df84dcbb..3f6a5d6eb78 100644 --- a/htdocs/ai/class/ai.class.php +++ b/htdocs/ai/class/ai.class.php @@ -80,6 +80,8 @@ class Ai */ public function generateContent($instructions, $model = 'auto', $function = 'textgeneration', $format = '') { + global $dolibarr_main_data_root; + if (empty($this->apiKey)) { return array('error' => true, 'message' => 'API key is not defined for the AI enabled service ('.$this->apiService.')'); } @@ -199,7 +201,7 @@ class Ai $postPrompt = $configurations[$function]['postPrompt']; } } - $fullInstructions = ($prePrompt ? $prePrompt.' ' : '').$instructions.($postPrompt ? '. '.$postPrompt : ''); + $fullInstructions = $instructions.($postPrompt ? (preg_match('/[\.\!\?]$/', $instructions) ? '' : '.').' '.$postPrompt : ''); // Set payload string /*{ @@ -224,18 +226,46 @@ class Ai "temperature": 0.7, "top_p": 0.95 }*/ - $payload = json_encode([ - 'messages' => [ - ['role' => 'user', 'content' => $fullInstructions] - ], - 'model' => $model, - //'stream' => false - ]); - $headers = ([ + $arrayforpayload = array( + 'messages' => array(array('role' => 'user', 'content' => $fullInstructions)), + 'model' => $model, + ); + + // Add a system message + $addDateTimeContext = 0; + if ($addDateTimeContext) { + $prePrompt = ($prePrompt ? $prePrompt.(preg_match('/[\.\!\?]$/', $prePrompt) ? '' : '.').' ' : '').'Today we are '.dol_print_date(dol_now(), 'dayhourtext'); + } + $arrayforpayload['messages'][] = array('role' => 'system', 'content' => $prePrompt); + + /* + $arrayforpayload['temperature'] = 0.7; + $arrayforpayload['max_tokens'] = -1; + $arrayforpayload['stream'] = false; + */ + + $payload = json_encode($arrayforpayload); + + $headers = array( 'Authorization: Bearer ' . $this->apiKey, 'Content-Type: application/json' - ]); + ); + + if (getDolGlobalString("AI_DEBUG")) { + if (@is_writable($dolibarr_main_data_root)) { // Avoid fatal error on fopen with open_basedir + $outputfile = $dolibarr_main_data_root."/dolibarr_ai.log"; + $fp = fopen($outputfile, "w"); // overwrite + + if ($fp) { + fwrite($fp, var_export($headers, true)."\n"); + fwrite($fp, var_export($payload, true)."\n"); + + fclose($fp); + dolChmod($outputfile); + } + } + } $localurl = 2; // Accept both local and external endpoints $response = getURLContent($this->apiEndpoint, 'POST', $payload, 1, $headers, array('http', 'https'), $localurl); @@ -248,7 +278,17 @@ class Ai } if (getDolGlobalString("AI_DEBUG")) { - dol_syslog("response content = ".var_export($response['content'], true)); + if (@is_writable($dolibarr_main_data_root)) { // Avoid fatal error on fopen with open_basedir + $outputfile = $dolibarr_main_data_root."/dolibarr_ai.log"; + $fp = fopen($outputfile, "a"); + + if ($fp) { + fwrite($fp, var_export((empty($response['content']) ? 'No content result' : $response['content']), true)."\n"); + + fclose($fp); + dolChmod($outputfile); + } + } } // Decode JSON response diff --git a/htdocs/core/class/CMailFile.class.php b/htdocs/core/class/CMailFile.class.php index 81efbf470a7..ce57b90a4c7 100644 --- a/htdocs/core/class/CMailFile.class.php +++ b/htdocs/core/class/CMailFile.class.php @@ -1483,24 +1483,26 @@ class CMailFile $outputfile = $dolibarr_main_data_root."/dolibarr_mail.log"; $fp = fopen($outputfile, "w"); // overwrite - if ($this->sendmode == 'mail') { - fwrite($fp, $this->headers); - fwrite($fp, $this->eol); // This eol is added by the mail function, so we add it in log - fwrite($fp, $this->message); - } elseif ($this->sendmode == 'smtps') { - fwrite($fp, $this->smtps->log); // this->smtps->log is filled only if MAIN_MAIL_DEBUG was set to on - } elseif ($this->sendmode == 'swiftmailer') { - fwrite($fp, "smtpheader=\n".$this->message->getHeaders()->toString()."\n"); - fwrite($fp, $this->logger->dump()); // this->logger is filled only if MAIN_MAIL_DEBUG was set to on - } + if ($fp) { + if ($this->sendmode == 'mail') { + fwrite($fp, $this->headers); + fwrite($fp, $this->eol); // This eol is added by the mail function, so we add it in log + fwrite($fp, $this->message); + } elseif ($this->sendmode == 'smtps') { + fwrite($fp, $this->smtps->log); // this->smtps->log is filled only if MAIN_MAIL_DEBUG was set to on + } elseif ($this->sendmode == 'swiftmailer') { + fwrite($fp, "smtpheader=\n".$this->message->getHeaders()->toString()."\n"); + fwrite($fp, $this->logger->dump()); // this->logger is filled only if MAIN_MAIL_DEBUG was set to on + } - fclose($fp); - dolChmod($outputfile); + fclose($fp); + dolChmod($outputfile); - // Move dolibarr_mail.log into a dolibarr_mail.log.v123456789 - if (getDolGlobalInt('MAIN_MAIL_DEBUG_LOG_WITH_DATE')) { - require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; - archiveOrBackupFile($outputfile, getDolGlobalInt('MAIN_MAIL_DEBUG_LOG_WITH_DATE')); + // Move dolibarr_mail.log into a dolibarr_mail.log.v123456789 + if (getDolGlobalInt('MAIN_MAIL_DEBUG_LOG_WITH_DATE')) { + require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; + archiveOrBackupFile($outputfile, getDolGlobalInt('MAIN_MAIL_DEBUG_LOG_WITH_DATE')); + } } } } diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index 6eac18f33e0..c3904969a64 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -1493,6 +1493,7 @@ class FormMail extends Form * @param string $format Format for output ('', 'html', ...) * @param string $htmlContent HTML name of WYSIWYG field * @return string HTML code to ask AI instruction and autofill result + * TODO Move into a file html.formai.class.php */ public function getSectionForAIPrompt($function = 'textgeneration', $format = '', $htmlContent = 'message') { @@ -1502,7 +1503,7 @@ class FormMail extends Form $htmlContent = preg_replace('/[^a-z0-9_]/', '', $htmlContent); - $out = '