Avoid ad blockers with Piwik Analytics

For many reasons you may want to circumvent an Ad Blocker that's blocking your analytics script. Ad Blockers may block these scripts to curb invasive marketing tracking from third party advertisers i.e. Google. If you're using Piwik Analytics (or most other self-hosted systems) then you're in luck, you can rewrite the url to avoid the Ad Blocker.

Image: Google Analytics & Piwik Analytics requests blocked
pw-blocked.png#asset:66

We'll rewrite the url in .htaccess, you can change the path to anything you want. (Make sure you set the "analytics" text sections to your actual path where Piwik is installed).

RewriteEngine On
RewriteRule ^proxy-analytics/(.*) /analytics/piwik.$1 [L]

Then we'll need to update the tracking code for Piwik to work with the rewrite, here's what your code should look like before the change.

<!-- Piwik -->
<script type="text/javascript">
  var _paq = _paq || [];
  _paq.push(['trackPageView']);
  _paq.push(['enableLinkTracking']);
  (function() {
    var u="//www.example.com/analytics/";
    _paq.push(['setTrackerUrl', u+'piwik.php']);
    _paq.push(['setSiteId', 2]);
    var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
    g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
  })();
</script>
<noscript><p><img src="//www.example.com/analytics/piwik.php?idsite=2" style="border:0;" alt="" /></p></noscript>
<!-- End Piwik Code -->

And here's the lines to change: (Replace "analytics" text sections to your actual path where Piwik is installed).

<!-- Piwik -->
<script type="text/javascript">
  var _paq = _paq || [];
  _paq.push(['trackPageView']);
  _paq.push(['enableLinkTracking']);
  (function() {
    var u="//www.example.com/proxy-analytics/";
    _paq.push(['setTrackerUrl', u+'php']);
    _paq.push(['setSiteId', 2]);
    var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
    g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'js'; s.parentNode.insertBefore(g,s);
  })();
</script>
<noscript><p><img src="//www.example.com/proxy-analytics/php?idsite=2" style="border:0;" alt="" /></p></noscript>
<!-- End Piwik Code -->

Line 7: You'll need to update the url to the path that is your directory rewrite from the htaccess file.
Line 8 + 11: Remove piwik. from these lines, ad blockers may target piwik.[php/js] or like ABP they may target just the word piwik.
Line 14: Lastly, you'll need to update the path like on line 7, and again remove piwik. from the image source. The htaccess rewrite prefixes the path with piwik. so that everything still works.

Once you've made this change you should immediately be able to collect Piwik Analytics data through most Ad Blockers.

Image: Google Analytics blocked & Piwik Analytics request not blocked
pw-unblocked.png#asset:67

This method has been tested and verified for use with 'Adblock Plus' but should work with most other Ad blockers. If you have any questions about Piwik with Ad Blockers, leave a comment or tweet me @WilliamIsted

Update: Interestingly I've just noticed that ABP is actually stopping all of the screenshot images with piwik in the name from displaying. That seems like rather aggressive filtering.

SourceGuardian - The Zend Engine API is outdated

The following error occurred on CentOS 6 with cPanel 11.48.4 when using the CLI version of PHP:

SourceGuardian requires Zend Engine API version 220131226. The Zend Engine API version 220121212 which is installed, is outdated.

Googling for a way to safely update Zend Engine API resulted in 3+ year old threads with no answers or clues on how to update with WHM installed.

If you're looking to update the Zend Engine API, then this guide is not for you. Instead, I chose to remove Zend from the CLI PHP config as the error message being displayed on all PHP crons was breaking the scripts.

Here's how to remove Zend from your PHP CLI config...

First, find the config file for the CLI PHP. You can do this by checking your existing config file using php -i – use grep to grab the path with php -i | grep 'Configuration File' .

For me php.ini was located at /usr/local/lib/php.ini

Find the line zend_extension = "/usr/local/sourceguard/ixed.x.x.lin" and comment it out. Restart Apache and you should notice that the error no longer displays. I'm not entirely sure what the Zend Engine is for under CLI PHP but I haven't encountered any issues yet.

If you encounter issues, just uncomment the line and restart Apache. Following these instructions should be a last resort if you cannot update Zend Engine API, so you do so at your own risk.

CSS hover menus with Microsoft Surface (touch device)

If you have a css hover menu you may notice that it doesn't play well with Microsoft Surface IE with either a touch pen or finger. A long press shows the menu but on releasing the menu hides again.

This is because the touch device is unaware that the hover menu is supposed to remain visible after the hover intent has ended. iOS and other touch operating systems handles this logically but the powers to be seem to require websites to be "touch optimised".

Here's what you need to do:

