Our first extension for Magento 2

We are proud to announce our first public extension for Magento 2, the Attribute Import-Export. It imports and exports Magento attributes, attribute options and attribute swatches. Being compatible with our Attribute Import-Export for Magento 1 extension (will be published shortly, contact us if you are interested), migration will be easier than ever. Soon in our webshop you will be able to buy these extensions together, with great discounts. Until then visit the Attribute Import-Export extension in Magento Marketplace.

Content disappeared partially after upgrading or patching Magento

I just met the problem, that after the SUPEE-6788 patch was applied to the website or the Magento was upgraded to at least version 1.9.2.2, some stuff has disappeared from the frontend. It was really annoying, because previously it worked well. After some digging, it has turned out that the patch brought some new features, like block permissions. What does this mean? When you have a cms page or static block which contains something like:

{{block type=”directory/currency” template=”directory/currency.phtml”}}

it simply won’t show up. Besides this, you will get some error messages in system.log:

Security problem: directory/currency has not been whitelisted.

Where can it be whitelisted? The answer is simple. In the Magento backend under System -> Permissions there is a new submenu, named Blocks. By clicking on this, you will see the list of whitelisted blocks. You can simply just add “directory/currency” and make sure that “Allowed” is set to Yes, then save it. This should do the trick.

 

Making work Magento with PHP 7 RC1, RC2 and RC3

I was curious whether it will work with PHP 7 the latest version of Magento Community Edition. When I write this article the latest released version is 1.9.2.1. As I expected, Magento has crashed with an ugly error message like:

Fatal error: Uncaught Error: Function name must be a string in ... app\code\core\Mage\Core\Model\Layout.php:555 ...

This error was easy to fix because the problem was in the following line:

$out .= $this->getBlock($callback[0])->$callback[1]();

Instead it should be:

$out .= $this->getBlock($callback[0])->{$callback[1]}();

Since it is not recommended to edit the core files, we will override them, which means that we will create the very similar structure of the core files in app/code/local. Example if we want to override app/code/core/Mage/Core/Model/Layout.php, then we will copy this file into app/code/local/Mage/Core/Model/Layout.php. Magento will automatically include what is in the app/code/local folder. Despite this solution works, this can be considered only a temporary solution until a fixed Magento / PHP 7 will be released. Overriding core files could be dangerous, problems could occur especially after upgrading Magento to a newer version.

This small change seemed to fix Magento, but I was wrong. While the frontend worked well, the backend did not log me in. In the meantime I had a lot of problems getting PHP and Apache configuration files ready. So I didn’t know whether my configuration is bad or simply the new PHP 7 RC1 does not like Magento.
Finally I found out the main reason why the login doesn’t work: despite the authentication of the backend user has happened and I was redirected back to the admin index, the user object was not saved into the session. Investigation was very difficult because currently there is no Xdebug for the unreleased PHP7. After another couple of hours of digging I’ve found out that in one of Magento’s abstract classes it was specified something like:

$this->_data = &$_SESSION;

So Magento just sets $this->_data as a reference to the $_SESSION. Hmm, maybe that thing does not work… And yes. First I just tried to use in the admin/session class instead of

$this->setUser($user);

this:

$_SESSION['admin']['user'] = $user;

then suddenly Magento logged me in. The next step was to make the session related functionality work all over Magento. For this I had to override Mage_Core_Model_Session_Abstract_Varien and had to change getData from:

public function getData($key='', $clear = false)
{
    $data = parent::getData($key);
    if ($clear && isset($this->_data[$key])) {
        unset($this->_data[$key]);
    }
    return $data;
}

to

