Unable to "Print as PDF"

I have created a PDF template, but when I try to print a PDF from a quote, I receive the following error:

Notice : Undefined index: None in C:\xampp\htdocs\SuiteCRM8\vendor\tecnickcom\tcpdf\include\tcpdf_colors.php on line 260
TCPDF ERROR: Some data has already been output, can’t send PDF file

It looks like that is part of an ongoing issue:

1 Like

In theory by github this problem is already solve, but i having the same problem on 2025 an i cant found and solution, do you found it?

What is your SuiteCRM version? Did you upgrade recently? Check config.php file to verify your PDF engine.

On “composer.json” i have ““tecnickcom/tcpdf”: “^6.4”,”

SuiteCRM versión 7.14.6, i recently install it from 0. But before i used 7.8.23, what i have to check of PDF Engine? " “tecnickcom/tcpdf”: “6.2.|6.3.
“tecnickcom/tcpdf”: “6.2.|6.3.|dev-main”" it?
I use php 8.2

okay, in the root directory: config.php

‘pdf’ =>
array (
‘defaultEngine’ => ‘TCPDFEngine’,
),


Yes, it is already like this:

‘pdf’ =>
array (
‘defaultEngine’ => ‘TCPDFEngine’,
),

and the problem in logs is;

[Mon Jan 13 14:33:02.801604 2025] [proxy_fcgi:error] [pid 300909] [client 192.168.31.11:54334] AH01071: Got error ‘PHP message: PHP Warning: Undefined array key “None” in /var/www/crm/vendor/tecnickcom/tcpdf/include/tcpdf_colors.php on line 260’, referer: http://crm.compelect.com.co/index.php?action=ajaxui

I don’t know if this is correct solution.

composer update tecnickcom/tcpdf

The problem was in the code, i had to change the code of GeneratedPDF.php
generatePdf.zip (4.0 KB)

Can you copy and paste code here? :thinking:

Basically i added “ob_start();” after <?php and “ob_clean();” before the part that start to generate the pdf, and another things but thoes is not necessaries, just with this two options is suffienct to fix the problem

<?php ob_start(); /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2021 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ use SuiteCRM\PDF\Exceptions\PDFException; use SuiteCRM\PDF\PDFWrapper; if (!isset($_REQUEST['uid']) || empty($_REQUEST['uid']) || !isset($_REQUEST['templateID']) || empty($_REQUEST['templateID'])) { die('Error retrieving record. This record may be deleted or you may not be authorized to view it.'); } require_once('modules/AOS_PDF_Templates/templateParser.php'); require_once('modules/AOS_PDF_Templates/sendEmail.php'); require_once('modules/AOS_PDF_Templates/AOS_PDF_Templates.php'); global $mod_strings, $sugar_config; $bean = BeanFactory::getBean($_REQUEST['module'], $_REQUEST['uid']); if (!$bean) { sugar_die("Invalid Record"); } $task = $_REQUEST['task']; $variableName = strtolower($bean->module_dir); $lineItemsGroups = array(); $lineItems = array(); $sql = "SELECT pg.id, pg.product_id, pg.group_id FROM aos_products_quotes pg LEFT JOIN aos_line_item_groups lig ON pg.group_id = lig.id WHERE pg.parent_type = '" . $bean->object_name . "' AND pg.parent_id = '" . $bean->id . "' AND pg.deleted = 0 ORDER BY lig.number ASC, pg.number ASC"; $res = $bean->db->query($sql); while ($row = $bean->db->fetchByAssoc($res)) { $lineItemsGroups[$row['group_id']][$row['id']] = $row['product_id']; $lineItems[$row['id']] = $row['product_id']; } $template = BeanFactory::newBean('AOS_PDF_Templates'); $template->retrieve($_REQUEST['templateID']); $object_arr = array(); $object_arr[$bean->module_dir] = $bean->id; //backward compatibility $object_arr['Accounts'] = $bean->billing_account_id ?? ''; $object_arr['Contacts'] = $bean->billing_contact_id ?? ''; $object_arr['Users'] = $bean->assigned_user_id ?? ''; $object_arr['Currencies'] = $bean->currency_id ?? ''; $search = array('/]*?>.*?<\/script>/si', // Strip out javascript
'/<[\/\!]*?[^<>]*?>/si',        // Strip out HTML tags
'/([\r\n])[\s]+/',          // Strip out white space
'/&(quot|#34);/i',          // Replace HTML entities
'/&(amp|#38);/i',
'/&(lt|#60);/i',
'/&(gt|#62);/i',
'/&(nbsp|#160);/i',
'/&(iexcl|#161);/i',
'/<address[^>]*?>/si',
'/&(apos|#0*39);/',
'/&#(\d+);/'

);