<nav>
	<ul>
		<li>
			<a>Hover menu</a>
			<ul>
				<li><a href="http://example.org">Item 1</li>
				<li><a href="http://example.org">Item 2</li>
				<li><a href="http://example.org">Item 3</li>
			</ul>
		</li>
	</ul>
</nav>

You'll need to make sure that your hover anchor has an href on it to make it "touchable" on certain devices, you can do this with either href="#" or href="javascript:void" , then you need to add aria-haspopup="true" to tell the device that after the user releases the tap, whatever was visible should remain visible until another interaction.

<nav>
	<ul>
		<li>
			<a aria-haspopup="true" href="#">Hover menu</a>
			<ul>
				<li><a href="http://example.org">Item 1</li>
				<li><a href="http://example.org">Item 2</li>
				<li><a href="http://example.org">Item 3</li>
			</ul>
		</li>
	</ul>
</nav>

Graceful degradation Helvetica Neue font family

Here's the tested and proven method of using Helvetica Neue fonts on your website. No Helvetica? No problem, degrades to Arial and Sans Serif for safety.

body {
	font-family: "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, sans-serif; 
	font-weight: 300;
}

On webkit browsers the font (along with others) may appear to have spare pixels around the lettering, making it look "fuzzy". To fix this we can tell webkit browsers to smooth the text with antialiasing:

body {
	-webkit-font-smoothing: antialiased;
}

This font stack originally appeared on CSS Tricks and has been modified slightly for compatibility with devices.

Recursive chmod only on directories

Run find on -type d (directories) with the -exec primary to perform the chmod only on folders:

find /your/path/here -type d -exec chmod o+x {} \;

To be sure it only performs it on desired objects, you can run just find /your/path/here -type d first; it will simply print out the directories it finds.

IE & CDN jQuery source fallback

<!--[if !IE]><!--><script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script><script>window.jQuery || document.write('<script src="/js/jquery-3.0.0.min.js"><\/script>')</script><!--<![endif]-->
<!--[if IE]><script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script><script>window.jQuery || document.write('<script src="/js/jquery-2.2.4.min.js"><\/script>')</script><![endif]-->

Sending dynamic status headers from PHP

Sending the right HTTP protocol for headers from PHP is easy, you just need to find which HTTP protocol your server is using. Most servers will be using HTTP/1.1, but allowing PHP to serve the protocol version for you means safer headers for older systems and greater cross-platform compatibility.

Rather than sending

header( 'HTTP/1.1 404 Not Found' );

we're going to send

header( $_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found' );

where

$_SERVER['SERVER_PROTOCOL']

gives us the HTTP protocol which matches the previous 'HTTP/x.x' format.

This can be used with any header that requires the HTTP protocol to be sent, it is not limited to 404 errors in particular.

EDIT 2015-10-10:

Available from PHP 5.4.0, function 'http_response_code' is the alternative to this and sends the correct protocol along with the correct status code description.

Usage: http_response_code(404);
Output: HTTP/1.1 404 Not Found

Recursively zip files with PHP

This function is useful for zipping files/directories when you only have FTP access to a site, and for when you are using a web host ‘File Manager’ which bugs out on permissions or otherwise.

function Zip($source, $destination)
{
    if (!extension_loaded('zip') || !file_exists($source)) {
        return false;
    }
    $zip = new ZipArchive();
    if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
        return false;
    }
    $source = str_replace('\', '/', realpath($source));
    if (is_dir($source) === true)
    {
        $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
        foreach ($files as $file)
        {
            $file = str_replace('\', '/', $file);
            // Ignore "." and ".." folders
            if( in_array(substr($file, strrpos($file, '/')+1), array('.', '..')) )
                continue;
            $file = realpath($file);
            if (is_dir($file) === true)
            {
                $zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
            }
            else if (is_file($file) === true)
            {
                $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
            }
        }
    }
    else if (is_file($source) === true)
    {
        $zip->addFromString(basename($source), file_get_contents($source));
    }
    return $zip->close();
}

Zip('/folder/to/compress/', './compressed.zip');

Recursively copy files with PHP

This function is useful for copying files when you only have FTP access to a site, and for when you are using a web host 'File Manager' which bugs out on permissions or otherwise.

function recurse_copy($src, $dst) { 
    $dir = opendir($src); 
    @mkdir($dst); 
    while(false !== ( $file = readdir($dir)) ) { 
        if (( $file != '.' ) && ( $file != '..' )) { 
            if ( is_dir($src . '/' . $file) ) { 
                recurse_copy($src . '/' . $file,$dst . '/' . $file); 
            } 
            else { 
                copy($src . '/' . $file,$dst . '/' . $file); 
            } 
        } 
    } 
    closedir($dir); 
} 

recurse_copy('./big-directory', './big-directory-new');