Tag Archives: magento

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?

Magento: How to create a timestamp table column with CURRENT_TIMESTAMP as default value?

Well, the easiest answer would be to use raw queries to create such tables. But that is not the Magento style. We will use the Magento style to build our table.

So, I suppose that you already created your install / upgrade script in your module. Now you have something like:

$this->startSetup();

//...

$this->endSetup();

In many cases you will meet with $installer = $this; and  with $installer->startSetup(), both are the same thing (it was created like this a long time ago, when such a notation influenced the autocomplete of the IDEs). The create table statement will be between the startSetup and the endSetup like:

$table = $this->getConnection()->newTable("your_table_definition")
->addColumn("column_name", Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array(
"default" => "SOMETHING_DEFAULT"
), "some comment here")
->addColumn(...)
...
;
$this->getConnection()->createTable($table);

The question is what to put instead of SOMETHING_DEFAULT? Use Varien_Db_Ddl_Table::TIMESTAMP_INIT, so your addColumn definition will look like:

->addColumn("column_name", Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array(
"default" => Varien_Db_Ddl_Table::TIMESTAMP_INIT
), "some comment here")

That’s it.

How to show the default checkbox near a Magento attribute

It is annoying that Magento doesn’t have a much easier way to insert this thing. What am I talking about? See this screenshot:

Default checkbox in product pages

Well, this is quite an ugly solution, but maybe it will work for you. First of all, on product pages there is a custom renderer for each element, that is why it is shown there. So, if you have the following element:

$name = $fieldset->addField('name', 'text', array(
    'name' => 'name',
    'required' => true,
    'class' => 'required-entry',
    'label' => Mage::helper('some_helper')->__('Name'),
));

you will have to render it with a custom renderer:

if ($name)
{
    $name->setRenderer(
        $this->getLayout()->createBlock('adminhtml/catalog_form_renderer_fieldset_element')
    );
}

At this point you should have the third column with the scope-label class. But the checkbox near it still won’t show up. For that we have to set the following for the form:

$storeObj = new Varien_Object();
$storeId = $this->getRequest()->getParam("store");
$storeObj->setId($storeId);
$storeObj->setStoreId($storeId);
$form->setDataObject($storeObj);

Now you should see also the checkbox. That’s all folks!

How to remove a tab from the Customer or Product page in the backend

I’ve been looking for this in google or stackoverflow for a couple of hours, before I found out how things work. We are talking about the backend. Lets begin with the Product tabs, because it is easier.

1. Removing Product tabs
a) Open your layout xml file (default: catalog.xml)
b) Search for <adminhtml_catalog_product_edit>
c) Add removeTab action Inside the “product_tabs” block

<reference name="left">
   <block type="adminhtml/catalog_product_edit_tabs" name="product_tabs">
      <action method="removeTab">
          <name>NAME_OF_TAB</name>
      </action>
   </block>
</reference>

You can find out the NAME_OF_TAB, by inspecting the tab’s anchor (<a>) and looking for the “name” attribute.

2. Removing Customer tabs
a) You have to override Mage_Adminhtml_Block_Customer_Edit_Tabs because the Magento guys did a small typo there: they are adding tabs in _beforeToHtml() method instead of _prepareLayout(). So first you have to modify your config.xml and add:

<global>
    <blocks>
        <adminhtml>
            <rewrite>
                <customer_edit_tabs>Yourmodule_Customer_Block_Edit_Tabs</customer_edit_tabs>
            </rewrite>
        </adminhtml>
    </blocks>
</global>

In Yourmodule_Customer_Block_Edit_Tabs just copy and paste the Mage_Adminhtml_Block_Customer_Edit_Tabs contents (don’t forget to change the class name!), and rename _beforeToHtml() method to _prepareLayout()

b) Add the removeTab action into your layout xml (default: customer.xml):

<adminhtml_customer_edit>
    <reference name="left">
        <block type="adminhtml/customer_edit_tabs" name="customer_edit_tabs">
            <action method="removeTab">
                <name>NAME_OF_TAB</name>
            </action>
        </block>
    </reference>
</adminhtml_customer_edit>

Finding out the NAME_OF_TAB is similar as for the products (see step 1).

That’s all folks!

Magento: eav attribute frontend input types

Sometimes you are looking for a specific frontend input types of the eav attributes, but you don’t have a full list. You can find the types in lib/Varien/Data/Form/Element. Here they are:

  • Button
  • Checkbox
  • Checkboxes
  • Collection
  • Column
  • Date
  • Editor
  • Fieldset
  • File
  • Gallery
  • Hidden
  • Image
  • Imagefile
  • Label
  • Link
  • Multiline
  • Multiselect
  • Note
  • Obscure
  • Password
  • Radio
  • Radios
  • Reset
  • Select
  • Submit
  • Text
  • Textarea
  • Time