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.

ALL countries in checkout, although allowed_countries set correctly

We had the problem, that we have a short list of countries we ship to:

Country list configuration in the backend shows a filtered list

But the country list for the invoice and shipping address in the checkout is complete.

Country select in the checkout shows all countries to choose from

Config

The configuration is fine as you can see above. I checked the cache, because I thought it might be wrong - but the config cache too was fine.

Cache

Digging into the issue shows, that more caches are involved:

\Mage_Checkout_Block_Onepage_Abstract::getCountryOptions
public function getCountryOptions()
{
    $options    = false;
    $useCache   = Mage::app()->useCache('config');
    if ($useCache) {
        $cacheId    = 'DIRECTORY_COUNTRY_SELECT_STORE_' . Mage::app()->getStore()->getCode();
        $cacheTags  = array('config');
        if ($optionsCache = Mage::app()->loadCache($cacheId)) {
            $options = unserialize($optionsCache);
        }
    }

    if ($options == false) {
        $options = $this->getCountryCollection()->toOptionArray();
        if ($useCache) {
            Mage::app()->saveCache(serialize($options), $cacheId, $cacheTags);
        }
    }
    return $options;
}

So I checked the cache entries for DIRECTORY_COUNTRY_SELECT_STORE_DE and found a complete list of countries. So the assumption is: Something "poisons" the cache.

So, what is writing into this cache and maybe we have a broken extension - we don't.

\Mage_XmlConnect_Model_Simplexml_Form_Element_CountryListSelect::_getCountryOptions
\Mage_XmlConnect_Block_Checkout_Address_Form::_getCountryOptions
\Mage_Directory_Block_Data::getCountryHtmlSelect
\Mage_Checkout_Block_Onepage_Abstract::getCountryOptions
\Mage_Catalog_Model_Product_Attribute_Source_Countryofmanufacture::getAllOptions

If we look further into it, we can assume the problem is not from here:

\Mage_XmlConnect_Block_Checkout_Address_Form::_getCountryOptions
\Mage_XmlConnect_Model_Simplexml_Form_Element_CountryListSelect::_getCountryOptions
\Mage_Checkout_Block_Onepage_Abstract::getCountryOptions
\Mage_Directory_Block_Data::getCountryHtmlSelect

because all of them filter the country list via Mage::getModel('directory/country')->getResourceCollection()->loadByStore();, therefore the filter of the config is applied.

\Mage_Catalog_Model_Product_Attribute_Source_Countryofmanufacture

The source model doesn't do correct filtering.

How is the cache filled with wrong data?

This is the tricky part. The only place were I know the source model is used is the backend. But Mage::app()->getStore()->getCode() is always admin. Even when you edit data for another store view.

After digging a while I found out, that when I place a configurable product into the cart one of our extensions reads the country of manufacturer in the frontend and poisons the cache.

I fixed the problem with a rewrite:

class Project_Bugfix_Model_Catalog_Product_Attribute_Source_Countryofmanufacture
    extends Mage_Catalog_Model_Product_Attribute_Source_Countryofmanufacture
{
    /**
     * Get list of all available countries
     *
     * @return mixed
     */
    public function getAllOptions()
    {
        $cacheKey = 'DIRECTORY_COUNTRY_SELECT_STORE_' . Mage::app()->getStore()->getCode();
        if (Mage::app()->useCache('config') && $cache = Mage::app()->loadCache($cacheKey)) {
            $options = unserialize($cache);
        } else {
            if (Mage::app()->getStore()->isAdmin()) {
                $collection = Mage::getModel('directory/country')->getResourceCollection();
            } else {
                $collection = Mage::getModel('directory/country')->getResourceCollection()->loadByStore();
            }
            $options = $collection->toOptionArray();

            if (Mage::app()->useCache('config')) {
                Mage::app()->saveCache(serialize($options), $cacheKey, ['config']);
            }
        }
        return $options;
    }   
}

Custom Product Attributes, Source Models and Flat Tables

How to add an attribute to product_flat_table

Either one of the following attribute settings is true:

  • backend_typ = 'static'
  • is_filterable > 0
  • used_in_product_listing = 1
  • is_used_for_promo_rules= 1
  • used_for_sort_by = 1

Or you add it manually by observing this event:
catalog_product_flat_prepare_columns

Attribute is not added - source models

If you attribute is still not added it might be, because \Mage_Eav_Model_Entity_Attribute_Abstract::getFlatColumns is called on the attribute, which checks wether a source model exists.

public function getFlatColumns()
{
    // If source model exists - get definition from it
    if ($this->usesSource() && $this->getBackendType() != self::TYPE_STATIC) {
        return $this->getSource()->getFlatColums();
    }
    // ...
}

As you can see, getFlatColums is called on your source model, which default implementation is:

// \Mage_Eav_Model_Entity_Attribute_Source_Abstract::getFlatColums
public function getFlatColums()
{
    return array();
}

Therefore your attribute is not added.

And implementation like this might help:

public function getFlatColums()
{
    $attributeCode = $this->getAttribute()->getAttributeCode();
    $column        = [
        'unsigned' => false,
        'default'  => null,
        'extra'    => null,
    ];

    if (Mage::helper('core')->useDbCompatibleMode()) {
        $column['type']    = 'varchar';
        $column['is_null'] = true;
    } else {
        $column['type']     = Varien_Db_Ddl_Table::TYPE_VARCHAR;
        $column['nullable'] = true;
        $column['comment']  = 'Seals column';
    }

    return [$attributeCode => $column];
}

*Update*

How to fill the field

After the final win that the column is created on the flat tables, it was NULL after reindexing. Something is missing and this is:

\Mage_Eav_Model_Entity_Attribute_Source_Abstract::getFlatUpdateSelect

This method has to return a Zend_Db_Select due to this:

\Mage_Catalog_Model_Resource_Product_Flat_Indexer::updateAttribute
// ...
$select = $attribute->getFlatUpdateSelect($storeId);
if ($select instanceof Varien_Db_Select) {
    if ($productIds !== null) {
        $select->where('e.entity_id IN(?)', $productIds);
    }
}
// ...

An example implementation which works for me:

public function getFlatUpdateSelect($store)
{
    return Mage::getResourceSingleton('eav/entity_attribute')
        ->getFlatUpdateSelect($this->getAttribute(), $store);
}