January 12, 2010

Subscriptions and Recurring Payments – new version and new user guide!

Today we are ready to announce 2 pieces of good news about the Subscriptions and Recurring Payments extension!
The first one is that User Guide is available to download from the extension page. The guide will make the work with Subscriptions and Recurring Payments much easier. In addition to covering all moments of the extension functioning – module configuration, managing subscriptions, subscribers and subscription products, etc. – you will find out how to manage subscriptions for customer’s part.
The next important point is the release of new version. The extension works more stable now as the following bugs were fixed:
  • It’s available to add product to cart from grid view even if product requires subscription options
  • Magento clears quotes after N days and subscription losts billing and shipping addresses
  • ePay 0.00 transaction payment fee is displayed on order page for recurring orders
  • Subscribers list exporting error
You can find the complete changelog and download the User Guide from the extension page.

January 8, 2010

Add a tree like left menu in Magento.

Magento in common has no left menu.
So i edit some core and design code to implement a left tree like menu.
Here i give the example of my code.
Step 1:Edit core code in app/code/core/mage/catalog/Block/Navigation.php and add these code in LeftNav.php in same place


<?php
/**
 * Magento
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@magentocommerce.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade Magento to newer
 * versions in the future. If you wish to customize Magento for your
 * needs please refer to http://www.magentocommerce.com for more information.
 *
 * @category   Mage
 * @package    Mage_Catalog
 * @copyright  Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com)
 * @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */

/**
 * Catalog navigation
 *
 * @category   Mage
 * @package    Mage_Catalog
 * @author      Magento Core Team <core@magentocommerce.com>
 */
class Mage_Catalog_Block_LeftNav extends Mage_Core_Block_Template
{
    protected $_categoryInstance = null;

    protected function _construct()
    {
        $this->addData(array(
            'cache_lifetime'    => false,
            'cache_tags'        => array(Mage_Catalog_Model_Category::CACHE_TAG, Mage_Core_Model_Store_Group::CACHE_TAG),
        ));
    }

    /**
     * Retrieve Key for caching block content
     *
     * @return string
     */
    public function getCacheKey()
    {
        return 'CATALOG_NAVIGATION_' . Mage::app()->getStore()->getId()
            . '_' . Mage::getDesign()->getPackageName()
            . '_' . Mage::getDesign()->getTheme('template')
            . '_' . Mage::getSingleton('customer/session')->getCustomerGroupId()
            . '_' . md5($this->getTemplate() . $this->getCurrenCategoryKey());
    }

    public function getCurrenCategoryKey()
    {
        if ($category = Mage::registry('current_category')) {
            return $category->getPath();
        } else {
            return Mage::app()->getStore()->getRootCategoryId();
        }
    }

    /**
     * Get catagories of current store
     *
     * @return Varien_Data_Tree_Node_Collection
     */
    public function getStoreCategories()
    {
        $helper = Mage::helper('catalog/category');
        return $helper->getStoreCategories();
    }

    /**
     * Retrieve child categories of current category
     *
     * @return Varien_Data_Tree_Node_Collection
     */
    public function getCurrentChildCategories()
    {
        $layer = Mage::getSingleton('catalog/layer');
        $category   = $layer->getCurrentCategory();
        /* @var $category Mage_Catalog_Model_Category */
        $categories = $category->getChildrenCategories();
        $productCollection = Mage::getResourceModel('catalog/product_collection');
        $layer->prepareProductCollection($productCollection);
        $productCollection->addCountToCategories($categories);
        return $categories;
    }

    /**
     * Checkin activity of category
     *
     * @param   Varien_Object $category
     * @return  bool
     */
    public function isCategoryActive($category)
    {
        if ($this->getCurrentCategory()) {
            return in_array($category->getId(), $this->getCurrentCategory()->getPathIds());
        }
        return false;
    }

    protected function _getCategoryInstance()
    {
        if (is_null($this->_categoryInstance)) {
            $this->_categoryInstance = Mage::getModel('catalog/category');
        }
        return $this->_categoryInstance;
    }

    /**
     * Get url for category data
     *
     * @param Mage_Catalog_Model_Category $category
     * @return string
     */
    public function getCategoryUrl($category)
    {
        if ($category instanceof Mage_Catalog_Model_Category) {
            $url = $category->getUrl();
        } else {
            $url = $this->_getCategoryInstance()
                ->setData($category->getData())
                ->getUrl();
        }

        return $url;
    }

