Adding viewModels to widgets

Or a journey through too many XML Schema files

We wanted to add the possibility to add a viewModel to a widget. We thought about it. There are two possibilities:

  1. Add it to an instance, e.g. one added in the backend or one declared via {{widget ...}}
  2. Add it to a widget type (e.g. by adding it to the widget.xml)

Widget Instance

The problems we had in mind and wanted to solve were like having a category or product list, and we wanted to move the loading of the products and/or category to a viewModel instead of adding all the business logic to the widget block. But especially with the curly brace syntac {{widget ...}} it is hard to add a parameter for the view model. And let’s be honest. In (almost?) all cases, we need the same viewModel to load data, products, etc. And adding the parameters, for the products or category already works fine.
So instead of the widget instance, we decided to add it directly to widget.xml

Widget Type

So the way to go was clear:
Add the possibility to have an <arguments> node in the widget.xml which gets copied in the merged layout. The tasks looked easy and I thought I could get it done in two days on the FireGento Hackathon in Bremen this weekend. But as you might guess, when I’m writing a blog post I don’t have much code to share :-)

Implementation and problems

When a widget is saved, Magento2 already creates layout xml for the widget block. This happens in \Magento\Widget\Model\Widget\Instance::generateLayoutUpdateXml.
So what we need to do?

  1. Add our <arguments> to widget.xml
    2.Make it accessable in \Magento\Widget\Model\Widget\Instance
  2. Add it to the generated layout.xml

Add it to widget.xml

It took a while to understand how everything works and I think I understand it now, which is the reason I stopped working on the task.
The widget.xml is validated agains Magento/Widget/etc/widget_file.xsd. So we need to change the widget_file.xsd to allow <arguments>. It took me a few hours to understand, that I need to edit widget_file.xsd instead of the widget.xsd, but after the penny dropped it went well. Unfortunately the XML Schema defines its own boolean, number, string, etc. therefore we have to rename all the types. Problem understood and fixed.

Open a widget

(I’m not 100% sure about this, I tried too much and didn’t test it deeply)
When you open a widget in the backend, the xml is validated against the widget.xsd, so we have to change this too. The structure is nearly the same, so one can copy the changes from widget_file.xsd.

We now can change a widget.xml and add the arguments, that is great! That looks like that:

<widgets ...>
    <widget ...>
        <arguments>
            <argument name="string" xsi:type="stringArgument">String</argument>
        </arguments>
    </widget>
</widgets>

To make the argument available during the \Magento\Widget\Model\Widget\Instance save, we have to fix the PHP-Validation of the XML and add our new node in \Magento\Widget\Model\Config\Converter::convert:

