7 October 2011

Magento - disabled modules and block rewrites

Often you can come across page loading issues by disabling a module in admin which uses block rewrites. Here, disabling the module does not actually disable any block rewrites, but it does stop block HTML from being generated - this creates a conflict of interests and stops pages that have module related block content from loading correctly. Also it doesn't create an exception so you can't be sure when it has happened for a customer.

This happens because the module config file is still loaded even when disabled (unlike after setting a particular modules active tag to false in app/etc/modules/Namespace_Module.xml). This in turn means that there could be some quite serious implications on the site frontend and any number of pages could fail to render correctly.

Now it's true that you can just set the modules active tag to false to truly disable the module and circumvent any problems of this type, but if you are not a developer then you may not even know this is an option, or if you do you may not be confident enough to want to risk changing it. Either way I think that an average store owner will probably head straight to admin when wanting to disable a module, and expect that action to actually disable it.

To achieve this we need to stop the module config file from being loaded when it is disabled in admin, and that is what is covered here.

The first step is to copy:
app/code/core/Mage/Core/Model/Config.php
to:
app/code/local/Mage/Core/Model/Config.php
if it's not already there. Open the copied file and look for the following lines:
$modules = $this->getNode('modules')->children();
foreach ($modules as $modName=>$module) {
We are going to need to add code between these two lines, and also directly after (before the if statement that follows).

Add the following code between the 2 lines:
try
{
    $get_db = get_object_vars(Mage::getConfig()->getResourceConnectionConfig("default_setup"));
    $get_core_table = Mage::getSingleton('core/resource')->getTableName('core_config_data');

    $get_connection = mysql_connect($get_db['host'], $get_db['username'], $get_db['password']);
    mysql_select_db($get_db['dbname']);

    $get_results = mysql_query("SELECT path, value FROM `" . $get_core_table . "` WHERE `path` LIKE 'advanced/modules_disable_output/%'");

    $get_status = array();

    while($get_result = mysql_fetch_array($get_results, MYSQL_ASSOC))
    {
        $get_status[$get_result['path']] = $get_result['value'];
    }

    mysql_close($get_connection);
}
catch (Exception $e) { }
So lets step through what is going on here. Firstly, the information about the enabled status of a module as set in admin is not available in any config file (and so is not loaded by Magento) and instead exists only in the core_config_data table of the database you are running Magento from. That means we are going to have to run a database query and pull this data before we decide whether or not to load a particular config file.

So, the first thing we want to do is to put everything inside a try catch block to add some exception handling and allow the code to fail gracefully should the worst happen.

Next we pull the database connection details and put them into an array:
$get_db = get_object_vars(Mage::getConfig()->getResourceConnectionConfig("default_setup"));
After this we request the table name for core_config_data, being the table we are going to be running our query against. It's important to not just use core_config_data directly as the install could be using a table prefix - this will return the table name with that prefix should there be one.

At this point we are not able to query the database in the standard Magento fashion as the necessary classes have not been instantiated, so we are going to have to send a good old fashioned raw SQL statement. We start by opening a connection to the database and selecting the database we want to query:
$get_connection = mysql_connect($get_db['host'], $get_db['username'], $get_db['password']);
mysql_select_db($get_db['dbname']);
Next we store the query that will pull the relevant data from the table. If you look at the core_config_data table, you will see the path column contains some content starting with advanced/modules_disable_output/. The value column associated with each of these entries defines whether the module is disabled or enabled in admin - 0 for enabled, 1 for disabled. This is the data we will pull with the following statement:
$get_results = mysql_query("SELECT path, value FROM `" . $get_core_table . "` WHERE `path` LIKE 'advanced/modules_disable_output/%'");
Next we initialise an array we will use to store the results of the query, we then run the query and store each instance of advanced/modules_disable_output/ as the array key:
$get_status = array();

while($get_result = mysql_fetch_array($get_results, MYSQL_ASSOC))
{
    $get_status[$get_result['path']] = $get_result['value'];
}
After that we close our now redundant database connection, and the try catch block is also closed (we don't do anything in the case of an exception being thrown).

With that added that you should now have the following:
$modules = $this->getNode('modules')->children();

try
{
    $get_db = get_object_vars(Mage::getConfig()->getResourceConnectionConfig("default_setup"));
    $get_core_table = Mage::getSingleton('core/resource')->getTableName('core_config_data');

    $get_connection = mysql_connect($get_db['host'], $get_db['username'], $get_db['password']);
    mysql_select_db($get_db['dbname']);

    $get_results = mysql_query("SELECT path, value FROM `" . $get_core_table . "` WHERE `path` LIKE 'advanced/modules_disable_output/%'");

    $get_status = array();

    while($get_result = mysql_fetch_array($get_results, MYSQL_ASSOC))
    {
        $get_status[$get_result['path']] = $get_result['value'];
    }

    mysql_close($get_connection);
}
catch (Exception $e) { }

foreach ($modules as $modName=>$module) {
Now we need to add some code directly after the opening bracket of the foreach loop. Insert the following code there:
$get_module = 'advanced/modules_disable_output/' . $modName;
$get_status_test = isset($get_status[$get_module]) ? $get_status[$get_module] : 0;
The foreach loop initialises the key $modName, being the current modules name. We can append this to advanced/modules_disable_output/ to create a string that directly correlates to the database results we just pulled and set as each array items key - this is what the first line is doing.

Now we don't want the test we are about to run to fail in the case of a particular module not having a correlating database entry, so the second line by way of a ternary operator pulls the enabled status of the current module, and if there is no entry in the array sets the status to 0 meaning the module should be loaded.

You should now have the following code:
$modules = $this->getNode('modules')->children();

try
{
    $get_db = get_object_vars(Mage::getConfig()->getResourceConnectionConfig("default_setup"));
    $get_core_table = Mage::getSingleton('core/resource')->getTableName('core_config_data');

    $get_connection = mysql_connect($get_db['host'], $get_db['username'], $get_db['password']);
    mysql_select_db($get_db['dbname']);

    $get_results = mysql_query("SELECT path, value FROM `" . $get_core_table . "` WHERE `path` LIKE 'advanced/modules_disable_output/%'");

    $get_status = array();

    while($get_result = mysql_fetch_array($get_results, MYSQL_ASSOC))
    {
        $get_status[$get_result['path']] = $get_result['value'];
    }

    mysql_close($get_connection);
}
catch (Exception $e) { }

foreach ($modules as $modName=>$module) {
    $get_module = 'advanced/modules_disable_output/' . $modName;
    $get_status_test = isset($get_status[$get_module]) ? $get_status[$get_module] : 0;
Now we have all the data we need in order to decide whether or not to load a modules config file according to it's enabled/disabled status. So the final part of this is to modify the if statement immediately following the two line of code you just added, unchanged it should be this:
if ($module->is('active')) {
$module->is('active') pulls the active status from that modules config file, but we want to also take into account the module status as set in admin, so change it to the following:
if ($module->is('active') && !$get_status_test) {
Now you should find that when you disable a module in admin, it is actually, fully, disabled including block rewrites. Remember that if you are not seeing the affects of this within admin, log out and then back in again.

7 comments:

  1. Nice solution, but it doesn't take into account multiple websites/stores. For example, I have a module disabled in one website but the default config has it enabled. You need to add a check for the scope_id and compare it to the current website/store.

    ReplyDelete
  2. Good point, perhaps I will extend the post to take into account a multi website/store setup.

    ReplyDelete
    Replies
    1. Hi Hussey,

      Thanks for the code you wrote here.

      Could you please post or provide me sample code for make that code work in multiple websites and stores.

      Kindly help me.

      Thanks,
      Prakash

      Delete
    2. Hi Prakash,

      Unfortunately I just can't put this change very high on my priority list at the moment so I haven't found time to update the post with those changes. I will do it at some point, however I can't see it being any time particularly soon just at the moment.

      Sorry about that.

      Delete
    3. Dear Hussey,

      Thanks for your prompt reply. Kindly post the changes if you find time.

      Thanks,
      Prakash

      Delete
  3. Brilliant!!! Well done! Any updates regarding the multi website code?

    ReplyDelete
  4. Thank you!
    This is exactly what I was looking for.
    If you want to do this whitout pulling the databse information directly you could always use Magentos own methods like this:

    $readResource = Mage::getSingleton('core/resource')->getConnection('core_read');
    $get_core_table = Mage::getSingleton('core/resource')->getTableName('core_config_data');

    $select_results = "SELECT path, value FROM `" . $get_core_table . "` WHERE `path` LIKE 'advanced/modules_disable_output/%'";
    $get_results=$readResource->fetchAll($select_results);
    $get_status = array();

    foreach($get_results as $result){
    $get_status[$result['path']] = $result['value'];
    }

    ReplyDelete