    /**
     * Enter description here...
     *
     * @param Mage_Catalog_Model_Category $category
     * @param int $level
     * @param boolean $last
     * @return string
     */
    public function drawItem($category, $level=0, $last=false)
    {
        $html = '';
        if (!$category->getIsActive()) {
            return $html;
        }
        if (Mage::helper('catalog/category_flat')->isEnabled()) {
            $children = $category->getChildrenNodes();
            $childrenCount = count($children);
        } else {
            $children = $category->getChildren();
            $childrenCount = $children->count();
        }
        $hasChildren = $children && $childrenCount;
        $html.= '<li';
        if ($hasChildren) {
             //$html.= ' onmouseover="toggleMenu(this,1)" onmouseout="toggleMenu(this,0)"';
   //replace
   $html.= '';
        }

       // $html.= ' class="level'.$level;
       // $html.= ' nav-'.str_replace('/', '-', Mage::helper('catalog/category')->getCategoryUrlPath($category->getRequestPath()));
        if ($this->isCategoryActive($category)) {
           // $html.= ' active';
      $html .='';
        }
        if ($last) {
           // $html .= ' last';
      $html .='';
        }
        if ($hasChildren) {
            $cnt = 0;
            foreach ($children as $child) {
                if ($child->getIsActive()) {
                    $cnt++;
                }
            }
            if ($cnt > 0) {
              //  $html .= ' parent';
      //$html .=' current';
      $html .='';
            }
        }
        $html.= '>'."\n";
        $html.= '<a href="'.$this->getCategoryUrl($category).'"><span>'.$this->htmlEscape($category->getName()).'</span></a>'."\n";

        if ($hasChildren){

            $j = 0;
            $htmlChildren = '';
            foreach ($children as $child) {
                if ($child->getIsActive()) {
                    $htmlChildren.= $this->drawItem($child, $level+1, ++$j >= $cnt);
                }
            }

            if (!empty($htmlChildren)) {
                $html.= '<ul>'."\n"
                        .$htmlChildren
                        .'</ul>';
            }

        }
        $html.= '</li>'."\n";
        return $html;
    }

    /**
     * Enter description here...
     *
     * @return Mage_Catalog_Model_Category
     */
    public function getCurrentCategory()
    {
        if (Mage::getSingleton('catalog/layer')) {
            return Mage::getSingleton('catalog/layer')->getCurrentCategory();
        }
        return false;
    }

    /**
     * Enter description here...
     *
     * @return string
     */
    public function getCurrentCategoryPath()
    {
        if ($this->getCurrentCategory()) {
            return explode(',', $this->getCurrentCategory()->getPathInStore());
        }
        return array();
    }

    /**
     * Enter description here...
     *
     * @param Mage_Catalog_Model_Category $category
     * @return string
     */
    public function drawOpenCategoryItem($category) {
        $html = '';
        if (!$category->getIsActive()) {
            return $html;
        }

        $html.= '<li';

        if ($this->isCategoryActive($category)) {
            $html.= ' class="active"';
        }

        $html.= '>'."\n";
        $html.= '<a href="'.$this->getCategoryUrl($category).'"><span>'.$this->htmlEscape($category->getName()).'</span></a>'."\n";

        if (in_array($category->getId(), $this->getCurrentCategoryPath())){
            $children = $category->getChildren();
            $hasChildren = $children && $children->count();

            if ($hasChildren) {
                $htmlChildren = '';
                foreach ($children as $child) {
                    $htmlChildren.= $this->drawOpenCategoryItem($child);
                }

                if (!empty($htmlChildren)) {
                    $html.= '<ul>'."\n"
                            .$htmlChildren
                            .'</ul>';
                }
            }
        }
        $html.= '</li>'."\n";
        return $html;
    }

}

add categories with images on homepage – magento