$replace = array(‘’,
‘’,
‘\1’,
‘"’,
‘&’,
‘<’,
‘>’,
’ ‘,
chr(161),

’,
"’",
‘chr(%1)’
);

$header = preg_replace($search, $replace, (string) $template->pdfheader);
$footer = preg_replace($search, $replace, (string) $template->pdffooter);
$text = preg_replace($search, $replace, (string) $template->description);
$text = str_replace(“

”, “”, $text);
$text = preg_replace_callback(
‘/{DATE\s+(.*?)}/’,
function ($matches) {
return date($matches[1]);
},
$text
);
$text = str_replace(“$aos_quotes”, “$” . $variableName, $text);
$text = str_replace(“$aos_invoices”, “$” . $variableName, $text);
$text = str_replace(“$total_amt”, “$” . $variableName . “_total_amt”, $text);
$text = str_replace(“$discount_amount”, “$” . $variableName . “_discount_amount”, $text);
$text = str_replace(“$subtotal_amount”, “$” . $variableName . “_subtotal_amount”, $text);
$text = str_replace(“$tax_amount”, “$” . $variableName . “_tax_amount”, $text);
$text = str_replace(“$shipping_amount”, “$” . $variableName . “_shipping_amount”, $text);
$text = str_replace(“$total_amount”, “$” . $variableName . “_total_amount”, $text);

$text = populate_group_lines($text, $lineItemsGroups, $lineItems);

$converted = templateParser::parse_template($text, $object_arr);
$header = templateParser::parse_template($header, $object_arr);
$footer = templateParser::parse_template($footer, $object_arr);

$printable = str_replace(“\n”, “
”, (string) $converted);
ob_clean();
if ($task === ‘pdf’ || $task === ‘emailpdf’) {
$file_name = $mod_strings[‘LBL_PDF_NAME’] . “" . str_replace(" ", "”, (string) $bean->name) . “.pdf”;

try {
    // Asegurarse de que no haya salida previa
    while (ob_get_level()) {
        ob_end_clean();
    }
    
    $pdf = PDFWrapper::getPDFEngine();

$pdf->configurePDF([
‘mode’ => ‘en’,
‘page_size’ => $template->page_size,
‘font’ => ‘DejaVuSansCondensed’,
‘margin_left’ => $template->margin_left,
‘margin_right’ => $template->margin_right,
‘margin_top’ => $template->margin_top,
‘margin_bottom’ => $template->margin_bottom,
‘margin_header’ => $template->margin_header,
‘margin_footer’ => $template->margin_footer,
‘orientation’ => $template->orientation,
‘setcolor’ => true // Agregar esta línea
]);
$pdf->writeHeader($header);
$pdf->writeFooter($footer);
$pdf->writeHTML($printable);

    if ($task === 'pdf') {
        $pdf->outputPDF($file_name, "D");
    } else {
        $fp = fopen($sugar_config['upload_dir'] . 'attachfile.pdf', 'wb');
        fclose($fp);
        $pdf->outputPDF($sugar_config['upload_dir'] . 'attachfile.pdf', 'F');
        $sendEmail = new sendEmail();
        $sendEmail->send_email($bean, $bean->module_dir, '', $file_name, true);
    }
} catch (PDFException $e) {
    LoggerManager::getLogger()->warn('PDFException: ' . $e->getMessage());
    echo 'Error generating PDF: ' . $e->getMessage();
}

} elseif ($task == ‘email’) {
$sendEmail = new sendEmail();
$sendEmail->send_email($bean, $bean->module_dir, $printable, ‘’, false);
}

