The Code007 shop has opened

It was a long time ago I have posted my last article, but this doesn’t mean that I’ve been on a long vacation in the meantime. In the last couple of months I was working on some Magento modules, which will be handy for many of you. These are already available in the Magento Marketplace.

Lately the Marketplace team has introduced a couple of quality measurements in the submission procedure of the Magento modules. This is indeed a very good idea, but there is a big drawback: they are testing each module manually. Lets not run that far away, let me write some details about the steps which needs to be passed in order to have a module in the Marketplace.

The first and most important step, to work hard and create your module. This can be achieved in a very short time if your module is simple or it can take much more if you are building a complex solution. The next step is to create a Marketplace account if you don’t have one. It is easy, just a few minutes and you are done with it. Now it is time to start creating your Marketplace product: add a new product… and then you get a simple form to complete your module’s name and a couple of description fields, where you will explain why is unique your module or why would someone buy it. Then your product is in “business review”. If you didn’t provide a long enough description, your product will be rejected and you can start over. But if you are lucky, then you’ll pass the business review in a couple of days and then you will be able to upload your module.

Shortly after you have uploaded your compressed module, an automated technical review will check your source code. A couple of months ago the warnings were also shown in the reports, but lately only the errors are included. These are really useful, because you will know in 5-10 minutes after uploading your module that it is at least syntactically correct, there are no huge mistakes.

In the meantime you can already fill in your product’s information: name, short and long description, upload images, logo, set a price, etc. Your product will pass the technical review even before finishing with these. You’ll see some optional fields as well, which could have been marked even as required. The document upload is marked as optional, but in reality it is a required field. It covers many document types (installation guide, user guide, etc.), all of them are optional, but at least one of them must be uploaded. Otherwise your product won’t pass the next step: the marketing review.

Some important things to keep in mind. Don’t forget to complete your developer profile, upload a profile image or a developer logo, because if your profile is not complete, then your first module will be rejected in the marketing review.

The marketing review is a slower process. I’m not sure how it works in the background, because sometimes it is passed in a couple of hours, other times it takes some days. But what I know is that at least partially the marketing review is done manually, by humans. I had many fights with them about my product logos. They have a basic idea about the ideal product logo, but I’m not sure if all the marketing review employees understand the same thing about it. It happened once that one of my products have passed the marketing review, but I had to upload a new version of the code with some bug-fixes. I didn’t change anything in the text fields or in the product images, so I expected an instant pass in the marketing review. Well, it didn’t pass. They didn’t like the logo, so I had to do some changes again on it. Sadly the feedback email, which you get after a rejection doesn’t always contain enough information about the problem. I’d insert some links which would open the edit product page and highlight somehow the problem, but instead of this, they are just summarizing the problems in a few words.

The time has come to pass the marketing review. You are happy that you have achieved something, you are almost there. Yes! It used to be like that a couple of months ago… And we are at that point where this article begins… One of the last steps is the quality assurance (QA) review. It takes very long. I’m not joking. I’ve uploaded my Shop Manager extension in February 15, and it was already February 18 when it has passed the marketing review. In that day they have started to QA it. In the mail I got the message that “The review process usually takes about 2 days”. Well… after a month and so I decided to ask them what happens. It has turned out that they have changed their terms and conditions from those “2 days” to “up to 90 days”. I remained shocked for a while. On March 27 I got the mail from the QA team that my product did not pass it. OK, it was my mistake that I didn’t test that one thing. I could reproduce the problem locally, created a fix for it and I thought that if my product was already QA reviewed, then it will have a priority. Nope. Now we wait… maybe it will pass or maybe not, but the amount of time you have to wait in the meantime… that is too long. Lets say that you have your module and you want to make it available in the Marketplace. It will take about 3 months or even more if you are not lucky.

Summa summarum, I decided to do an important step: to open my own webshop, to sell my modules for Magento 1 and 2. Most of the information about the modules are the same as in the Marketplace, the only difference is that if an important update will be released, then it will be done instantly and not after a couple of months. This is important from a security perspective. I’ll upload my modules also to Marketplace, just to prove that these are good, quality products.

And last, but not least, a quick summary about the currently available products.

Clear Cache is a handy tool for clearing cache quickly from the back-end. Currently it is available only for Magento 1. I recommend it to back-end developers, because it can clear your cache without navigating away from your current page. It is really useful when you work with AJAX requests.

Attribute Import-Export for Magento 1 and Attribute Import-Export for Magento 2 are modules, which can export and import attributes. Some product attributes can have a couple of hundreds of options, which is really difficult to maintain. In such situations you can export the attributes into a CSV file, then you can modify it and import it back. And if you have decided to take a big step and upgrade your webshop to Magento 2, you can import your attributes into Magento 2 even if the file is exported from your Magento 1 shop.

Shop Manager is my latest module for Magento 2. It is a graphical implementation of the Magento console application. Usually you must have SSH console access to your Magento 2 shop’s hosting machine in order to be able to manage Magento. But what happens if you – as a system administrator – don’t want to give console access to the site owner for different reasons (ex. in a shared hosting)? Then you can use this module and it will just work. Deploy static content, setup upgrade, reindex… piece of cake. Never have done it easier!

Some more toughts before I end this article. Magento Marketplace doesn’t have any kind of discounting system while purchasing modules. Another reason to open my shop. I’m planning to create in the near future bundles and different kind of black friday-like discounts.

That’s all folks!

Why model overriding does not work

I just met a case when overriding a module in Magento 1.x didn’t work. After some digging it has turned out that it will work only when the slashed notation of class naming is used. I won’t write down the steps to create a new extension, how to write a class or where to put files, because this article is about how to correctly override a model.

Example, when in the code it is used:

$validator = Mage:getModel("core/url_validator");

then you will be able to override it from your own module, by defining in config.xml:

<global>
    <modules>
        <core>
            <rewrite>
                <url_validator>Foo_Model_Bar_Checker</url_validator>
            </rewrite>
        </core>
    </modules>
</global>

However, if the code would use:

$validator = Mage::getModel("Mage_Core_Model_Url_Validator");

then it would be useless your efforts to override it. Why? The answer is quite obvious. Just take a look at Mage_Core_Model_Config:

public function getModelClassName($modelClass)
{
    $modelClass = trim($modelClass);
    if (strpos($modelClass, '/')===false) {
        return $modelClass;
    }
    return $this->getGroupedClassName('model', $modelClass);
}

When you are using the full class name notation, then it will just return it back. But when using Mage::getModel with slashes in the argument, then it will start parsing the config.xml files after possible rewrites.

It is up to you how you want to declare your models, maybe you want to make harder for others to override your code, then you can always specify the full class name. Or maybe you want your code to be faster by skipping a lot of config.xml parsing, that is also good. Otherwise just use the slashed notation.

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.