Tag Archives: php

Creating a stateless request in Magento

Have you ever wanted to create a stateless request in Magento? Something that doesn’t touch any of Magento’s sessions?  We were having issues with some of the ajax calls on our cart and checkout pages mucking with the user’s cart and had get stateless on these calls.  The issue we were having was our checkout page was loading, then a javascript include was going out and bringing code from a 3rd party relevance engine into our dom, which was in turn calling back an ajax request to our servers.  This issue with this being that at the start of the page load, the checkout session was being set to a certain state.  This state was then being sent through the rest of the page load, and the ajax calls. Unfortunately, by the time the ajax call got back to our server, the session was different in both locations, creating a race condition.  The ajax request usually won, removing the work the full page load had done with trying to process checkout.  The good news was there was nothing in the ajax call that needed to touch the session, it was just some data lookup. So, nix the session part of that call, and our troubles should be over… Magento’s api controller is the only place that implements a stateless request this but its fairly easy to do (after a bit of digging).

As long as Mage_Core_Controller_Varien_Action is a parent in your controller’s hierchy, you are good to go (it probably is).  This class has a const FLAG_NO_START_SESSION which looks promising. Digging into the code a little we see that it controls whether cookies are processed or the session is started:

<?php
...
        if (!$this->getFlag('', self::FLAG_NO_START_SESSION)) {
            $checkCookie = in_array($this->getRequest()->getActionName(), $this->_cookieCheckActions);
            $checkCookie = $checkCookie && !$this->getRequest()->getParam('nocookie', false);
            $cookies = Mage::getSingleton('core/cookie')->get();
            if ($checkCookie && empty($cookies)) {
                $this->setFlag('', self::FLAG_NO_COOKIES_REDIRECT, true);
            }
            Mage::getSingleton('core/session', array('name' => $this->_sessionNamespace))->start();
        }

By adding to the preDispatch() method of our Action or Controller we can toggle this:

<?php
class Ai_AjaxCatalog_Controller_Action extends Mage_Core_Controller_Front_Action
{
        public function preDispatch()
        {
                $this->setFlag('', self::FLAG_NO_START_SESSION, 1); // Do not start standard session
                parent::preDispatch();
                return $this;
        }
}

Now, any action in this controller will be stateless and not effect any sessions.

Extending a Magento Controller

We’re ajaxing part of the Magento shopping cart so we need to modify/extend some of the cart controller functionality.  Sometimes when modifying controller’s you have to worry about updating the routes. For this, we don’t need to, we still want all the urls to be used the same way.

app/code/local/Ai/Checkout/etc/config.xml:

<config>
    <modules>
        <Ai_Checkout>
             <version>0.0.1</version>
        </Ai_Checkout>
    </modules>
...
    <frontend>
        <routers>
            <checkout>
                <use>standard</use>
                <args>
                    <module>Ai_Checkout</module>
                    <frontName>checkout</frontName>
                </args>
            </checkout>
        </routers>
    </frontend>
</config>

app/code/local/Ai/Checkout/controllers/CartController.php:

require_once Mage::getModuleDir('controllers', 'Mage_Checkout') . DS . 'CartController.php';