function populate_group_lines($text, $lineItemsGroups, $lineItems, $element = ‘table’)
{
$firstValue = ‘’;
$firstNum = 0;

$lastValue = '';
$lastNum = 0;

$startElement = '<' . $element;
$endElement = '</' . $element . '>';


$groups = BeanFactory::newBean('AOS_Line_Item_Groups');
foreach ($groups->field_defs as $name => $arr) {
    if (!((isset($arr['dbType']) && strtolower($arr['dbType']) == 'id') || $arr['type'] == 'id' || $arr['type'] == 'link')) {
        $curNum = strpos((string) $text, '$aos_line_item_groups_' . $name);
        if ($curNum) {
            if ($curNum < $firstNum || $firstNum == 0) {
                $firstValue = '$aos_line_item_groups_' . $name;
                $firstNum = $curNum;
            }
            if ($curNum > $lastNum) {
                $lastValue = '$aos_line_item_groups_' . $name;
                $lastNum = $curNum;
            }
        }
    }
}
if ($firstValue !== '' && $lastValue !== '') {
    //Converting Text
    $parts = explode($firstValue, $text);
    $text = $parts[0];
    $parts = explode($lastValue, $parts[1]);
    if ($lastValue === $firstValue) {
        $groupPart = $firstValue . $parts[0];
    } else {
        $groupPart = $firstValue . $parts[0] . $lastValue;
    }

    if ((is_countable($lineItemsGroups) ? count($lineItemsGroups) : 0) != 0) {
        //Read line start <tr> value
        $tcount = strrpos($text, $startElement);
        $lsValue = substr($text, $tcount);
        $tcount = strpos($lsValue, ">") + 1;
        $lsValue = substr($lsValue, 0, $tcount);


        //Read line end values
        $tcount = strpos($parts[1], $endElement) + strlen($endElement);
        $leValue = substr($parts[1], 0, $tcount);

        //Converting Line Items
        $obb = array();

        $tdTemp = explode($lsValue, $text);

        $groupPart = $lsValue . $tdTemp[(is_countable($tdTemp) ? count($tdTemp) : 0) - 1] . $groupPart . $leValue;

        $text = $tdTemp[0];

        foreach ($lineItemsGroups as $group_id => $lineItemsArray) {
            $groupPartTemp = populate_product_lines($groupPart, $lineItemsArray);
            $groupPartTemp = populate_service_lines($groupPartTemp, $lineItemsArray);

            $obb['AOS_Line_Item_Groups'] = $group_id;
            $text .= templateParser::parse_template($groupPartTemp, $obb);
            $text .= '<br />';
        }
        $tcount = strpos($parts[1], $endElement) + strlen($endElement);
        $parts[1] = substr($parts[1], $tcount);
    } else {
        $tcount = strrpos($text, $startElement);
        $text = substr($text, 0, $tcount);

        $tcount = strpos($parts[1], $endElement) + strlen($endElement);
        $parts[1] = substr($parts[1], $tcount);
    }

    $text .= $parts[1];
} else {
    $text = populate_product_lines($text, $lineItems);
    $text = populate_service_lines($text, $lineItems);
}


return $text;

}

function populate_product_lines($text, $lineItems, $element = ‘tr’)
{
$firstValue = ‘’;
$firstNum = 0;

$lastValue = '';
$lastNum = 0;

$startElement = '<' . $element;
$endElement = '</' . $element . '>';

//Find first and last valid line values
$product_quote = BeanFactory::newBean('AOS_Products_Quotes');
foreach ($product_quote->field_defs as $name => $arr) {
    if (!((isset($arr['dbType']) && strtolower($arr['dbType']) == 'id') || $arr['type'] == 'id' || $arr['type'] == 'link')) {
        $curNum = strpos((string) $text, '$aos_products_quotes_' . $name);

        if ($curNum) {
            if ($curNum < $firstNum || $firstNum == 0) {
                $firstValue = '$aos_products_quotes_' . $name;
                $firstNum = $curNum;
            }
            if ($curNum > $lastNum) {
                $lastValue = '$aos_products_quotes_' . $name;
                $lastNum = $curNum;
            }
        }
    }
}

$product = BeanFactory::newBean('AOS_Products');
foreach ($product->field_defs as $name => $arr) {
    if (!((isset($arr['dbType']) && strtolower($arr['dbType']) == 'id') || $arr['type'] == 'id' || $arr['type'] == 'link')) {
        $curNum = strpos((string) $text, '$aos_products_' . $name);
        if ($curNum) {
            if ($curNum < $firstNum || $firstNum == 0) {
                $firstValue = '$aos_products_' . $name;


                $firstNum = $curNum;
            }
            if ($curNum > $lastNum) {
                $lastValue = '$aos_products_' . $name;
                $lastNum = $curNum;
            }
        }
    }
}

if ($firstValue !== '' && $lastValue !== '') {

    //Converting Text
    $tparts = explode($firstValue, $text);
    $temp = $tparts[0];

    //check if there is only one line item
    if ($firstNum === $lastNum) {
        $linePart = $firstValue;
    } else {
        $tparts = explode($lastValue, $tparts[1]);
        $linePart = $firstValue . $tparts[0] . $lastValue;
    }


    $tcount = strrpos($temp, $startElement);
    $lsValue = substr($temp, $tcount);
    $tcount = strpos($lsValue, ">") + 1;
    $lsValue = substr($lsValue, 0, $tcount);

    //Read line end values
    $tcount = strpos($tparts[1], $endElement) + strlen($endElement);
    $leValue = substr($tparts[1], 0, $tcount);
    $tdTemp = explode($lsValue, $temp);

    $linePart = $lsValue . $tdTemp[(is_countable($tdTemp) ? count($tdTemp) : 0) - 1] . $linePart . $leValue;
    $parts = explode($linePart, $text);
    $text = $parts[0];

    //Converting Line Items
    if ((is_countable($lineItems) ? count($lineItems) : 0) != 0) {
        foreach ($lineItems as $id => $productId) {
            if ($productId != null && $productId != '0') {
                $obb['AOS_Products_Quotes'] = $id;
                $obb['AOS_Products'] = $productId;
                $text .= templateParser::parse_template($linePart, $obb);
            }
        }
    }
    $partsCount = is_countable($parts) ? count($parts) : 0;

    for ($i = 1; $i < $partsCount; $i++) {
        $text .= $parts[$i];
    }
}
return $text;

}