to add categories along with images on homepage
just add the code given below to your homepage from admininstration cms management.
{{block type="catalog/navigation" name="catalog.category" template="catalog/category/list.phtml"}}
Also you need to create a list.phtml file under “/app/design/frontend/default/default/template/catalog/category/list.phtml”
and add the below given code to it:
===========================================
<?php foreach ($this->getStoreCategories() as $_category): ?>
<?php $open
= $this->isCategoryActive($_category); ?>
<?php
$cur_category
=Mage::getModel('catalog/category')->load($_category->getId());
$layer = Mage::getSingleton('catalog/layer');
$layer->setCurrentCategory($cur_category);
if (
$immagine = $this->getCurrentCategory()->getImageUrl()):
?> <div style="float: left; padding-right: 30px; text-align: center;">
<
div class="linkimage">
<
p>
<
a href="<?php echo $this->getCategoryUrl($_category)?>">
<
img src="<?php echo $immagine ?>" alt="<?php echo $this->htmlEscape($this->getCurrentCategory()->getName()) ?>" width="135" height="135" />
<?php echo $_category->getName()?>
</a>
</
p>
</
div>
</
div> <?php endif; ?>
<?php
endforeach; ?>
================================================

January 5, 2010

Manual creation of Google Sitemap in Magento

Most of you probably know this, but let’s define what the sitemaps are. Sitemaps are a simple way for site creators to give search engines the information about pages on their site that are available for public. Basically, those are just 1 or more XML files that lists URLs for a site along with additional meta informatio about each URL (like last update, how often it changes, its importance relevant to other pages). With this in mind, search engines can be more clever while crawling the site. Click here see how sitemap of this site looks like.

You will hear the term Google Sitemap a lot, but XML Schema for the Sitemap protocol is not related only to Google at all. It can be used in many places, but the most important ones are Google Webmaster Tools and Yahoo! Site Explorer.

How do we create Google Sitemap in Magento?

1. Sitemap File Configuration

First we need to create sitemap file in Magento. This is basically pointer to tell Magento where to create sitemap file and how to name it. Log in to Magento Administration and go to:
Catalog -> Google Sitemap
Check to see if the sitemap file is already present. Lets assume we wish to create sitemap in the URL: http://www.yourstore.com/sitemap/sitemap.xml
To do it successfully,
  1. FTP to the server
  2. create sitemap folder
  3. chmod the folder to 777
On some servers, previous steps will not be required, but it is better to create the file first so that Magento can edit it.
Let’s go to Magento administration now and click on “Add Sitemap” button. Just insert the default values.
After this step, we just told Magento where to create sitemap file. It is not yet created.

2. Configure Sitemap

Sitemap can be configured from the interface System -> Configuration -> Catalog->Google Sitemap. If you are not sure what does it all mean, check XML Tag definitions section from “Sitemaps XML format” document.

3. Create Sitemap Manually

Looks like Magento folks were thinking that sitemap should only be created by a cron job. Yes, it can be done that way also. Be sure to read Wiki article How to Set Up a Cron Job to get familiar with it. If you are looking for manual creation, quit your search for “Create Sitemap” button. You will not find it. But there is a workaround. You can enter manual URL to execute sitemap creation:
http://www.yourstore.com/index.php/admin/sitemap/generate/sitemap_id/[ID of Sitemap from step 1]
Of course, replace the content with square brackets with actual Sitemap ID.

January 2, 2010

Programmaticaly adding new customers to the Magento store

Every now and then you will have specific case in Magento store where you might need to programmaticaly add new customers. Adding customer with basic information like First name, Last name, email and password is relatively trivial task. Here is the sample (working code):
<?php
$websiteId = Mage::app()->getWebsite()->getId();
$store = Mage::app()->getStore();
 
//$customer = new Mage_Customer_Model_Customer();
$customer = Mage::getModel("customer/customer");
$customer->website_id = $websiteId; 
$customer->setStore($store);
 
$customer->firstname = "Branko";
$customer->lastname = "Ajzele";
$customer->email = "ajzele@someserver.com";
$customer->password_hash = md5("mycoolpass");
$customer->save();
?>
Here are some screenshots of the final result:
customer1
customer2
customer3
Ok, so now we now how to add new customer from our code. But this is bare minimum. Where are the addresses you say? Well, below is the code that fulfills the above one and ads an address information to the customer.
<?php 
 
$websiteId = Mage::app()->getWebsite()->getId();
$store = Mage::app()->getStore();
 
//$customer = new Mage_Customer_Model_Customer();
$customer = Mage::getModel("customer/customer");
$customer->website_id = $websiteId; 
$customer->setStore($store);
 
$customer->firstname = "Branko";
$customer->lastname = "Ajzele";
$customer->email = "ajzele@someserver.com";
$customer->password_hash = md5("mycoolpass");
$customer->save();
 