public function getData($key='', $clear = false)
{
    $data = $this->getSessionData($key);
    if ($clear && isset($_SESSION[$key])) {
        unset($_SESSION[$key]);
    }
    return $data;
}
public function getSessionData($key='', $index=null)
{
    if (''===$key) {
        return $_SESSION;
    }

    $default = null;

    // accept a/b/c as ['a']['b']['c']
    if (strpos($key,'/')) {
        $keyArr = explode('/', $key);
        $data = $_SESSION;
        foreach ($keyArr as $i=>$k) {
            if ($k==='') {
                return $default;
            }
            if (is_array($data)) {
                if (!isset($data[$k])) {
                    return $default;
                }
                $data = $data[$k];
            } elseif ($data instanceof Varien_Object) {
                $data = $data->getData($k);
            } else {
                return $default;
            }
        }
        return $data;
    }

    // legacy functionality for $index
    if (isset($_SESSION[$key])) {
        if (is_null($index)) {
            return $_SESSION[$key];
        }

        $value = $_SESSION[$key];
        if (is_array($value)) {
            //if (isset($value[$index]) && (!empty($value[$index]) || strlen($value[$index]) > 0)) {
            /**
            * If we have any data, even if it empty - we should use it, anyway
            */
            if (isset($value[$index])) {
                return $value[$index];
            }
            return null;
        } elseif (is_string($value)) {
            $arr = explode("\n", $value);
            return (isset($arr[$index]) && (!empty($arr[$index]) || strlen($arr[$index]) > 0)) ? $arr[$index] : null;
        } elseif ($value instanceof Varien_Object) {
            return $value->getData($index);
        }
        return $default;
    }
    return $default;
}

then I had to create the __call magic method to override the parent class’ behavior:

public function __call($method, $args)
{
    if (substr($method, 0, 3) == "has")
    {
        $key = $this->_underscore(substr($method,3));
        return isset($_SESSION[$key]);
    }
    return parent::__call($method, $args);
}

and then I’ve added the modified setData, unsetData and _addFullNames methods:

public function setData($key, $value=null)
{
    $this->_hasDataChanges = true;
    if(is_array($key)) {
        $_SESSION = $key;
        $this->_addFullNames();
    } else {
        $_SESSION[$key] = $value;
        if (isset($this->_syncFieldsMap[$key])) {
            $fullFieldName = $this->_syncFieldsMap[$key];
            $_SESSION[$fullFieldName] = $value;
        }
    }
    return $this;
}
public function unsetData($key=null)
{
    $this->_hasDataChanges = true;
    if (is_null($key)) {
        $_SESSION = array();
    } else {
        unset($_SESSION[$key]);
        if (isset($this->_syncFieldsMap[$key])) {
            $fullFieldName = $this->_syncFieldsMap[$key];
            unset($_SESSION[$fullFieldName]);
        }
    }
    return $this;
}

protected function _addFullNames()
{
    $existedShortKeys = array_intersect($this->_syncFieldsMap, array_keys($_SESSION));
    if (!empty($existedShortKeys)) {
        foreach ($existedShortKeys as $key) {
            $fullFieldName = array_search($key, $this->_syncFieldsMap);
            $_SESSION[$fullFieldName] = $_SESSION[$key];
        }
    }
}

It has worked for me. Maybe it can be fixed with some php.ini setting also, but I really don’t see a reason to disable passing variables by reference. We will find out shortly if PHP 7 RC2 solves this problem.

So, to sum it up:
– the problem with the “fatal error. function name must be a string” can be fixed by overriding Mage_Core_Model_Layout
– the other problem:  getData / setData / unsetData methods does not write into the session, which causes the admin login problem. It can be fixed by overriding Mage_Core_Model_Session_Abstract_Varien.

Happy patching!

p.s. In the meantime the PHP team released the PHP 7 RC2, which has the same behavior as the RC1. It seems that the problem is already reported and it is under discussion by the PHP team.

p.s.2 In PHP 7 RC3 the session-related problem has been solved, so the only thing you need is to fix Layout.php.

p.s.3 If you override Varien.php with this fix, while using the final version of PHP 7, you will meet some serious problems in the frontend, the customers not being able to log in. So I repeat: don’t use it with the final releases of PHP 7!

Magento: How to remove index.php from admin URL

As you may already know, the index.php from the URLs can be easily removed from the frontend, by going to System -> Configuration -> Web -> Search Engine Optimization and setting Use Web Server Rewrites to Yes. But this works only for the frontend. This can be tricked easily, by overriding one of the Magento core files. Lets see the method which is responsible for the URL generation in Mage_Core_Model_Store:

