Magento – Magento2: Override template not working

magento2templatetheme

I am trying to put template /vendor/magento/module-payment/view/frontend/templates/info/default.phtml to my theme on below path

app/design/frontend/Smartwave/porto/Magento_Payment/templates/info/default.phtml

Another template works perfect. Above file is used in email template so I can't check by template path hint but I have put some html to test but its not taking from theme its take from vendor folder only.

I have removed cache,removed pub/static and did static content deploy. There is not layout related to it so I have not override it.

But no luck.

Edit:

I've also override info block to see if its work. below is code.

di file

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Payment\Block\Info" type="Vendor\Module\Block\Info" />
</config>

and block class file.

<?php
namespace Vendor\Module\Block;
use Magento\Framework\View\Element\Template;
class Info extends \Magento\Payment\Block\Info {
    /**
     * Payment rendered specific information
     *
     * @var \Magento\Framework\DataObject
     */
    public $_paymentSpecificInformation;

    /**
     * @var string
     */
    public $_template = 'Vendor_Module::info/default.phtml';

    /**
     * Retrieve info model
     *
     * @return \Magento\Payment\Model\InfoInterface
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function getInfo()
    {
        $writer = new \Zend\Log\Writer\Stream(BP . '/var/log/infoblock.log');
            $logger = new \Zend\Log\Logger();
            $logger->addWriter($writer);
            $logger->info("Info block");

        $info = $this->getData('info');
        if (!$info instanceof \Magento\Payment\Model\InfoInterface) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('We cannot retrieve the payment info model object.')
            );
        }
        return $info;
    }

    /**
     * Retrieve payment method model
     *
     * @return \Magento\Payment\Model\MethodInterface
     */
    public function getMethod()
    {
        return $this->getInfo()->getMethodInstance();
    }

    /**
     * Render as PDF
     * @return string
     */
    public function toPdf()
    {
        $this->setTemplate('Magento_Payment::info/pdf/default.phtml');
        return $this->toHtml();
    }

    /**
     * Getter for children PDF, as array. Analogue of $this->getChildHtml()
     *
     * Children must have toPdf() callable
     * Known issue: not sorted
     * @return array
     */
    public function getChildPdfAsArray()
    {
        $result = [];
        foreach ($this->getLayout()->getChildBlocks($this->getNameInLayout()) as $child) {
            if (method_exists($child, 'toPdf') && is_callable([$child, 'toPdf'])) {
                $result[] = $child->toPdf();
            }
        }
        return $result;
    }

    /**
     * Get some specific information in format of array($label => $value)
     *
     * @return array
     */
    public function getSpecificInformation()
    {
        return $this->_prepareSpecificInformation()->getData();
    }

    /**
     * Render the value as an array
     *
     * @param mixed $value
     * @param bool $escapeHtml
     * @return array
     */
    public function getValueAsArray($value, $escapeHtml = false)
    {
        if (empty($value)) {
            return [];
        }
        if (!is_array($value)) {
            $value = [$value];
        }
        if ($escapeHtml) {
            foreach ($value as $_key => $_val) {
                $value[$_key] = $this->escapeHtml($_val);
            }
        }
        return $value;
    }

    /**
     * Check whether payment information should show up in secure mode
     * true => only "public" payment information may be shown
     * false => full information may be shown
     *
     * @return bool
     * @SuppressWarnings(PHPMD.BooleanGetMethodName)
     */
    public function getIsSecureMode()
    {
        if ($this->hasIsSecureMode()) {
            return (bool)(int)$this->_getData('is_secure_mode');
        }

        $method = $this->getMethod();
        if (!$method) {
            return true;
        }

        $store = $method->getStore();
        if (!$store) {
            return false;
        }

        $methodStore = $this->_storeManager->getStore($store);
        return $methodStore->getCode() != \Magento\Store\Model\Store::ADMIN_CODE;
    }

    /**
     * Prepare information specific to current payment method
     *
     * @param null|\Magento\Framework\DataObject|array $transport
     * @return \Magento\Framework\DataObject
     */
    protected function _prepareSpecificInformation($transport = null)
    {
        if (null === $this->_paymentSpecificInformation) {
            if (null === $transport) {
                $transport = new \Magento\Framework\DataObject();
            } elseif (is_array($transport)) {
                $transport = new \Magento\Framework\DataObject($transport);
            }
            $this->_paymentSpecificInformation = $transport;
        }
        return $this->_paymentSpecificInformation;
    }
}

Best Answer

This is a bug in Magento. It’s been already reported here: https://github.com/magento/magento2/issues/10402 but closed due to inactivity.

The theme template overrides are not resolved correctly when the admin is trying to emulate the frontend (note that emails are sent via the admin but the environment emulation is supposed to take the correct theme template from the frontend based on the store id of the order). However, it always take the default template, making impossible an overwrite by copying the template into your theme.

I’m currently looking for a workaround and will post it here if I find one.

UPDATE:

Just found a workaround to force Magento to take the overwritten template from your custom theme. You need to overwrite the function

getInfoBlockHtml()

from vendor/magento/module-payment/Helper/Data.php

Specifically, by passing the $force = true option to the startEnvironmentEmulation() method it picks up the correct theme and will render the modified template.

In a custom module add the following preference to the app/code/[Vendor]/[ModuleName]/etc/di.xml file:

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Payment\Helper\Data" type="[Vendor]\[ModuleName]\Helper\Payment\Data" />
</config>

Now create a new helper file that extends the original helper: app/code/[Vendor]/[ModuleName]/Helper/Payment/Data.php

<?php
namespace [Vendor]\[ModuleName]\Helper\Payment;

use Magento\Payment\Model\InfoInterface;

/**
 * Payment module base helper
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 *
 * @api
 * @since 100.0.2
 */
class Data extends \Magento\Payment\Helper\Data
{
    /**
     * Render payment information block
     *
     * @param InfoInterface $info
     * @param int $storeId
     * @return string
     * @throws \Exception
     */
    public function getInfoBlockHtml(InfoInterface $info, $storeId)
    {
        // add the area and most importantly set the "force" parameter to true to allow the theme template override
        $this->_appEmulation->startEnvironmentEmulation($storeId, \Magento\Framework\App\Area::AREA_FRONTEND, true);
        // end of custom code

        try {
            // Retrieve specified view block from appropriate design package (depends on emulated store)
            $paymentBlock = $this->getInfoBlock($info);
            $paymentBlock->setArea(\Magento\Framework\App\Area::AREA_FRONTEND)
                     ->setIsSecureMode(true);
            $paymentBlock->getMethod()
                     ->setStore($storeId);
            $paymentBlockHtml = $paymentBlock->toHtml();
        } catch (\Exception $exception) {
            $this->_appEmulation->stopEnvironmentEmulation();
            throw $exception;
        }

        $this->_appEmulation->stopEnvironmentEmulation();

        return $paymentBlockHtml;
    }
}

Now your custom template at app/design/frontend/[Vendor]/[theme_name]/Magento_Payment/templates/info/default.phtml will be used instead of the default one. Enjoy!