//$address = new Mage_Customer_Model_Address();
$address = Mage::getModel("customer/address");
$address->setCustomerId($customer->getId());
$address->firstname = $customer->firstname;
$address->lastname = $customer->lastname;
$address->country_id = "HR"; //Country code here
$address->postcode = "31000";
$address->city = "Osijek";
/* NOTE: If country is USA, please set up $address->region also */
$address->telephone = "0038531444888";
$address->fax = "0038531555999";
$address->company = "ActiveCodeline";
$address->street = "My Cool Street";
 
$address->save();
 
?>
And below is the screenshot that reflex the state after executing code above.
customer6
As you can see, adding a new customer from within a code is pretty straightforward process. The thing you should keep an eye on are the required fields. Useful way achieving a proper result is to try to add the customer from within Magento admin interface with all the required address fields then do a little “reverse engineering” by loading and dumping the loaded instance of Mage_Customer_Model_Customer and Mage_Customer_Model_Address objects. Examining the dumped ($object->debug()) structure would give you a pretty good idea of what needs to be provided to object when it is created programmaticaly.
Hope I don’t have to mention that all of this “playing” should first be done on developer machine, never on live site.
Hope this was helpful. Cheers.

How to delete Magento product from frontend template code

f for some reason (like mine) you need to delete the product from the custom code you placed in lets say view files you might get a bit surprised by the “Cannot complete this operation from non-admin area.” error. Here is a little trick on how to pass behind this.
Here is the working sample code part:

$_authors_product = new Mage_Catalog_Model_Product();
 $_authors_product->load($_item_val_id);
 
 //echo "DELETED... ".$_POST['submit_item4sale_remove_by_entity_id'];
 $_item_val_id = $_POST['submit_item4sale_remove_by_entity_id']; 
 $_item_val_id = (int)str_replace('entity_id_', '', $_item_val_id);
 
 $_authors_product = new Mage_Catalog_Model_Product();
 $_authors_product->load($_item_val_id);
 
 $_current_customer_id = Mage::getSingleton('customer/session')->getCustomer()->getId();
 
 //Allow deletion only if product is from author
 if($_authors_product->submited_by_author == $_current_customer_id) 
 {
  //var_dump(Mage::registry('isSecureArea'));
 
  Mage::register('isSecureArea', true);
  //$_authors_product->delete();
  echo 'ALLOWED DELETED OH YEAAAAAA....';
  Mage::unregister('isSecureArea'); 
 }
 
 
The above code is part of a code extracted form a project I am working on these days. The important stuff is the following part:

Mage::register('isSecureArea', true);
echo 'ALLOWED DELETED OH YEAAAAAA....';
Mage::unregister('isSecureArea');
 
 
Basically the idea is to write the appropriate value in registry, Mage::register(‘isSecureArea’, true);, and remove it once we delete our product.

Magento – Custom email contact form with notification system

In this article, hopefully, you will learn how to create a module that uses its own controller, programatically creates a block based on core template and assigned via file, handles form submission and utilizes Magento notification system to output the notifications to user.
Although the module it self might look relatively simple in the end, keep in mind that these are powerful concepts that you can latter reuse for much more complex requirements.
file 1:
/app/etc/modules/ActiveCodeline_SimpleContact.xml
content of file 1:
<?xml version="1.0"?>
 
<config>
    <modules>
        <ActiveCodeline_SimpleContact>
            <active>true</active>
            <codePool>local</codePool>
        </ActiveCodeline_SimpleContact>
    </modules>    
</config>
file 2: app/code/local/ActiveCodeline/SimpleContact/etc/config.xml
content of file 2:
<?xml version="1.0"?>
 
<config>
    <modules>
        <ActiveCodeline_SimpleContact>
            <version>0.1.0</version>
        </ActiveCodeline_SimpleContact>
    </modules>   
 
    <frontend>
        <routers>
            <JustSomeFreeRouterNameHereNo1>
                <use>standard</use>
                <args>
                    <module>ActiveCodeline_SimpleContact</module>
                    <frontName>activecodeline-simplecontact</frontName>
                </args>
            </JustSomeFreeRouterNameHereNo1>
        </routers>
    </frontend>    