protected function _updatePathUseRewrites($url)
{
    if ($this->isAdmin() || !$this->getConfig(self::XML_PATH_USE_REWRITES) || !Mage::isInstalled()) {
        $indexFileName = $this->_isCustomEntryPoint() ? 'index.php' : basename($_SERVER['SCRIPT_FILENAME']);
        $url .= $indexFileName . '/';
    }
    return $url;
}

As you can see, when the $this->isAdmin() is true, then it will add the index.php to the URL. So, in order to remove it, we have to:

1. copy app/code/core/Mage/Core/Model/Store.php to app/code/local/Mage/Core/Model/Store.php
2. modify app/code/local/Mage/Core/Model/Store.php file so it will look like:

protected function _updatePathUseRewrites($url)
{
    if (!$this->getConfig(self::XML_PATH_USE_REWRITES) || !Mage::isInstalled()) {
        $indexFileName = $this->_isCustomEntryPoint() ? 'index.php' : basename($_SERVER['SCRIPT_FILENAME']);
        $url .= $indexFileName . '/';
    }
    return $url;
}

Now the only thing you need to do is to go to System -> Configuration -> Web -> Search Engine Optimization and to set Use Web Server Rewrites to Yes, then to clear your cache. This will remove the index.php both from frontend and backend.

Note that overriding core functionality must be avoided as much as possible.

Magento: Paypal express checkout unable to communicate with gateway

Do you have PayPal express checkout on your website? Does it give the following error message when pressing the “Check out with PayPal” button?:

Unable to communicate with the PayPal gateway.

If you have tried everything and you are still searching for a solution, then you are on the right place. I’ve spent the last couple of hours to figure this out. If you enable the Magento logs, then you will find in your exception.log two errors:

exception ‘Mage_Core_Exception’ with message ‘Unable to communicate with the PayPal gateway.’

and

exception ‘Exception’ with message ‘PayPal NVP CURL connection error #77: Problem with the SSL CA cert (path? access rights?)’

What does this mean? It means that something is not configured well.  And this something is the parameter named CALLBACK. On PayPal developer’s page it is written clearly:

CALLBACK: (Optional) URL to which the callback request from PayPal is sent. It must start with HTTPS for production integration. It can start with HTTPS or HTTP for sandbox testing.

Character length and limitations: 1024 single-byte characters.

This field is available since version 53.0.

Most probably you are trying to use express checkout from a http shopping cart, which is not permitted by PayPal in production environments. Solution? Buy a SSL certificate (if you don’t have one already), configure Magento’s base secure URL in system configuration and it will work. Otherwise you won’t be able to use PayPal Express checkout.

How to fix Php 5.6 deprecated messages in Magento

I know, that Magento officially does not support Php 5.6, but as you may know, the latest version of Php (right now 5.6.1) comes with a lots of new features and optimizations, which could be handy. But those nasty deprecated messages “Deprecated functionality: iconv_set_encoding(): Use of iconv.internal_encoding is deprecated” fills Magento’s logs, right?
There is a temporary solution for this, simply we just have to override some core files of Magento (well, they are core Zend Framework files). Here are the steps to follow:
1. Create the following folders:
/app/code/local/Zend/Locale
/app/code/local/Zend/Service
/app/code/local/Zend/Validate
/app/code/local/Zend/XmlRpc

2. Copy the following files from /lib/Zend/ to /app/code/local/Zend/:
Locale/Format.php
Service/Audioscrobbler.php
Service/Technorati.php
Validate/Hostname.php
Validate/StringLength.php
XmlRpc/Client.php

3. Open the files from /app/code/local/Zend/ in a text editor and search for “internal_encoding” and replace them with “default_charset”. Before doing this, make sure that the “internal_encoding” string is a parameter of an iconv related function. Don’t replace “mb_internal_encoding”, “$internal_encoding” or any other non iconv related stuff, because it will break the code.

