[TikiWiki-commits] [Git][tikiwiki/tiki][27.x] [ENH] mpdf: Add debug output pref which saves the html sent to mPDF for 27.x

"Jonny Bradley \(@jonnybradley\) via TikiWiki-cvs" <[email protected]>
Newsgroups gmane.comp.cms.tiki.cvs
Message-ID <697b3c1a5d3b4_3b183c3835722@gitlab-sidekiq-low-urgency-cpu-bound-v2-d998fd55b-qwhd7.mail>

Jonny Bradley pushed to branch 27.x at Tiki Wiki CMS Groupware / Tiki


Commits:
71b5dfb6 by Jonny Bradley at 2026-01-29T10:46:40+00:00
[ENH] mpdf: Add debug output pref which saves the html sent to mPDF for 27.x
---
* Make tablesorter tables appear in mPDF output (and improve mpdf_debug_output)
---
* [FIX] pdf: Move the mpdf_debug_output view to temp/public and add a note on how to easily remove it

* [FIX] pdf: Set php tidy `show-body-only` option to `auto` which makes tablesorter tables work in mPDF output (not sure why)

See merge request tikiwiki/tiki!9431


(cherry picked from commit 075da344d0b5b876ad5e53c7139764081460a02b)

c5f31727 [FIX] pdf: Set php tidy `show-body-only` option to `auto` which makes...
6b452708 [FIX] pdf: Move the mpdf_debug_output view to temp/public and add a note on how to easily remove it

Co-authored-by: Jonny Bradley <[email protected]>

* [ENH] mpdf: Add debug output pref which saves the html sent to mPDF
---
* [FIX] mpdf: Add a link to the debug output and make the warning clearer

* [FIX] phpcs: Indentation fail #2

* [FIX] phpcs: Indentation fail

* [FIX] mpdf: Don't add empty (whitespace) values to the css, and include the actual data for inline `url(data:...)` to avoid invalid css being sent to mPDF (also add a missing semicolon on the `opacity:0` replacement)

* [FIX] mpdf: Don't add an unnecessary and incorrect `div.row` if not adding modules in `cols` within it

* [ENH] mpdf: Add debug output pref which saves the html sent to mPDF in an html file (with a warning that this html file does not obey tiki permissions)

See merge request tikiwiki/tiki!9320

(cherry picked from commit 53caad511811cd437967b29a8becff4b00028102)

See merge request tikiwiki/tiki!9440

- - - - -


3 changed files:

- lib/pdflib.php
- lib/prefs/print.php
- templates/admin/include_print.tpl


Changes:

=====================================
lib/pdflib.php
=====================================
@@ -443,13 +443,17 @@ class PdfGenerator
 
         $cssStyles = str_replace(
             [".tiki","opacity: 0;","page-break-inside: avoid;"],
-            ["","fill: #fff;opacity:0.3;stroke:black","page-break-inside: auto;"],
+            ["","fill: #fff;opacity:0.3;stroke:black;","page-break-inside: auto;"],
             $allCss
         );
         //PDF import templates will not work if background color is set, need to replace in css
         $cssStyles = $this->replaceCssVariables($cssStyles);
         $cssStyles = $this->evaluateCalcExpressions($cssStyles);
 
+        if ($prefs['print_pdf_mpdf_debug'] === 'y') {
+            $debugOutput = '';
+        }
+
         if (
             array_filter(array_column($pdfPages, 'pageContent'), function ($var) {
                 return preg_match("/\bpdfinclude\b/i", $var);
@@ -490,8 +494,28 @@ class PdfGenerator
                     return date($matches[1]);
                 }, $coverPage[$i]);
             }
