Magento 1.4.1.1
Back in history, app/code/core/Mage/GoogleAnalytics/Block/Ga.php was copied over to app/code/local/Mage/GoogleAnalytics/Block/Ga.php and changes were made so the protected function _toHtml() {} function would produce a proper Google Analytics asynchronous javascript entry on the page. The original had issues that didn’t post tracking information to Google properly for this website. This rewrite was fully functional with the desired result.
In order to clean things up, it was decided to modularize this change to a local namespace module. The following files were created…
apps/etc/modules/Chief_GoogleAnalytics.xml
<?xml version="1.0"?>
<config>
<modules>
<Chief_GoogleAnalytics>
<active>true</active>
<codePool>local</codePool>
</Chief_GoogleAnalytics>
</modules>
</config>
apps/code/local/Chief/GoogleAnalytics/etc/config.xml
<?xml version="1.0"?>
<config>
<modules>
<Chief_GoogleAnalytics>
<version>0.1.0</version>
</Chief_GoogleAnalytics>
</modules>
<global>
<blocks>
<googleanalytics>
<rewrite>
<ga>Chief_GoogleAnalytics_Block_Ga</ga>
</rewrite>
</googleanalytics>
</blocks>
</global>
</config>
app/code/local/Chief/GoogleAnalytics/Block/Ga.php
/**
* GoogleAnalitics Page Block
*
* @category Chief
* @package Chief_GoogleAnalytics
* @author Magento Core Team <core@magentocommerce.com>
*/
class Chief_GoogleAnalytics_Block_Ga extends Mage_GoogleAnalytics_Block_Ga
{
/**
* Prepare and return block's html output
*
* @return string
*/
protected function _toHtml()
{
if (!Mage::getStoreConfigFlag('google/analytics/active')) {
return '';
}
$this->addText('
<!-- BEGIN GOOGLE ANALYTICS CODE -->
<script type="text/javascript">
//<![CDATA[
var _gaq = _gaq || [];
_gaq.push(["_setAccount", "' . $this->getAccount() . '"]);
_gaq.push(["_trackPageview", "'.$this->getPageName().'"]);
(function() {
var ga = document.createElement(\'script\'); ga.type = \'text/javascript\'; ga.async = true;
ga.src = (\'https:\' == document.location.protocol ? \'https://ssl\' : \'http://www\') + \'.google-analytics.com/ga.js\';
var s = document.getElementsByTagName(\'script\')[0]; s.parentNode.insertBefore(ga, s);
})();
//]]>
</script>
<!-- END GOOGLE ANALYTICS CODE -->
');
$this->addText($this->getQuoteOrdersHtml());
if ($this->getGoogleCheckout()) {
$protocol = Mage::app()->getStore()->isCurrentlySecure() ? 'https' : 'http';
$this->addText('<script src="'.$protocol.'://checkout.google.com/files/digital/ga_post.js" type="text/javascript"></script>');
}
return parent::_toHtml();
}
}
Instead of the single corrected script entry as was normal when it was coming from app/code/local/Mage/GoogleAnalytics/Block/Ga.php, I now get a duplication. The Chief_GoogleAnalytics Block overwrite comes first, followed by the old stock junk code from app/code/core/Mage/GoogleAnalytics/Block/Ga.php. There is a single layout XML file for Google Analytics that defines it to show in after_body_start, so that’s working. Here’s the duplication where the new Block overwrite and old Block are showing. What gives?
<!-- BEGIN GOOGLE ANALYTICS CODE -->
<script type="text/javascript">
//<![CDATA[
var _gaq = _gaq || [];
_gaq.push(["_setAccount", "UA-xxxxxx-x"]);
_gaq.push(["_trackPageview", "/"]);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
//]]>
</script>
<!-- END GOOGLE ANALYTICS CODE -->
<!-- BEGIN GOOGLE ANALYTICS CODE -->
<script type="text/javascript">
//<![CDATA[
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);
})();
var _gaq = _gaq || [];
_gaq.push(["_setAccount", "UA-xxxxxx-x"]);
_gaq.push(["_trackPageview", "/"]);
//]]>
</script>
<!-- END GOOGLE ANALYTICS CODE -->
page.phtml entries that load the block.
<?php echo $this->getChildHtml('after_body_start') ?>
googleanalytics.xml layout
<layout version="0.1.0">
<default>
<!-- Mage_GoogleAnalytics -->
<reference name="after_body_start">
<block type="googleanalytics/ga" name="google_analytics" as="google_analytics" />
</reference>
</default>
</layout>
Added forensics by use of Mage::Log() with $this->getData() in strategic locations
Since we’re passing an array object around here, it’s a little more complicated than working with a string.
Start of _toHtml() function
2012-06-24T21:02:04+00:00 DEBUG (7): Array
(
[type] => googleanalytics/ga
[module_name] => Chief_GoogleAnalytics
)
Immediately cause it to load parent::_toHtml(); with $original_output =parent::_toHtml
2012-06-24T21:02:04+00:00 DEBUG (7): Array
(
[type] => googleanalytics/ga
[module_name] => Chief_GoogleAnalytics
[account] => UA-xxxxxx-x
[page_name] => /
[text] =>
<!-- BEGIN GOOGLE ANALYTICS CODE -->
<script type="text/javascript">
//<![CDATA[
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);
})();
var _gaq = _gaq || [];
_gaq.push(["_setAccount", "UA-xxxxxx-x"]);
_gaq.push(["_trackPageview", "/"]);
//]]>
</script>
<!-- END GOOGLE ANALYTICS CODE -->
)
To prevent this from turning into a ;tldr, logging $this->getData(); where the return was at the end, spits out both javascripts, but in reverse of the original.
$this->getData(); without ever referencing parent::_toHtml passes the following which is what we want for a returned object. One script with proper identifiers…
2012-06-24T21:06:24+00:00 DEBUG (7): Array
(
[type] => googleanalytics/ga
[module_name] => Chief_GoogleAnalytics
[account] => UA-xxxxxx-x
[page_name] => /aircraft
[text] =>
<!-- BEGIN GOOGLE ANALYTICS CODE -->
<script type="text/javascript">
//<![CDATA[
var _gaq = _gaq || [];
_gaq.push(["_setAccount", "UA-xxxxxx-x"]);
_gaq.push(["_trackPageview", "/aircraft"]);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
//]]>
</script>
<!-- END GOOGLE ANALYTICS CODE -->
)
First, a seemingly-but-not-really-pedantic naming convention. When you put files in
localthat’s not a rewrite, it’s a code pool override. When you create a custom module and use therewritenode, that’s a class rewrite. These are two different actions, and each one behaves differently. These differences are why you’re seeing the behavior you’re seeing.When you use a class override, you’re saying
This completely replaces the original class in the system.
When you use a class rewrite, you’re saying
Then you, by having your class extend the original
Mage_GoogleAnalytics_Block_Gaclass have an object that behaves the same as the original object, and you can add your methods. However, when you add your methods their parent method is the original block, which is different from an override.Step one would be removing the local code pool file
It sounds like you’ve already done this. Next, there’s the method in your rewritten class. When you call the
parent::_toHtml();methodyou’re telling Magento “Hey, do whatever the original object would have done”. With blocks and the
_toHtmlmethod, that means producing the same output. That’s why you’re getting the “old stock junk code”. The contract for the_toHtmlmethod is “whatever string this returns, will be included as the block output”. Your new code is also being included because you’re using theaddTextmethod, which your parent method is aware of.So, if you want to change the behavior of
_toHtmlin a block rewrite, you need to do something like thisBy calling the parent method first, you ensure that anything the block does from a state point of view still happens (which a block shouldn’t do, but, well, we know how that goes). Then, you return your string instead.