function populate_service_lines($text, $lineItems, $element = ‘tr’)
{
$firstValue = ‘’;
$firstNum = 0;

$lastValue = '';
$lastNum = 0;

$startElement = '<' . $element;
$endElement = '</' . $element . '>';

$text = str_replace("\$aos_services_quotes_service", "\$aos_services_quotes_product", (string) $text);

//Find first and last valid line values
$product_quote = BeanFactory::newBean('AOS_Products_Quotes');
foreach ($product_quote->field_defs as $name => $arr) {
    if (!((isset($arr['dbType']) && strtolower($arr['dbType']) == 'id') || $arr['type'] == 'id' || $arr['type'] == 'link')) {
        $curNum = strpos($text, '$aos_services_quotes_' . $name);
        if ($curNum) {
            if ($curNum < $firstNum || $firstNum == 0) {
                $firstValue = '$aos_products_quotes_' . $name;
                $firstNum = $curNum;
            }
            if ($curNum > $lastNum) {
                $lastValue = '$aos_products_quotes_' . $name;
                $lastNum = $curNum;
            }
        }
    }
}
if ($firstValue !== '' && $lastValue !== '') {
    $text = str_replace("\$aos_products", "\$aos_null", $text);
    $text = str_replace("\$aos_services", "\$aos_products", $text);

    //Converting Text
    $tparts = explode($firstValue, $text);
    $temp = $tparts[0];

    //check if there is only one line item
    if ($firstNum === $lastNum) {
        $linePart = $firstValue;
    } else {
        $tparts = explode($lastValue, $tparts[1]);
        $linePart = $firstValue . $tparts[0] . $lastValue;
    }

    $tcount = strrpos($temp, $startElement);
    $lsValue = substr($temp, $tcount);
    $tcount = strpos($lsValue, ">") + 1;
    $lsValue = substr($lsValue, 0, $tcount);

    //Read line end values
    $tcount = strpos($tparts[1], $endElement) + strlen($endElement);
    $leValue = substr($tparts[1], 0, $tcount);
    $tdTemp = explode($lsValue, $temp);

    $linePart = $lsValue . $tdTemp[(is_countable($tdTemp) ? count($tdTemp) : 0) - 1] . $linePart . $leValue;
    $parts = explode($linePart, $text);
    $text = $parts[0];

    //Converting Line Items
    if ((is_countable($lineItems) ? count($lineItems) : 0) != 0) {
        foreach ($lineItems as $id => $productId) {
            if ($productId == null || $productId == '0') {
                $obb['AOS_Products_Quotes'] = $id;
                $text .= templateParser::parse_template($linePart, $obb);
            }
        }
    }
    $partsCount = is_countable($parts) ? count($parts) : 0;

    for ($i = 1; $i < $partsCount; $i++) {
        $text .= $parts[$i];
    }
}
return $text;

}