diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad7b5ef..8859b81 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
+## [Unreleased]
+
+### Fixed
+
+- Fixed approval comments rendering as raw HTML in PDF
+- Fixed PDF crash when exporting Problems with linked items lacking serial/inventory fields
+- Fixed Change and Problem description exported as a single unstructured text block
+- Fixed Change analysis and plan fields rendering as raw HTML in PDF
+- Fixed rich text content rendered as visible HTML tags and unformatted text in PDF exports
+- Fixed Change approval comments rendering as visible HTML entities in PDF exports
+- Fixed Changes linked items showing phantom empty entries for users with broad entity access
+
## [4.1.3] - 2026-06-24
### Fixed
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ef1bed5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1 @@
+include ../../PluginsMakefile.mk
diff --git a/inc/change.class.php b/inc/change.class.php
index f66e41a..1a6d8ac 100644
--- a/inc/change.class.php
+++ b/inc/change.class.php
@@ -298,7 +298,7 @@ public static function pdfMain(PluginPdfSimplePDF $pdf, Change $job)
$pdf->displayText(
'' . sprintf(__s('%1$s: %2$s') . '', __s('Description'), ''),
- Toolbox::stripTags($job->fields['content']),
+ $job->fields['content'],
1,
);
@@ -310,19 +310,15 @@ public static function pdfAnalysis(PluginPdfSimplePDF $pdf, Change $job)
$pdf->setColumnsSize(100);
$pdf->displayTitle('' . __s('Analysis') . '');
- $pdf->setColumnsSize(10, 90);
-
- $pdf->displayText(sprintf(
- __s('%1$s: %2$s'),
+ $pdf->displayText(
'' . __s('Impacts') . '',
$job->fields['impactcontent'],
- ));
+ );
- $pdf->displayText(sprintf(
- __s('%1$s: %2$s'),
+ $pdf->displayText(
'' . __s('Control list') . '',
$job->fields['controlistcontent'],
- ));
+ );
}
public static function pdfPlan(PluginPdfSimplePDF $pdf, Change $job)
@@ -330,25 +326,20 @@ public static function pdfPlan(PluginPdfSimplePDF $pdf, Change $job)
$pdf->setColumnsSize(100);
$pdf->displayTitle('' . __s('Plans') . '');
- $pdf->setColumnsSize(10, 90);
-
- $pdf->displayText(sprintf(
- __s('%1$s: %2$s'),
+ $pdf->displayText(
'' . __s('Deployment plan') . '',
$job->fields['rolloutplancontent'],
- ));
+ );
- $pdf->displayText(sprintf(
- __s('%1$s: %2$s'),
+ $pdf->displayText(
'' . __s('Backup plan') . '',
$job->fields['backoutplancontent'],
- ));
+ );
- $pdf->displayText(sprintf(
- __s('%1$s: %2$s'),
+ $pdf->displayText(
'' . __s('Checklist') . '',
$job->fields['checklistcontent'],
- ));
+ );
}
public static function pdfStat(PluginPdfSimplePDF $pdf, Change $job)
diff --git a/inc/change_item.class.php b/inc/change_item.class.php
index e6790b7..dca4a69 100644
--- a/inc/change_item.class.php
+++ b/inc/change_item.class.php
@@ -91,8 +91,8 @@ public static function pdfForChange(PluginPdfSimplePDF $pdf, Change $change)
$query = ['FIELDS' => [$itemtable . '.*', 'glpi_changes_items.id AS IDD',
'glpi_entities.id AS entity'],
- 'FROM' => 'glpi_changes_items',
- 'LEFT JOIN' => [$itemtable => ['FKEY' => [$itemtable => 'id',
+ 'FROM' => 'glpi_changes_items',
+ 'INNER JOIN' => [$itemtable => ['FKEY' => [$itemtable => 'id',
'glpi_changes_items' => 'items_id'],
'glpi_changes_items.itemtype' => $itemtype,
'glpi_changes_items.changes_id' => $instID]]];
diff --git a/inc/changevalidation.class.php b/inc/changevalidation.class.php
index 6b20670..2a8e713 100644
--- a/inc/changevalidation.class.php
+++ b/inc/changevalidation.class.php
@@ -92,10 +92,10 @@ public static function pdfForChange(PluginPdfSimplePDF $pdf, Change $change)
TicketValidation::getStatus($row['status']),
Html::convDateTime($row['submission_date']),
$dbu->getUserName($row['users_id']),
- trim($row['comment_submission']),
+ trim(html_entity_decode($row['comment_submission'] ?? '', ENT_QUOTES, 'UTF-8')),
Html::convDateTime($row['validation_date']),
$dbu->getUserName($row['users_id_validate']),
- trim($row['comment_validation']),
+ trim(html_entity_decode($row['comment_validation'] ?? '', ENT_QUOTES, 'UTF-8')),
);
}
}
diff --git a/inc/common.class.php b/inc/common.class.php
index a883614..528f1ab 100644
--- a/inc/common.class.php
+++ b/inc/common.class.php
@@ -59,6 +59,7 @@ public function addStandardTab($itemtype, array &$ong, array $options)
$withtemplate = $options['withtemplate'];
}
+ // @phpstan-ignore-next-line function.impossibleType - is_numeric() kept for parity with CommonGLPI::addStandardTab(), whose $itemtype PHPDoc type makes it always false
if (!is_numeric($itemtype) && ($obj = $dbu->getItemForItemtype($itemtype)) && (method_exists($itemtype, 'displayTabContentForPDF'))) {
$titles = $obj->getTabNameForItem($this->obj, $withtemplate);
if (!is_array($titles)) {
diff --git a/inc/item_problem.class.php b/inc/item_problem.class.php
index 0a1bdc8..0ceb1fb 100644
--- a/inc/item_problem.class.php
+++ b/inc/item_problem.class.php
@@ -182,8 +182,8 @@ public static function pdfForProblem(PluginPdfSimplePDF $pdf, Problem $problem)
Toolbox::stripTags(sprintf(__s('%1$s: %2$s'), $typename, $nb)),
Toolbox::stripTags($name),
Dropdown::getDropdownName('glpi_entities', $data['entity']),
- Toolbox::stripTags($data['serial']),
- Toolbox::stripTags($data['otherserial']),
+ Toolbox::stripTags($data['serial'] ?? ''),
+ Toolbox::stripTags($data['otherserial'] ?? ''),
$nb,
);
} else {
@@ -191,8 +191,8 @@ public static function pdfForProblem(PluginPdfSimplePDF $pdf, Problem $problem)
'',
Toolbox::stripTags($name),
Dropdown::getDropdownName('glpi_entities', $data['entity']),
- Toolbox::stripTags($data['serial']),
- Toolbox::stripTags($data['otherserial']),
+ Toolbox::stripTags($data['serial'] ?? ''),
+ Toolbox::stripTags($data['otherserial'] ?? ''),
$nb,
);
}
diff --git a/inc/problem.class.php b/inc/problem.class.php
index f2ce73b..2484950 100644
--- a/inc/problem.class.php
+++ b/inc/problem.class.php
@@ -298,7 +298,7 @@ public static function pdfMain(PluginPdfSimplePDF $pdf, Problem $job)
$pdf->displayText(
'' . sprintf(__s('%1$s: %2$s'), __s('Description') . '', ''),
- Toolbox::stripTags($job->fields['content']),
+ $job->fields['content'],
1,
);
diff --git a/inc/simplepdf.class.php b/inc/simplepdf.class.php
index 6b9d322..eb46c07 100644
--- a/inc/simplepdf.class.php
+++ b/inc/simplepdf.class.php
@@ -345,8 +345,10 @@ public function displayText($name, $content = '', $minline = 3, $maxline = 100)
$save = [$this->cols, $this->colsx, $this->colsw, $this->align];
$this->setColumnsSize(100);
- $text = $name . ' ' . $content;
- $content = RichText::getEnhancedHtml($text, ['text_maxsize' => 0]);
+
+ // Decode $content alone: mixing literal HTML from $name breaks isHtmlEncoded(), leaving GLPI entities (<) un-decoded and visible as text in TCPDF.
+ $decoded = RichText::getEnhancedHtml($content ?? '', ['text_maxsize' => 0]);
+ $content = preg_replace('/