class Ai_Checkout_CartController extends Mage_Checkout_CartController
{
   public function updatePostAction()
    {
        Mage::log("NEW CONTROLLER", null, 'tim.log');
        try {

A note on Magento and multiple nodes using Memcached

If you have multiple nodes using a shared memcached server, make sure you define a shared prefix for the keys to use.

In local.xml:

        <cache>
...
            <prefix>a1i</prefix>
            <id_prefix>a1i</id_prefix>
            <memcached>
...

Want to output the full xml config Magento is running?

Mage::getConfig()->getNode()->asNiceXml('full_config.xml');

Where full_config.xml is the file you want it dumped to.

Want to trace the call stack in Magento?

This has helped me immensely in situations like “Where is this getting called from??!?”

Create a helper like so:

class Timbroder_Stack_Helper_Callstack extends Mage_Core_Helper_Abstract
{
        private function get_callstack($delim="\n") {
          $dt = debug_backtrace();
          $cs = '';
          foreach ($dt as $t) {
            $cs .= $t['file'] . ' line ' . $t['line'] . ' calls ' . $t['function'] . "()" . $delim;
          }

          return $cs;
        }

        public function toLog() {
                Mage::log($this->get_callstack());
        }

        public function toFirePhp() {
                $stack = $this->get_callstack();
                foreach (explode("\n", $stack) as $line) {
                        Mage::helper('firephp')->send($line);
                }
        }
}

That can be called from anywhere:

Mage::helper('stack/callstack')->toFirePhp();
Mage::helper('stack/callstack')->toLog();

I’ve also wrapped this into a module that you can drop right into your project. Details here: https://bitbucket.org/broderboy/magento_callstack/src

Example output:

.../app/code/community/Timbroder/Stack/Helper/Callstack.php line 16 calls get_callstack()
.../app/design/frontend/mongoose/default/template/catalog/cms/bikes_bmx.phtml line 12 calls toLog()
.../app/design/frontend/mongoose/default/template/catalog/cms/bikes.phtml line 21 calls require_once()
.../app/code/core/Mage/Core/Block/Template.php line 212 calls include()
.../app/code/core/Mage/Core/Block/Template.php line 239 calls fetchView()
.../app/code/core/Mage/Core/Block/Template.php line 253 calls renderView()
.../app/code/core/Mage/Core/Block/Abstract.php line 668 calls _toHtml()
.../app/code/core/Mage/Core/Model/Email/Template/Filter.php line 190 calls toHtml()
.../lib/Varien/Filter/Template.php line 134 calls call_user_func()
.../app/code/core/Mage/Core/Model/Email/Template/Filter.php line 501 calls filter()
.../app/code/core/Mage/Cms/Block/Page.php line 100 calls filter()
.../app/code/core/Mage/Core/Block/Abstract.php line 668 calls _toHtml()
.../app/code/core/Mage/Core/Block/Abstract.php line 513 calls toHtml()
.../app/code/core/Mage/Core/Block/Abstract.php line 460 calls _getChildHtml()
.../app/code/local/Mage/Page/Block/Html/Wrapper.php line 52 calls getChildHtml()
.../app/code/core/Mage/Core/Block/Abstract.php line 668 calls _toHtml()
.../app/code/core/Mage/Core/Block/Text/List.php line 43 calls toHtml()
.../app/code/core/Mage/Core/Block/Abstract.php line 668 calls _toHtml()
.../app/code/core/Mage/Core/Block/Abstract.php line 513 calls toHtml()
.../app/code/core/Mage/Core/Block/Abstract.php line 464 calls _getChildHtml()
.../app/design/frontend/mongoose/default/template/page/1column.phtml line 55 calls getChildHtml()
.../app/code/core/Mage/Core/Block/Template.php line 212 calls include()
.../app/code/core/Mage/Core/Block/Template.php line 239 calls fetchView()
.../app/code/core/Mage/Core/Block/Template.php line 253 calls renderView()
.../app/code/core/Mage/Core/Block/Abstract.php line 668 calls _toHtml()
.../app/code/core/Mage/Core/Model/Layout.php line 529 calls toHtml()
.../app/code/local/Mage/Core/Controller/Varien/Action.php line 389 calls getOutput()
.../app/code/core/Mage/Cms/Helper/Page.php line 130 calls renderLayout()
.../app/code/core/Mage/Cms/Helper/Page.php line 52 calls _renderPage()
.../app/code/core/Mage/Cms/controllers/PageController.php line 45 calls renderPage()
.../app/code/local/Mage/Core/Controller/Varien/Action.php line 418 calls viewAction()
.../app/code/core/Mage/Core/Controller/Varien/Router/Standard.php line 254 calls dispatch()
.../app/code/core/Mage/Core/Controller/Varien/Front.php line 177 calls match()
.../app/code/core/Mage/Core/Model/App.php line 304 calls dispatch()
.../app/Mage.php line 598 calls run()
.../index.php line 155 calls run()

Thanks to nextide for some of the code

Want to dump every SQL query Magento runs?

In: lib/Zend/DB/Adapter/Adapter.php

public function query($sql, $bind = array())
    {
        // connect to the database if needed
        $this->_connect();

        // is the $sql a Zend_Db_Select object?
        if ($sql instanceof Zend_Db_Select) {
            if (empty($bind)) {
                $bind = $sql->getBind();
            }

            $sql = $sql->assemble();
        }
        $time_start = microtime(true);

        // make sure $bind to an array;
        // don't use (array) typecasting because
        // because $bind may be a Zend_Db_Expr object
        if (!is_array($bind)) {
            $bind = array($bind);
        }

        // prepare and execute the statement with profiling
        $stmt = $this->prepare($sql);
        $stmt->execute($bind);

        // return the results embedded in the prepared statement object
        $stmt->setFetchMode($this->_fetchMode);

        $time_end = microtime(true);
        $time = $time_end - $time_start;

        echo "SQL[$time | $sql ]" . "\n<br />\n";
        return $stmt;
    }

Magento: Want CKEditor to appear on Category edit pages in Magento?

It is pretty common to replace the TinyMCE editor in the Magento Admin with the CKEditor using this extension

However, the way magento ajaxes the form fields into view on the category pages breaks this functionality.

This snippet should help anyone trying to get it to work

in app/design/adminhtml/default/default/template/fontis/wysiwyg/wysiwyg.phtml

<?php } else if($editorType == 'ckeditor') { ?>
    <script type="text/javascript" src="<?php echo $this->getJsUrl() ?>fontis/ckeditor/ckeditor.js"></script>
    <script type="text/javascript">
        var pageLoaded = false;

        function applyCKEditor() {
                if(pageLoaded) {
                    var editable_areas = '<?php echo $editableAreas ?>';
                    <?php if(strpos($this->helper('core/url')->getCurrentUrl(), 'catalog_category') != false): ?>
                        CKEDITOR.instances = {};
                    <?php endif; ?>
                    <?php /* Add CKeditor to any matching textareas. */ ?>
                    editable_areas.split(',').each(function(dom_id) {
                        if($(dom_id)) {
                            <?php /* Remove the required-entry CSS class so Magento will
                                     allow the contents of the editor to be submitted. */ ?>
                            var loopCheck = 0;
                            while($(dom_id).hasClassName('required-entry') && loopCheck < 10) {
                                $(dom_id).removeClassName('required-entry');
                                loopCheck += 1;
                            }

                            CKEDITOR.replace(dom_id, {
                                width : 640,
                                height: 350,
                                protectedSource : ['(/{{[\s\S]*?}}/g)']
                            });
                        }
                    });
                }
        }        

            window.onload = function() {
                pageLoaded = true;
                <?php if(strpos($this->helper('core/url')->getCurrentUrl(), 'catalog_category') === false): ?>
                applyCKEditor();
                <?php endif; ?>
            }
    </script>
<?php } ?>

in app/design/adminhtml/default/default/template/catalog/category/edit/form.phtml add this at the bottom but inside the script tag

if(typeof applyCKEditor == ‘function’) {
applyCKEditor();
}

Magento:: Enabling the Product Review form anywhere

the form code lives in app/design/frontend/yourtemplate/default/template/review/form.phtml

in catalog.xml enable it with:

<block type="review/form" name="product.info.review_form" as="review_form" template="review/form.phtml"/>

and in your template:

<?php echo $this->getChildHtml('review_form'); ?>
Performance Optimization WordPress Plugins by W3 EDGE