</config>
As we dissect our module, the first thing that pops up is the “frontend” element. We can see it has lot of sub-elements of which “routers” is first. In order for something in Magento to be accessible on certain url, that something needs to have controller, like all Zend powered applications. Unlike pure Zend apps, Magento has its own way of mapping controllers, trough xml definitions.
I intentionally used “JustSomeFreeRouterNameHereNo1″ for element name making it self explanatory. You can freely assign name to a router wheres “use” and “args” are two parameters each router should have. Parametar “module” if the full name of your module and “frontName” is the actual url path trough which you acccess your controller. In example above I would access my controller indexAction() method trough url like http://shop.local/index.php/activecodeline-simplecontact/index/ or http://shop.local/index.php/activecodeline-simplecontact/. In case we have url rewrite set up we can even access it by omitting the “index.php” part from url.
Now we will look into the content of IndexController.php.
file 3: app/code/local/ActiveCodeline/SimpleContact/controllers/IndexController.php
content of file 3:
<?php
 
class ActiveCodeline_SimpleContact_IndexController extends Mage_Core_Controller_Front_Action
{
    public function indexAction()
    {
        //Get current layout state
        $this->loadLayout();   
 
        $block = $this->getLayout()->createBlock(
            'Mage_Core_Block_Template',
            'activecodeline.simple_contact',
            array(
                'template' => 'activecodeline/simple_contact.phtml'
            )
        );
 
        $this->getLayout()->getBlock('content')->append($block);
        //$this->getLayout()->getBlock('right')->insert($block, 'catalog.compare.sidebar', true);
 
        $this->_initLayoutMessages('core/session');
 
        $this->renderLayout();
    }
 
    public function sendemailAction()
    {
        //Fetch submited params
        $params = $this->getRequest()->getParams();
 
        $mail = new Zend_Mail();
        $mail->setBodyText($params['comment']);
        $mail->setFrom($params['email'], $params['name']);
        $mail->addTo('somebody_else@example.com', 'Some Recipient');
        $mail->setSubject('Test ActiveCodeline_SimpleContact Module for Magento');
        try {
            $mail->send();
        }        
        catch(Exception $ex) {
            Mage::getSingleton('core/session')->addError('Unable to send email. Sample of a custom notification error from ActiveCodeline_SimpleContact.');
 
        }
 
        //Redirect back to index action of (this) activecodeline-simplecontact controller
        $this->_redirect('activecodeline-simplecontact/');
    }
}
 
?>
Above file, although simple, demonstrates two powerful concepts. First we have an example of creating a block “on the fly”. There are several ways one can add an output block to be shown in Magento, this is the “hardest” way. Most of the materials you will find on the web will show you how to do it from xml files. However, I want you to know how to do it from code.
There is one important reason why you should now how to do this from code: Simplicity! If you were to output the block from layout files then you are adding at leas one more file to your module. The more files you have in your module, bigger the chance for bugs. This is really something you should consider and keep in mind for modules who actually require controllers and are not very user centric.
When I say “not very user centric” I think in terms where designer needs to have full control of layout and the way it is arranged. However in cases where not much layout modifications will be required on custom made controller output I believe that “saving yourself” from writing another layout file is the right way to go.
Note that inside the method call createBlock i use ‘Mage_Core_Block_Template’ as one of the parameters. This is the block class. In this example core block template is used. However, you can freely use one of your own declared blocks (will be shown in latter modules). Created block is appended (inserted actualy) in existing block named “content”. Block “content” is one that is almost always present in the output.
Inside the sendemailAction method we have another important concept, the notification system example. Catch exception block triggers the “adding in the notification message” to the session storage. There are few different storage mechanism inside the Magento. Most of them simply extend core/session without any noticeable diference, therefore unles you wish to extend the existing and write your own, you can use the Mage::getSingleton(‘core/session’). You can use 4 diferent types of notification messages: error, warning, notice, success. Which you can call respectively like trough methods addError(’Custom error here’), addWarning(’Custom warning here’), addNotice(’Custom notice here’), addSuccess(’Custom success here’) as shown above.
Any notifications added to ‘core/session’ storage are outputted to frontend trough view files. For instance one such example is app/design/frontend/default/default/template/page/3columns.phtml file. Inside the file there is the getChildHtml(‘global_messages’) ?> line of code that outputs all global notifications. Note that the “global_messages” referes to block name from within the page.xml layout file, which in turn referes to Magento “core/messages” (Mage_Core_Block_Messages) class type.
Final file for our module is the view file itself. As you might notice from the IndexController, we are using the ‘activecodeline/simple_contact.phtml’ as one of the parameters passed to createBlock method. What this means is that our module is going to look for app/design/frontend/default/default/template/activecodeline/simple_contact.phtml file. Note that if we were to assign diferent template then “default” to be used for our site then ‘activecodeline/simple_contact.phtml’ would refer to folder and file from within that template. If that file is not find in this “other” template then the system would look for one in default template.
file 4: app/design/frontend/default/default/template/activecodeline/simple_contact.phtml
content of file 4:
<div class="box simple_contact">
 