-            $mpdf->WriteHTML('<body style="' . $coverPageBgColor . 'margin:0px;padding:0px"><div style="height:100%;background-image:url(' . $coverImage . ');padding:20px;background-repeat: no-repeat;background-position: center; "><div style="' . $coverPageBorder . 'height:95%;">
-            <div style="text-align:' . $textAlign . ';margin-top:30%;' . $textColor . '"><div style="' . $textBgColor . $coverPageTextBorder . 'margin-bottom:10px;font-size:50px;">' . $coverPage[0] . '</div>' . $coverPage[1] . '</div></div></body>');
+            $bodyStyle = "{$coverPageBgColor}margin:0px;padding:0px";
+            $containerStyle = "height:100%;background-image:url({$coverImage});padding:20px;background-repeat:no-repeat;background-position:center;";
+            $borderStyle = "{$coverPageBorder}height:95%;";
+            $contentStyle = "text-align:{$textAlign};margin-top:30%;{$textColor}";
+            $titleStyle = "{$textBgColor}{$coverPageTextBorder}margin-bottom:10px;font-size:50px;";
+
+            $htmlChunk = <<<HTML
+ <body style="$bodyStyle">
+     <div style="$containerStyle">
+         <div style="$borderStyle">
+             <div style="$contentStyle">
+                 <div style="$titleStyle">{$coverPage[0]}</div>
+                 {$coverPage[1]}
+             </div>
+         </div>
+     </div>
+ </body>
+ HTML;
+            if ($prefs['print_pdf_mpdf_debug'] === 'y') {
+                $debugOutput .= $htmlChunk;
+            }
+            $mpdf->WriteHTML($htmlChunk);
         }
         //Checking bookmark
         if (is_array($pdfSettings['autobookmarks'])) {
@@ -583,7 +607,11 @@ class PdfGenerator
                                 }
                             }
                         } catch (Exception $e) {
-                            $mpdf->WriteHTML("PDF not supported");
+                            $htmlChunk = "PDF not supported";
+                            if ($prefs['print_pdf_mpdf_debug'] === 'y') {
+                                $debugOutput .= $htmlChunk;
+                            }
+                            $mpdf->WriteHTML($htmlChunk);
                         }
                     }
                     unlink($tmpExtPDF);
@@ -633,16 +661,27 @@ class PdfGenerator
                     $pdfPage['pageContent'] = $this->getHtmlLayout($pdfPage['pageContent']);
                     $this->_getImages($pdfPage['pageContent'], $tempImgArr);
 
-                    $mpdf->WriteHTML('<html><body class="' . $bodycss . '" style="margin:0px;padding:0px;">' . $cssStyles);
+                    $htmlChunk = '<html><body class="' . $bodycss . '" style="margin:0px;padding:0px;">' . $cssStyles;
+                    if ($prefs['print_pdf_mpdf_debug'] === 'y') {
+                        $debugOutput .= $htmlChunk;
+                    }
+                    $mpdf->WriteHTML($htmlChunk);
                     $pagesTotal += floor(strlen($pdfPage['pageContent']) / 3000);
                     //checking if page content is less than mPDF character limit, otherwise split it and loop to writeHTML
                     for ($charLimit = 0; $charLimit <= strlen($pdfPage['pageContent']); $charLimit += $pdfLimit) {
                         $content_slice = substr($pdfPage['pageContent'], $charLimit, $pdfLimit);
                         if ($content_slice) {
+                            if ($prefs['print_pdf_mpdf_debug'] === 'y') {
+                                $debugOutput .= $content_slice;
+                            }
                             $mpdf->WriteHTML($content_slice);
                         }
                     }
-                    $mpdf->WriteHTML('</body></html>');
+                    $htmlChunk = '</body></html>';
+                    if ($prefs['print_pdf_mpdf_debug'] === 'y') {
+                        $debugOutput .= $htmlChunk;
+                    }
+                    $mpdf->WriteHTML($htmlChunk);
                     $pageNo++;
                     $cssStyles = ''; //set to blank after added with first page
                 }
@@ -661,6 +700,11 @@ class PdfGenerator
         $this->clearTempImg($tempImgArr);
         $tempFile = fopen("temp/public/pdffile_" . session_id() . ".txt", "w");
         fwrite($tempFile, ($pagesTotal * 30));
+
+        if ($prefs['print_pdf_mpdf_debug'] === 'y') {
+            file_put_contents('temp/public/mpdf_debug_output.html', $debugOutput);
+        }
+
         return $mpdf->Output('', 'S');                  // Return as a string
     }
 
@@ -747,45 +791,50 @@ class PdfGenerator
             $htmlLayout["staringPart"] = $htmlLayout["staringPart"] . '<div class="col-xs-12">' . $modules_to_print_contents['topbar_modules'] . '</div>';
         }
 
