Decoding Magento 2 PDF Woes: How HTML Entities in Custom Options Get Fixed
Unmasking the Magento 2 PDF Entity Bug
At Shopping Mover, we often encounter intricate Magento 2 challenges, and one that recently surfaced in the community highlights a subtle yet impactful rendering issue within the platform's PDF generation for sales documents. Issue #41041 on GitHub, originating from a pull request, meticulously details a bug where custom option values on order, invoice, credit memo, and shipment PDFs are displayed as raw HTML entities rather than their decoded characters.
The Problem: '42" x 80"' Instead of '42" x 80"'
Imagine a customer ordering a product with a custom option, say a 'Size' field with the value 42" x 80". While this displays correctly on the storefront, in admin panels, and within order emails, the generated PDF documents for invoices, credit memos, and shipments would render it as 42" x 80". This seemingly minor display anomaly can lead to confusion and present an unprofessional appearance for merchants.
The Technical Deep Dive: Why It Happens
The core of the problem lies in how Magento 2 handles and renders custom option values across different output channels. Custom option values are stored in the sales_order_item.product_options table, HTML-escaped during the save process via methods like Magento\Catalog\Model\Product\Option\Type\*::getFormattedOptionValue() or getPrintableOptionValue(). For HTML-based interfaces (like the admin panel or customer account), these entities are automatically decoded by the browser. However, PDF generation in Magento utilizes Zend_Pdf, a non-HTML canvas.
The critical asymmetry identified in the issue is that while the PDF renderers (specifically DefaultInvoice, DefaultCreditmemo, and DefaultShipment) already decode item names and SKUs using html_entity_decode() (or via DefaultInvoice::prepareText() which includes this decoding), they failed to apply the same decoding to custom option values. This resulted in the raw HTML entities being drawn directly onto the PDF.
The Proposed Solution and Its Nuances
The solution, as detailed in the associated pull request, involves decoding the option value at the 'draw site' within the respective PDF renderer files:
- For
Invoice/DefaultInvoice.php, the existingprepareText()method, which already handles decoding and RTL (right-to-left) text, is leveraged. - For
Creditmemo/DefaultCreditmemo.phpandShipment/DefaultShipment.php, a direct call tohtml_entity_decode()is added, mirroring their existing handling for names and SKUs.
A key consideration in this fix is its strategic placement. The decoding is applied at the rendering stage rather than modifying the AbstractItems::getItemOptions() method. This is crucial because getItemOptions() is an @api method shared by multiple renderers, and altering it could have unintended side effects on HTML surfaces that correctly decode the values. Furthermore, applying the decode after stripTags ensures safety, preventing any stored HTML tags from being interpreted as live HTML/JS on the PDF canvas, instead rendering them as literal glyph text.
Community Confirmation and Impact
The issue was confirmed by engcom-Bravo, successfully reproducing the bug on a 2.4-develop instance, validating the problem's existence across recent Magento versions. While the initial scope of the fix was limited to core Sales renderers, the author expressed willingness to extend it to other areas like Downloadable product PDF renderers, demonstrating a thorough understanding of potential broader implications.
This detailed bug report and its proposed solution offer significant value to the Magento community, providing a clear understanding of a specific rendering issue and a precise, well-reasoned fix. For merchants, it means cleaner, more professional PDF documents. For developers, it's a lesson in the intricacies of Magento's rendering pipeline and the importance of consistent data handling across different output formats.