<form id="simple_contact_form" name="simple_contact_form" action="<?php echo $this->getUrl('activecodeline-simplecontact/') ?>index/sendemail" method="post">
 
    <fieldset class="group-select">
        <h4 class="legend">ActiveCodeline_SimpleContact module sample</h4>
        <ul>
        <li>
                <div class="input-box">
                    <label for="name">Gimme your name <span class="required">*</span></label><br />
 
                    <input name="name" id="name" title="Name" value="" class="required-entry input-text" type="text" />
                </div>
 
                <div class="input-box">
                    <label for="email">And your email <span class="required">*</span></label><br />
                    <input name="email" id="email" title="Email" value="" class="required-entry input-text validate-email" type="text" />
                </div>
 
                <div class="clear"></div>
 
                <div class="input-box">
           &nbsp;        <label for="comment">Some comment?</label><br />
 
                    <textarea name="comment" id="comment" title="Comment" class="required-entry input-text" style="height:100px;" cols="50" rows="3"></textarea>
                </div>
                </li>
                </ul>
    </fieldset>
    <div class="button-set">
        <p class="required">* Required Fields</p>
        <button class="form-button" type="submit"><span>Submit</span></button>
 
    </div>
</form>
 
</div>
Content of above file is mostly HTML related so there is not much to talk about it.
That’s it, the end.

Inchoo_Heared4us – Magento module for adding new tab under Onepage checkout

Just a small update. I wrote a module that you can download from Inchoo.net. Here is the link of article where you can find a module for download. Hope some of you find it useful. Please read the article before installing module.

Invoice printing to PDF taking to long in Magento?

Recently I was assigned a task to check the slowness of PDF printing on invoice page of Magento admin. It took him around 5minutes to print PDF (return PDF document on Invoice page). After diving into the code trying to trace the issue I found what was the cause of slow printing. PDF document that gets created has the ability to include the logo in header.
Code that adds the image into the header is located under the method named “getPdf()” in “app/code/core/Mage/Sales/Model/Order/Pdf/Invoice.php” file. There is a line that says “$this->insertLogo($page, $invoice->getStore());”.
Method “insertLogo()” is located under “app/code/core/Mage/Sales/Model/Order/Pdf/Abstract.php”. When commented out the invoice gets printed to PDF in a matter of 2-3 seconds. However, simply commenting out the method is not the proper solution. After short introspection, conclusion was that the image assigned as logo was to big in dimensions (around 3500x… px). Reducing logo in size drastically improved generation of PDF.
Hope this helps someone with the same “slow invoice printing” issue.

Bulk disable multiple Magento products

With all its ORM, model, resource and so on greatness, sometimes platform limitations are too obvious in Magento when it comes to large number of products in store. Recently I faced a simple task, I needed to disable around 6500 products in Magento that were assigned to a single category called “Inactive Products”. Given that it was an import from old system to Magento it is irrelevant why one would use category “Inactive Products” instead of just setting their status to “disabled”. My point is, whats the fastest way to do a bulk action on large number of products.
There are certain bulk actions you can do from “Catalog > Manage Products” section, however they all fail on large number of products selected. Logical solution is to simply use the raw SQL query. Only one catch, knowing where to look for.
Below is a practical example on “How to bulk update product status based on product category”. In my case, the id of my category in question was 35, while my “status” attribute had an id of 80. Most likely yours “status” attribute will have the same id value given that its a default Magento attribute.
Here is the actual SQL code that disables the products which have only one category assigned, the one with id 35:

UPDATE ma_catalog_product_entity_int
SET value = '2'
WHERE attribute_id = 80
AND entity_id IN (SELECT entity_id FROM ma_catalog_product_entity WHERE category_ids = '35');

Code above executed under two seconds, disabling around 6500 products in one run. Doing something like this purely from PHP or shall I say Magento can turn out to be “mission impossible”.
Only one advice, always do a full database backup prior to any database changes.