-        $htmlLayout["staringPart"] = $htmlLayout["staringPart"] . '<div class="row">';
+        if (array_filter($modules_to_print_contents)) {
+            $htmlLayout["staringPart"] = $htmlLayout["staringPart"] . '<div class="row">';
 
-        if ($modules_to_print_contents['left_modules'] || $modules_to_print_contents['right_modules']) {
-            $sideColumn = 'col-xs-4';
+            if ($modules_to_print_contents['left_modules'] || $modules_to_print_contents['right_modules']) {
+                $sideColumn = 'col-xs-4';
 
-            if ($modules_to_print_contents['left_modules'] && $modules_to_print_contents['right_modules']) {
-                $sideColumn = 'col-xs-2';
-            }
+                if ($modules_to_print_contents['left_modules'] && $modules_to_print_contents['right_modules']) {
+                    $sideColumn = 'col-xs-2';
+                }
 
-            if ($modules_to_print_contents['left_modules']) {
-                $htmlLayout["staringPart"] = $htmlLayout["staringPart"] . '<div class="' . $sideColumn . '">' . $modules_to_print_contents['left_modules'] . '</div>';
-            }
+                if ($modules_to_print_contents['left_modules']) {
+                    $htmlLayout["staringPart"] = $htmlLayout["staringPart"] . '<div class="' . $sideColumn . '">'
+                        . $modules_to_print_contents['left_modules'] . '</div>';
+                }
 
-            $htmlLayout["staringPart"] = $htmlLayout["staringPart"] . '<div class="col-xs-8">';
+                $htmlLayout["staringPart"] = $htmlLayout["staringPart"] . '<div class="col-xs-8">';
 
-            if ($modules_to_print_contents['pagetop_modules']) {
-                $htmlLayout["staringPart"] = $htmlLayout["staringPart"] . '<div>' . $modules_to_print_contents['pagetop_modules'] . '</div>';
-            }
+                if ($modules_to_print_contents['pagetop_modules']) {
+                    $htmlLayout["staringPart"] = $htmlLayout["staringPart"] . '<div>' . $modules_to_print_contents['pagetop_modules'] . '</div>';
+                }
 
-            if ($modules_to_print_contents['pagebottom_modules']) {
-                $htmlLayout["endingPart"] = $htmlLayout["endingPart"] . '<div>' . $modules_to_print_contents['pagebottom_modules'] . '</div>';
-            }
+                if ($modules_to_print_contents['pagebottom_modules']) {
+                    $htmlLayout["endingPart"] = $htmlLayout["endingPart"] . '<div>' . $modules_to_print_contents['pagebottom_modules'] . '</div>';
+                }
 
-            $htmlLayout["endingPart"] = $htmlLayout["endingPart"] . '</div>';
+                $htmlLayout["endingPart"] = $htmlLayout["endingPart"] . '</div>';
 
-            if ($modules_to_print_contents['right_modules']) {
-                $htmlLayout["endingPart"] = $htmlLayout["endingPart"] . '<div class="' . $sideColumn . '">' . $modules_to_print_contents['right_modules'] . '</div>';
-            }
-        } else {
-            if ($modules_to_print_contents['pagetop_modules']) {
-                $htmlLayout["staringPart"] = $htmlLayout["staringPart"] . '<div class="col-xs-12">' . $modules_to_print_contents['pagetop_modules'] . '</div>';
-            }
-            if ($modules_to_print_contents['pagebottom_modules']) {
-                $htmlLayout["endingPart"] = $htmlLayout["endingPart"] . '<div class="col-xs-12">' . $modules_to_print_contents['pagebottom_modules'] . '</div>';
+                if ($modules_to_print_contents['right_modules']) {
+                    $htmlLayout["endingPart"] = $htmlLayout["endingPart"] . '<div class="' . $sideColumn . '">'
+                        . $modules_to_print_contents['right_modules'] . '</div>';
+                }
+            } else {
+                if ($modules_to_print_contents['pagetop_modules']) {
+                    $htmlLayout["staringPart"] = $htmlLayout["staringPart"] . '<div class="col-xs-12">'
+                        . $modules_to_print_contents['pagetop_modules'] . '</div>';
+                }
+                if ($modules_to_print_contents['pagebottom_modules']) {
+                    $htmlLayout["endingPart"] = $htmlLayout["endingPart"] . '<div class="col-xs-12">'
+                        . $modules_to_print_contents['pagebottom_modules'] . '</div>';
+                }
             }
-        }
-
-        $htmlLayout["endingPart"] = $htmlLayout["endingPart"] . '</div>';
 
+            $htmlLayout["endingPart"] = $htmlLayout["endingPart"] . '</div>';
+        }
 
         //check if Module contains navbar and force display (when printing nav is by default display none)
         if (str_contains($htmlLayout["staringPart"], '<nav') || str_contains($htmlLayout["endingPart"], '<nav')) {
@@ -1537,16 +1586,19 @@ TEXT;
 
     private function runReplaceVars($css, $variables)
     {
+        // FIXME this pattern doesn't match multi-argument var calls
+        // e.g. "var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)))"
+        // from vendor_bundled/vendor/twbs/bootstrap/dist/css/bootstrap.css:1884
+
         return preg_replace_callback('/var\(--([a-zA-Z0-9-]+)(?:,\s*(.+?))?\)/', function ($matches) use ($variables) {
             $var_name = $matches[1];
             $fallback = isset($matches[2]) ? $matches[2] : null;  // Optional fallback value
 
             // Check if the variable exists
-            if (isset($variables[$var_name])) {
-                $value = $variables[$var_name];
-
+            $value = trim($variables[$var_name]);
+            if ($value) {
                 // If the variable contains another `var()`, recursively resolve it
-                if (preg_match('/var\(--([a-zA-Z0-9-]+)\)/', $value)) {
+                if (preg_match('/var\(--([a-zA-Z0-9-]+)\)/U', $value)) {
                     $value = $this->runReplaceVars($value, $variables);
                 }
                 return $value;  // Return resolved value
@@ -1577,6 +1629,15 @@ TEXT;
         // Create an associative array of variable names to their values
         $variables = array_combine($matches[1], $matches[2]);
 
+        // match again to find inline url data: values
+        preg_match_all('/--([a-zA-Z0-9-]+)\s*:\s*(url\(data:[^)]+\));/', $css, $matches);
+        // Reverse so the light variables are processed first?
+        $matches[1] = array_reverse($matches[1]);
+        $matches[2] = array_reverse($matches[2]);
+
+        // Create an associative array of variable names to their values
+        $variables = array_merge($variables, array_combine($matches[1], $matches[2]));
+
         // Replace all `var(--variable)` occurrences in the CSS
         $css = $this->runReplaceVars($css, $variables);
 
@@ -1605,7 +1666,7 @@ function cleanHtml($html, $config = null, $encoding = 'utf8')
             'clean' => true,
             'output-xhtml' => false,
             'merge-divs' => false,
-            'show-body-only' => false,
+            'show-body-only' => 'auto',
             'new-blocklevel-tags' => 'pdfsettings pdfpage pdfinclude article aside audio bdi canvas details dialog figcaption figure footer header hgroup main menu menuitem nav section source summary template track video',
             'new-empty-tags' => 'embed keygen source track wbr',
             'new-inline-tags' => 'svg audio command datalist embed mark menuitem meter output progress source time video wbr',


=====================================
lib/prefs/print.php
=====================================
@@ -309,6 +309,16 @@ function prefs_print_list()
             'tags' => ['advanced'],
             'default' => 'y',
         ],
+        'print_pdf_mpdf_debug' => [
+            'name' => tra('Debug mPDF output'),
+            'description' => tra('Saves a copy of the html sent to mPDF in the temp directory'),
+            'warning' => tra('Does not respect permissions, do not use with production data.'),
+            'hint' => tra('Clear the public cache to remove this and other temporary files.'),
+            'view' => 'temp/public/mpdf_debug_output.html',
+            'type' => 'flag',
+            'tags' => ['advanced'],
+            'default' => 'n',
+        ],
         'print_wiki_authors' => [
             'name' => tra('Print wiki authors'),
             'description' => tra('Include wiki page authors and date in print versions of wiki pages.'),


=====================================
templates/admin/include_print.tpl
=====================================
@@ -44,6 +44,7 @@
             {preference name=print_pdf_mpdf_coverpage_text_settings}
             {preference name=print_pdf_mpdf_coverpage_image_settings}
             {preference name=print_pdf_mpdf_allow_unsafe_ssl_requests}
+            {preference name=print_pdf_mpdf_debug}
         </div>
         {preference name=allocate_memory_print_pdf}
         {preference name=allocate_time_print_pdf}



View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/71b5dfb6f5505c1fafeeb8d17b0f74107e928c43

-- 
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/71b5dfb6f5505c1fafeeb8d17b0f74107e928c43
You're receiving this email because of your account on gitlab.com.

_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.