Coding

  • Coding,  Random

    Magento – Applying Security Patches and Fixing Conflicts

    As you are probably well aware, Magento has been releasing a steady stream of security patches recently. I’m going to give a quick overview of applying them, and also dealing with possible conflicts.

    Here are the official instructions for applying a patch. As you can see, they recommend running

    sh patch-file-name.sh

    However, as this Stack Overflow question illustrates, sometimes that won’t work. You might get something like

    sh PATCH_SUPEE-5344_CE_1.8.0.0_v1-2015-02-10-08-10-38.sh -R
    PATCH_SUPEE-5344_CE_1.8.0.0_v1-2015-02-10-08-10-38.sh: 14: PATCH_SUPEE-      5344_CE_1.8.0.0_v1-2015-02-10-08-10-38.sh: 127: not found
    PATCH_SUPEE-5344_CE_1.8.0.0_v1-2015-02-10-08-10-38.sh: 14: PATCH_SUPEE-5344_CE_1.8.0.0_v1-2015-02-10-08-10-38.sh: 127: not found
    PATCH_SUPEE-5344_CE_1.8.0.0_v1-2015-02-10-08-10-38.sh: 25: PATCH_SUPEE-5344_CE_1.8.0.0_v1-2015-02-10-08-10-38.sh: 0: not found
    Checking if patch can be applied/reverted successfully...
    Patch was applied/reverted successfully.

    The two most highly rated answers are correct – there are bash specific lines in the patch file, and the default system shell may not be bash. In Ubuntu 6.10+, they have changed /bin/sh to dash. To get around this, make the patch file executable, and then run the patch file directly, without sh. That is,

    chmod +x patch-file-name.sh
    ./patch-file-name.sh
    

    Change patch-file-name.sh to the actual name of your file.

    If all goes well, then you should see the following messages:

    $ ./patch-file-name.sh
    Checking if patch can be applied/reverted successfully...
    Patch was applied/reverted successfully.
    

    If it was not successful, you’ll see an exhaustive list what changes were applied, and which failed. Usually this happens when you have local changes of a file, or in one of our client’s cases, when Magento did not provide a patch specifically for their version. In that case, you might get something like this:

    $ ./PATCH_SUPEE-5994_CE_1.4.1.0_v1-2015-05-15-04-33-58.sh
    Checking if patch can be applied/reverted successfully...
    ERROR: Patch can't be applied/reverted successfully.
    
    patching file app/code/core/Mage/Core/Controller/Varien/Router/Admin.php
    Hunk #1 succeeded at 93 with fuzz 2 (offset -2 lines).
    patching file app/code/core/Mage/Core/Controller/Varien/Router/Standard.php
    Hunk #1 succeeded at 206 (offset 1 line).
    Hunk #2 succeeded at 271 (offset -8 lines).
    Hunk #3 succeeded at 307 (offset -8 lines).
    patching file app/code/core/Mage/Customer/Model/Customer.php
    patching file app/code/core/Mage/Dataflow/Model/Convert/Parser/Csv.php
    patching file app/code/core/Mage/Install/Controller/Router/Install.php
    patching file app/code/core/Mage/Install/etc/config.xml
    can't find file to patch at input line 185
    Perhaps you used the wrong -p or --strip option?
    The text leading up to this was:
    --------------------------
    |diff --git app/code/core/Mage/Sales/controllers/Recurring/ProfileController.php app/code/core/Mage/Sales/controllers/Recurring/ProfileController.php
    |index 95c66dc..6f5ed5d 100644
    |--- app/code/core/Mage/Sales/controllers/Recurring/ProfileController.php
    |+++ app/code/core/Mage/Sales/controllers/Recurring/ProfileController.php
    --------------------------
    File to patch:
    Skip this patch? [y]
    Skipping patch.
    1 out of 1 hunk ignored
    patching file lib/PEAR/PEAR/PEAR.php
    patching file lib/PEAR/PEAR/PEAR5.php
    patching file lib/Varien/Io/File.php
    Hunk #1 succeeded at 224 (offset -2 lines).
    

    In this case, look for the line around

    app/code/core/Mage/Sales/controllers/Recurring/ProfileController.php. For this example, that file didn’t exist in the client’s version, so I edited the .sh file and removed the entire block, starting from diff --git app/code/core/Mage/Sales/controllers/Recurring/ProfileController.php app/code/core/Mage/Sales to the line just before the next diff.

    If it’s a specific hunk that’s misbehaving, you can apply the change manually and then remove the hunk from the .sh file. It should be fairly easy to tell – lines starting with a + are new lines, lines with - are removed, and lines with neither stay the same. Look for the lines with neither +/- as a reference point, and add/remove lines accordingly. To remove the hunk, remove the hunk starting with @@ to just before the next line starting with @@ For example, in the below snippet, if I wanted to remove hunk #2, I would remove lines 16-33.

    diff --git app/code/core/Mage/Core/Controller/Varien/Router/Standard.php app/code/core/Mage/Core/Controller/Varien/Router/Standard.php
    index efaf186..42c2fe6 100644
    --- app/code/core/Mage/Core/Controller/Varien/Router/Standard.php
    +++ app/code/core/Mage/Core/Controller/Varien/Router/Standard.php
    @@ -205,6 +205,10 @@ class Mage_Core_Controller_Varien_Router_Standard extends Mage_Core_Controller_V
                 // instantiate controller class
                 $controllerInstance = Mage::getControllerInstance($controllerClassName, $request, $front->getResponse());
     
    +            if (!$this->_validateControllerInstance($controllerInstance)) {
    +                continue;
    +            }
    +
                 if (!$controllerInstance->hasAction($action)) {
                     continue;
                 }
    @@ -275,6 +279,17 @@ class Mage_Core_Controller_Varien_Router_Standard extends Mage_Core_Controller_V
         }
     
         /**
    +     * Check if current controller instance is allowed in current router.
    +     * 
    +     * @param Mage_Core_Controller_Varien_Action $controllerInstance
    +     * @return boolean
    +     */
    +    protected function _validateControllerInstance($controllerInstance)
    +    {
    +        return $controllerInstance instanceof Mage_Core_Controller_Front_Action;
    +    }
    +
    +    /**
          * Generating and validating class file name,
          * class and if evrything ok do include if needed and return of class name
          *
    @@ -300,7 +315,6 @@ class Mage_Core_Controller_Varien_Router_Standard extends Mage_Core_Controller_V
             return $controllerClassName;
         }
     
    -
         /**
          * @deprecated
          * @see _includeControllerClass()
    

    Finally, after you’ve run the patch script successfully, you might need to reset the owner/group of your files back to your web server user – otherwise the patched files will belong to the user you’re running the .sh file as. Quoting from the official docs,

    a. Find the web server user: ps -o "user group command" -C httpd,apache2
    The value in the USER column is the web server user name.
    Typically, the Apache web server user on CentOS is apache and the Apache web server user on Ubuntu is www-data.
    b. As a user with root privileges, enter the following command from the Magento installation directory:

    chown -R web-server-user-name .

    For example, on Ubuntu where Apache usually runs as www-data, enter

    chown -R www-data .

    Good luck in your patch adventures!

  • Coding

    Getting the SQL Query for Magento Fulltext Search

    Short version: Magento searches through the catalogsearch_fulltext table. Reindex the Catalog Search Index if the data in there seems to be incorrect.

    Long version: Ever searched for a keyword of Magento and wondered, “why did this product show up under this keyword search?” If you go to the catalog/product/list.phtml and echoed out the SQL query for the Product Collection (via $_productCollection->getSelectSql(true)), you’ll get a useless SQL query that looks something like this:

    SELECT 
    	1 AS `status`, <....> 
    FROM `catalog_product_flat_1` AS `e`
    	INNER JOIN `catalogsearch_result` AS `search_result` ON 
    		search_result.product_id=e.entity_id AND 
    		search_result.query_id='19091'
    	INNER JOIN `catalog_product_index_price` AS `price_index` ON 
    		price_index.entity_id = e.entity_id AND 
    		price_index.website_id = '1' AND 
    		price_index.customer_group_id = '1'
    	INNER JOIN `catalog_category_product_index` AS `cat_index` ON 
    		cat_index.product_id=e.entity_id AND 
    		cat_index.store_id='1' AND 
    		cat_index.visibility IN(3, 4) AND 
    		cat_index.category_id='2' 
    ORDER BY `position` asc, `e`.`name` asc LIMIT 50

    So, you can see that query results are stored in the catalogsearch_result table, but how is that table built in the first place?

    If you go to Mage_CatalogSearch_Model_Resource_Fulltext (or Mage_CatalogSearch_Model_Mysql4_Fulltext if you’re using an older version of Magento), and scroll down to the prepareResult() function, you can see it building the search query if that keyword hasn’t been searched before (if (!$query->getIsProcessed())). To get the query itself, you can add Mage::log($sql); Mage::log($bind); just before $adapter->query($sql, $bind);. Remember, this will only fire on a keyword that hasn’t been searched before, unless you add a || true on the getIsProcessed() line.

    The log will show something like this:

    INSERT INTO `catalogsearch_result` 
    	(SELECT STRAIGHT_JOIN '19091', 
    		`s`.`product_id`, 
    		MATCH (`s`.`data_index`) AGAINST (:query IN BOOLEAN MODE), 
    		c.position 
    	FROM 
    		`catalog_category_product` as `c`,  
    		`catalogsearch_fulltext` AS `s` INNER JOIN `catalog_product_entity` AS `e` ON 
    			`e`.`entity_id`=`s`.`product_id` 
    	WHERE 
    		((`s`.`data_index` LIKE :likew0)) AND 
    		`s`.`store_id`='1' and 
    		s.product_id = c.product_id) 
    ON DUPLICATE KEY UPDATE `relevance`=VALUES(`relevance`)
    

    So, you can see that the query checks the catalogsearch_fulltext table. For each product id, it stores a concatenated string of all the data that’s been indexed. It’ll look something like this:

    fulltext_id	product_id	store_id	data_index
    4		1		1		123456789|Taxable Goods|Laptop|Laptop Description|Laptop Description|1200|1
    

    To control which data fields are indexed in this table, go to the Magento Admin and go to Catalog > Attributes > Manage Attribute. Select an attribute, and then scroll down to Frontend Properties. Toggle the “Use in Quick Search” settings. Then, reindex the Catalog Search Index if necessary.

  • Coding

    Magento Community Edition to Enterprise Edition Version Mapping

    Magento Enterprise Edition (EE) is essentially an extension of the Community Edition (CE). Enterprise Edition is a superset of the features of the Community Edition. That is, there no features in Community Edition that aren’t also in Enterprise Edition.

    You can see that when you look at the folder tree.

    Magento Enterprise Edition Folder Tree
    The core Magento files are located in app/code/core/Mage, while all Enterprise related features are in app/code/core/Enterprise.

    As such, you can roughly determine the corresponding CE edition based on your version of EE. Here’s a mapping, based on the release dates of each. It doesn’t cover the minor revisions in between, just major versions. I will attempt to keep this updated as new versions come out.

    Community Edition Enterprise Edition
    Magento CE 1.9 (5/13/14) Magento EE 1.14 (5/13/14)
    Magento CE 1.8 (12/11/13) Magento EE 1.13 (10/17/13)
    Magento CE 1.7 (4/24/12) Magento EE 1.12 (4/24/12)
    Magento CE 1.6 (8/18/11) Magento EE 1.11 (8/18/11)
    Magento CE 1.5 (2/8/11) Magento EE 1.10 (2/8/11)
    Magento CE 1.4 (2/12/10) Magento EE 1.9 (7/19/10)
    Magento EE 1.8 (4/14/10)
    Magento EE 1.7 (1/19/10)
    Magento CE 1.3 (3/30/09) Magento EE 1.6 (10/30/09)

    As you can see, there is some overlap between the base Magento versions of EE 1.7-1.9, most likely because the Magento team were focusing their efforts on developing Enterprise Edition. The next parallel release was Magento CE 1.5 and EE 1.10, and they were kept pretty much in sync since then.

    I found such a mapping helpful when I was looking for bug fixes for the core Magento code. I could say, I’m running EE 1.x, and there’s a bug in the app/code/core/Mage directory, so I can find the information about the bug in the corresponding CE edition.

    Sources:
    Magento Enterprise and Magento Community – The Key Differences
    Infographic – Magento History and Evolution

  • Coding

    Block types in Magento

    Updated 3/25/11: TLDR; If you just want to make a block that displays the contents of a phtml file, use the block type core/template, like so

    
    

    Then you can use $this->getChildHtml(‘logos’) in the relevant parent block to display it.

    Original Post:
    Block types are used in the layout XML files, ex,

    Per this post, block types conform to the format “group/specific_class.”

    For example Mage_Core module declares “core” group, which introduces blocks such as:
    * “core/text” – simple block type to show text strings
    * “core/template” – made for rendering templates into html

    Each block type refers to specific class:
    * “core/text” – Mage_Core_Block_Text
    * “core/template” – Mage_Core_Block_Template

    So, how it works that defining the block type essentially modifies the object you get with $this, which affects the data / methods you can use in that phtml template. Here’s a list of the more useful ones:

    • core/template – very simple, inserts the pthml file specified in the template attribute. Example, <block type=”core/template” template=”page.phtml”>
  • Coding

    Deleting orders in Magento

    Using this blog as reference guide – perhaps slightly easier than bookmarking topics, and in case the internet eats the posts.

    Per this thread, to delete an order, do the following:

    SET @orderId = '100000001';
    
    SET FOREIGN_KEY_CHECKS = 1;
    
    DELETE FROM sales_flat_order
        WHERE increment_id = @orderId;
        
    DELETE FROM sales_flat_quote
        WHERE reserved_order_id = @orderId; 
    

    This works in Magento Community v 1.4.1.0 and Magento Enterprise 1.8.0.0. It deleted all mention of the order from the dashboard/reports.