This solution is for Magento 1.8, but it should be very similar for 1.9 as well. If you are not sure, just search in all files for “internal_encoding” and do the three steps above for the needed files.

That’s all. Easy, isn’t it?

WordPress image not editable

It just happened to me that after uploading an image (a portrait oriented) in the WordPress (version 4) admin page, the image was rotated. I tried to edit the image, by clicking on the Edit original image, but image simply didn’t load. When I opened the url the placeholder pointed to in a new window, it was showing a “-1” instead of the image itself.

I could solve this problem, by opening the image file in an image editing software and saving it as a PNG file, then reuploading. Then the image was loaded well.

It seems that the problem happens with photos taken by devices which has accelerometer or other similar features (ex. smartphones).

Magento: Unquote database query elements

If you are in a hurry, then just scroll down to the end of the article to see my answer to the problem.

Sometimes you have to use complex queries for ex. collections, which will be really an ugly code. Since direct query is not a real option, we have to go with the Magento way. This is all nice and beautiful, until you realize, that your idea is not possible with the Magento-Zend selects. Lets see an example, first with the needed result:

SELECT `main_table`.*, (SELECT `b`.`name` FROM `names` AS `b` WHERE `b`.`customer_id` = `main_table`.`customer_id` ORDER BY `b`.`name` DESC LIMIT 1) AS `names_name`
FROM `sales_flat_order_grid` AS `main_table` 
WHERE `main_table`.`group_id` = 1

You can say that this is an ugly query which can be replaced with joins and maybe with other methods, but we are talking about the unquoting something from the query.
So, the query above translated to a collection will look something like:

$collection = Mage::getResourceModel("sales/order_grid_collection")
->addExpressionFieldToSelect("names_name", "(SELECT `b`.`name` FROM `names` AS `b` WHERE `b`.`customer_id` = `main_table`.`entity_id` ORDER BY `b`.`name` DESC LIMIT 1)")
->addFieldToFilter("`main_table`.`group_id`", "1");

Ugly, isn’t it? And if we echo out the resulting query with echo $collection->getSelect(), it will be even more uglier:

SELECT `main_table`.*, `(SELECT ``b```.```name`` FROM ``names``` AS ```b`` WHERE ``b``.``customer_id`` = ``main_table``.``entity_id`` ORDER BY ``b``.``name`` DESC LIMIT 1)` 
FROM `sales_flat_order_grid` AS `main_table` 
WHERE (`main_table`.`group_id` = '1')

Lets get rid of the double and triple quotes by changing our addExpressionFieldToSelect:

->addExpressionFieldToSelect("names_name", "(SELECT b.name FROM names AS b WHERE b.customer_id = main_table.entity_id ORDER BY b.name DESC LIMIT 1)")

Now it is much better, but still not ok:

SELECT `main_table`.*, `(SELECT b`.`name FROM names` AS `b WHERE b.customer_id = main_table.entity_id ORDER BY b.name DESC LIMIT 1)` 
FROM `sales_flat_order_grid` AS `main_table` 
WHERE (group_id = '1')

Our whole subselect is quoted and it is not aliased at all. Here comes the trick. We will have to use the Zend_Db_Expr for this and we will put this new column directly into our select with $collection->getSelect()->columns() method. See it how:

$collection = Mage::getResourceModel("sales/order_grid_collection");
$collection->getSelect()->columns(array("names_name" => new Zend_Db_Expr("(SELECT b.name FROM names AS b WHERE b.customer_id = main_table.entity_id ORDER BY b.name DESC LIMIT 1)")));
$collection->addFieldToFilter("`main_table`.`group_id`", "1");

The resulting sql will be:

SELECT `main_table`.*, (SELECT b.name FROM names AS b WHERE b.customer_id = main_table.entity_id ORDER BY b.name DESC LIMIT 1) AS `names_name` 
FROM `sales_flat_order_grid` AS `main_table` 
WHERE (`main_table`.`group_id` = '1')

The only thing you need to be careful is to have your subselect between brackets, so your sql will remain valid.