switch ($widgetSubNode->nodeName) {
		// ...
    case 'arguments':
        $widgetArray['arguments'] = $source->saveXML($widgetSubNode);
        break;
		// ...

Now we have finally the arguments in our widget instance and can add it to the \Magento\Widget\Model\Widget\Instance::generateLayoutUpdateXml!

$xml .= '<block class="' . $this->getType() . '" name="' . $hash . '"' . $template . '>';

	// NEW CODE
	if(isset($this->_widgetConfigXml['arguments'])) {
		$xml .= $this->_widgetConfigXml['arguments'];
	}

Cool! It should work now.

Add a widget to the homepage, hit F5 and... BAM.

1 exception(s):
Exception #0 (Magento\Framework\Config\Dom\ValidationException): Element 'argument', attribute '{http://www.w3.org/2001/XMLSchema-instance}type': The QName value 'stringArgument' of the xsi:type attribute does not resolve to a type definition.
Line: 689

Element 'argument': The type definition is abstract.
Line: 689

The layout which is generated by the method is added to the global layout (what else?! :D) and again validated, this time against lib/internal/Magento/Framework/View/Layout/etc/layout_merged.xsd.

Unfortunately due to our renaming it doesn’t work as expected.

What we need is to rewrite the types to the layout types. I started the blogpost without a plan how to continue, but now I see a way, we str_replace the Argument in the name.

Horrible idea, but would work.

If one day someone wants to add viewModels again to widgets, this blogpost might help you. Good luck with it!

Configurable Attribute in Magento 1

To use an attribute for configurable products we need:

  • is_configurable = 1
  • is_global = 1
  • is_user_defined = 1
  • backend_type = int
  • frontend_input = select

Translate Magento Cookie Notice

Thanks to DSGVO some customers want to have a cookie notice:

Bildschirmfoto-2018-05-24-um-15.22.39

Magento has this feature already built in. You can turn it on in the backend:

System > Configuration > General > Web > Session Cookie Management > Cookie Restriction Mode: YES

Beside this you can define what the customer sees in the CMS block: cookie_restriction_notice_block.

Magento can have the same cms block identifier for different stores. Unfortunately this doesn't work for this feature.

The reason is:

class Mage_Page_Block_Html_CookieNotice extends Mage_Core_Block_Template
{
    public function getCookieRestrictionBlockContent()
    {
        $blockIdentifier = Mage::helper('core/cookie')->getCookieRestrictionNoticeCmsBlockIdentifier();
        $block = Mage::getModel('cms/block')->load($blockIdentifier, 'identifier');
        //             [...]
    }
}

load() doesn't care about the store mapping and takes the first block with the identifier it finds - which is for every store the same.

I fixed the problem with a rewrite on the block and replaced the method with:

public function getCookieRestrictionBlockContent()
{
    $blockIdentifier = Mage::helper('core/cookie')->getCookieRestrictionNoticeCmsBlockIdentifier();
    $block = Mage::getModel('cms/block')
        // ADDED store filter
        ->setStoreId(Mage::app()->getStore()->getId()) 
        ->load($blockIdentifier);

    $html = '';
    if ($block->getIsActive()) {
        /* @var $helper Mage_Cms_Helper_Data */
        $helper = Mage::helper('cms');
        $processor = $helper->getBlockTemplateProcessor();
        $html = $processor->filter($block->getContent());
    }

    return $html;
}

git: ignored files and why

find . -type f  | git check-ignore -v --stdin

git check-ignore -v **/* works too, if you don't encounter zsh: argument list too long: git.

Payone and no order confirmation email

Welcome! You have the problem, that Payone is not sending order confirmation emails in Magento 1? And we are talking about a local development system, were Payone is not able to deliver the Instant Payment Notification (IPN - or whatever it is called at Payone)?

Then I can tell you: This is correct. And I have no clue to fix it.

But why?

Magento only sends an order confirmation email if:

\Mage_Checkout_Model_Type_Onepage::saveOrder
[...]
if (!$redirectUrl && $order->getCanSendNewEmailFlag()) {
    try {
        $order->queueNewOrderEmail();
    } catch (Exception $e) {
        Mage::logException($e);
    }
}
[...]

Payone is setting can_send_new_email_flag explicitly to false in \Payone_Core_Model_Payment_Method_Abstract::initialize, to take control of the email.

$order->setCanSendNewEmailFlag(false);

And when the order is appointed, the email is send:

\Payone_Core_Model_Observer_TransactionStatus_OrderConfirmation::onAppointed
public function onAppointed(Varien_Event_Observer $observer)
{
    $this->initData($observer);

    $this->getServiceOrderConfirmation()->sendMail($this->order);
}

And this method is called via observer:

<config>
    <global>
        <events>
             <payone_core_transactionstatus_appointed>
                <observers>
                    <payone_core_observer_orderConfirmation>
                        <type>singleton</type>
                        <class>payone_core/observer_transactionStatus_orderConfirmation</class>
                        <method>onAppointed</method>
                    </payone_core_observer_orderConfirmation>
            </payone_core_transactionstatus_appointed>
        </events>
    </global>
</config>

I hope this helps.