Coding Tips

In the course of our various projects we occasionally encounter requirements which
dictate original approaches. We publish these code examples in the hope that others
may use them to improve the performance of their sites, and to improve the quality
of service on the web in general.

In the course of our development work, we rely heavily on the work of others to
improve the efficiency and portability of our code, and we strongly endorse the
open-souce community for the improvements they have brought to the world of
computer services.

Drupal Tips

By far the most advanced Content Management System (CMS) available today, Drupal is a community supported system with a suite of plug and play options that put their competition to shame.

The code is accessible and customizable, and the modular system does not suffer from the inter-modular dependencies we so often see plaguing other code systems. The coding standards are rigorous, as is the testing suite before modules are deployed.

In the many years we have been developing Drupal sites, we have picked up a few practices which make the experience easier.

Including Site-Specific Files and Code in a Drupal Site

With every site we develop, we include a custom module directory, sites/all/modules/mysite. In this directory we place all of the custom style sheets, javascript files and php code specific to our site at mysite.com.

First we create a file, mysite.info:

name = mysite
description = Various functions and files required by mysite.com
core = 7.x
package = Other
; Uncomment and expand the following lines as required.
;
; Files containing classes
;files[] = mysite.inc
;
;stylesheets[all][] = mysite.css
;scripts[] = mysite.js

Any special stylesheets or javascript files are placed in the modules/mysite directory, and references with the appropriate directive are put in the modules/mysite/mysite.info file. This allows the files to be aggregated and cached on a production site, improving performance, and it means that the modifications are not overwritten when the core, a theme or a module is upgraded. Any scripts or stylesheets defined in the .info file will be loaded by hook_init() and will be aggregated and included on every page. More information is available on the Drupal site here.

We also create a file, mysite.module, containing any site-specific php code which might be necessary. The most common function in this module is mysite_cron(), which implements any special maintenance tasks necessary for the site to function. Even if you do not have any site-specific functions, place an empty mysite.module file in the modules/mysite directory so your module can be activated under admin/modules.

<?php
/*
* mysite.module
*
* Add any site specific tasks and hook implementations below.
*/
?>

The sky is the limit here, but the intent of this method is to create a repository of all those little odds and ends you include in any site development.

If you are developing a major function for your site, you are advised to do it in its own module, and perhaps put it up on the Drupal site, as others may have an interest in it. That way you have the whole community testing, coding, proposing upgrades, etc.

Block to show node data structure

One of the most frustrating tasks for a new Drupal developer is to visualize the information structures produced by Drupal during a page load. This can be extremely importand during major version upgrades, when intrinsic changes to data structures can break the php code your previous site relied on.

Rather than hacking modules, writing print_r outputs to watchdog, or other methods I have heard of, I generally create a php enabled block, restricted to the administrator role, and activate it in the page footer.

The code to display the $node object structure in D7 is:


<?php
$node = menu_get_object();
print "node " . $node->nid . " array is: <pre>" . print_r($node, TRUE) ."</pre>";
?>

Showing the block only for the administrator role even allows you to activate it on a live site that you are trying to trouble shoot.

Javascript Tips

Javascript has become the scripting language of choice for we browsers since standards are almost universal.

Javascript and AJAX: clean degradation

AJAX (Asynchronous Javascript and X(HT)ML) is a wonderful method of making pages more responsive for sites which require small changes in content. However there are two serious drawbacks.

  1. Browsers with javascript disabled are unable to navigate the site, and
  2. Search engines are unable to index the site.

Current web philosophy dictates that the pages should degrade cleanly into a form which does not require javascript, although some page designers will include the ultimate insult:

<noscript>This site requires javascript.</noscript>

In our practice, we reverse the paradigm, and serve the site in a javascript-disabled form. If javascript is enabled in the browser, we change the menu links dynamically to permit AJAX to be used. Otherwise the site will behave as a standard html-encoded web site.

The document body is coded with a javascript onload procedure:

<body onload="fixAnchors();">

and the following script walks the necessary portions of the document to change the links to a form which allows AJAX to be active using onclick functions:

function fixAnchors() {
var menudivs = ['head','c1','c2','c3','tabs','foot'];
for (var i=0; i < menudivs.length; i++){
var menuElement = document.getElementById(menudivs[i]);
if (menuElement != null) {
var menuElementList = menuElement.getElementsByTagName('a');
for (var j=0; j < menuElementList.length; j++) {
menuElementList[j].setAttribute("href", "#");
}
}
}
} // end function fixanchors

If javascript is disabled, the links are unaltered, and page navigation procedes normally, as does indexing by search engines.

If javascript is enabled, the links are re-written, permitting full AJAX functionality, which becomes active even with pages found from links given by the search engines.

This technique requires a careful page design which will display pages properly without javascript enhancements, and will avoid the two major problems associated with AJAX-enabled pages.

MySQL Tips

MySQL has come of age with Version 5. We use it as the database of choice because it is fast, reliable and robust.

During the course of our work we have run accress a number of times we were required to create unusual queries. We will share these query methods with you as we encounter them.

How to sort or group on CIDR

I found myself having to detect user download accesses for a client with the user possibly using different IP's within the same classless subnet. The user's IP is recorded as part of the session, and the client then updates the netmask using CIDR notation. The IP is stored as varchar(15) and the netmask may be stored as int or char(2) with a default value of 32.

CREATE TABLE logtable (
.... ,
ip varchar(15) default "0.0.0.0",
netmask int default 32,
.... )

MySQL has two functions, INET_ATON and INET_NTOA which convert dotted quad notation to an integer and back. It is easy to sort by IP using INET_ATON(ip). However grouping by subnet requires an understanding of the bitwise operators in MySQL.

When two numbers are compared using the MySQL bitwise operators, the numbers and the result are cast to bigint (64 bits). To create a bit mask for the network address, which is 32 bits, we must first shift the mask the required number of bits, then we have to mask the lower 32 bits to create the bit mask for the subnet:

Thus the SQL becomes:

SELECT ip,
INET_NTOA((INET_ATON("255.255.255.255") << (32 - netmask))
& INET_ATON("255.255.255.255")) AS bitmask,
INET_NTOA(INET_ATON(ip)
& ((INET_ATON("255.255.255.255") << (32 - netmask))
& INET_ATON("255.255.255.255"))) AS subnet
FROM logtable;

For an IP of 123.123.123.15 and a netmask of 24, this will give:

+----------------+---------------+---------------+
| ip | bitmask | subnet |
+----------------+---------------+---------------+
| 123.123.123.15 | 255.255.255.0 | 123.123.123.0 |
+----------------+---------------+---------------+

The SQL might be a little more readable, and a tad more efficient, if we substitute the actual integer:

INET_ATON("255.255.255.255") = 4294967295

SELECT ip,
INET_NTOA((4294967295 << (32 - netmask))
& 4294967295) AS bitmask,
INET_NTOA(INET_ATON(ip)
& ((4294967295 << (32 - netmask))
& 4294967295)) AS subnet
FROM logtable;