Category Archives: Web Site Dev

Posts concerning web site design and development. Examples of php, xhtml, and javascript code. Wordpress-related posts may be cross-categorized here, but also have their own category.

File compression is possible on Apache web hosts that do not have mod_gzip or mod_deflate enabled, and it’s easier than you might think.

A great explanation of why compression helps the web deliver a better user experience is at betterexplained.com.

Two authoritative articles on the subject are Google’s Performance Best Practices documentation | Enable compression and Yahoo’s Best Practices for Speeding Up Your Web Site | Gzip Components.

Compressing PHP files

If your Apache server does not have mod_gzip or mod_deflate enabled (Godaddy and JustHost shared hosting, for example), you can use PHP to compress pages on-the-fly. This is still preferable to sending uncompressed files to the browser, so don’t worry about the additional work the server has to do to compress the files at each request.

Option 1: PHP.INI using zlib.output_compression

The zlib extension can be used to transparently compress PHP pages on-the-fly if the browser sends an “Accept-Encoding: gzip” or “deflate” header. Compression with zlib.output_compression seems to be disabled on most hosts by default, but can be enabled with a custom php.ini file:

[PHP]

zlib.output_compression = On

Credit: http://php.net/manual/en/zlib.configuration.php

Check with your host for instructions on how to implement this, and whether you need a php.ini file in each directory.

Option 2: PHP using ob_gzhandler

If your host does not allow custom php.ini files, you can add the following line of code to the top of your PHP pages, above the DOCTYPE declaration or first line of output:

<?php if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) ob_start("ob_gzhandler"); else ob_start(); ?>

Credit: GoDaddy.com

For WordPress sites, this code would be added to the top of the theme’s header.php file.

According to php.net, using zlib.output_compression is preferred over ob_gzhandler().

For WordPress or other CMS sites, an advantage of zlib.output_compression over the ob_gzhandler method is that all of the .php pages served will be compressed, not just those that contain the global include (eg.: header.php, etc.).

Running both ob_gzhandler and zlib.output_compression at the same time will throw a warning, similar to:

Warning: ob_start() [ref.outcontrol]: output handler ‘ob_gzhandler’ conflicts with ‘zlib output compression’ in /home/path/public_html/ardamis.com/wp-content/themes/mytheme/header.php on line 7

Compressing CSS and JavaScript files

Because the on-the-fly methods above only work for PHP pages, you’ll need something else to compress CSS and JavaScript files. Furthermore, these files typically don’t change, so there isn’t a need to compress them at each request. A better method is to serve pre-compressed versions of these files. I’ll describe a few different ways to do this, but in both cases, you’ll need to add some lines to your .htaccess file to send user agents the gzipped versions if they support the encoding. This requires that Apache’s mod_rewrite be enabled (and I think it’s almost universally enabled).

Option 1: Compress locally and upload

CSS and JavaScript files can be gzipped on the workstation, then uploaded along with the uncompressed files. Use a utility like 7-Zip (quite possibly the best compression software around, and it’s free) to compress the CSS and JavaScript files using the gzip format (with extension *.gz), then upload them to your server.

For Windows users, here is a handy command to compress all the .css and .js files in the current directory and all sub directories (adjust the path to the 7-Zip executable, 7z.exe, as necessary):

for /R %i in (*.css *.js) do "C:\Program Files (x86)\7-Zip\7z.exe" a -tgzip "%i.gz" "%i" -mx9

Note that the above command is to be run from the command line. The batch file equivalent would be:

for /R %%i in (*.css *.js) do "C:\Program Files (x86)\7-Zip\7z.exe" a -tgzip "%%i.gz" "%%i" -mx9

Option 2: Compress on the server

If you have shell access, you can run a command to create a gzip copy of each CSS and JavaScript file on your site (or, if you are developing on Linux, you can run it locally):

find . -regex ".*\(css\|js\)$" -exec bash -c 'echo Compressing "{}" && gzip -c --best "{}" > "{}.gz"' \;

This may be a bit too technical for many people, but is also much more convenient. It is particularly useful when you need to compress a large number of files (as in the case of a WordPress installation with multiple plugins). Remember to run it every time you automatically update WordPress, your theme, or any plugins, so as to replace the gzip’d versions of any updated CSS and JavaScript files.

The .htaccess (for both options)

Add the following lines to your .htaccess file to identify the user agents that can accept the gzip encoded versions of these files.

<files *.js.gz>
  AddType "text/javascript" .gz
  AddEncoding gzip .gz
</files>
<files *.css.gz>
  AddType "text/css" .gz
  AddEncoding gzip .gz
</files>
RewriteEngine on
#Check to see if browser can accept gzip files.
ReWriteCond %{HTTP:accept-encoding} gzip
RewriteCond %{HTTP_USER_AGENT} !Safari
#make sure there's no trailing .gz on the url
ReWriteCond %{REQUEST_FILENAME} !^.+\.gz$
#check to see if a .gz version of the file exists.
RewriteCond %{REQUEST_FILENAME}.gz -f
#All conditions met so add .gz to URL filename (invisibly)
RewriteRule ^(.+) $1.gz [QSA,L]

Credit: opensourcetutor.com

I’m not sure it’s still necessary to exclude Safari.

For added benefit, minify the CSS and JavaScript files before gzipping them. Google’s excellent Page Speed Firefox/Firebug Add-on makes this very easy. Yahoo’s YUI Compressor is also quite good.

Verify that your content is being compressed

Use the nifty Web Page Content Compression Verification tool at http://www.whatsmyip.org/http_compression/ to confirm that your server is sending the compressed files.

Speed up page load times for returning visitors

Compression is only part of the story. In order to further speed page load times for your returning visitors, you will want to send the correct headers to leverage browser caching.

I had noticed mentions of analytic information provided by Compete.com often enough that I was curious about what it could do for me.

Compete already had some information about ardamis.com, but it was stunningly wrong. For example, it was telling me that the phrase “godaddy referral program” was responsible for 20.35% of the total traffic sent to my site by search engines. Until recently, I did have a page that mentioned godaddy referral programs, but according to Google Analytics, it was barely ever visited (7 page views in the last 30 days – it was the 78th most popular page on my site, which does get a few thousand visitors a month). Even more strange, it told me that I was getting traffic for the search phrase “myspace”. I have never written anything about myspace before.

I figured that once I installed their JavaScript tracking code, the analytics information would be much more accurate. So I installed the code, confirmed it appeared at the bottom of the home page, and attempted to verify my site at //ardamis.com/, but was unable to. I had read somewhere that the free account does not support tracking subdomains, and the verification process seemed to get hung up on the use of .htaccess to redirect non-www traffic to www.ardamis.com. I was mystified that Compete apparently could not recognize this was happening via a 301 redirect header and compensate.

Sorry,

It looks like the CompeteXL code has not been correctly placed on the homepage of your site.

This could be because either the code was not copied over correctly, or because it has been placed on the wrong page.

We think your homepage is

ardamis.com

I went so far as to email my amazement to their support staff, who promptly and politely wrote me back. (Thumbs up to the guys answering the emails.)

I had made a few other changes to my site at the same time, so I ran a Page Speed check on it. Page Speed told me that I was linking to a resource at trgc.opt.fimserve.com that was throwing a 404 error. I was pretty sure I didn’t intentionally link to anything at that domain, so I Googled it. Surprisingly, there’s not much out there on trgc.opt.fimserve.com other than this and this. As it turns out, fimserve.com is part of something called the FOX Audience Network, and FAN’s parent company is News Corporation, which also owns myspace.com.

Here’s the WHOIS on fimserve.com:

Domain Name: FIMSERVE.COM
Registrar: REGISTER.COM, INC.
Whois Server: whois.register.com
Referral URL: http://www.register.com
Name Server: NS1.MYSPACE.COM
Name Server: NS2.MYSPACE.COM
Status: clientTransferProhibited
Updated Date: 17-oct-2006
Creation Date: 17-oct-2006
Expiration Date: 17-oct-2011

And here’s the WHOIS on foxaudiencenetwork.com:

Domain Name: FOXAUDIENCENETWORK.COM
Registrar: MARKMONITOR INC.
Whois Server: whois.markmonitor.com
Referral URL: http://www.markmonitor.com
Name Server: NS1.MYSPACE.COM
Name Server: NS2.MYSPACE.COM
Status: clientDeleteProhibited
Status: clientTransferProhibited
Status: clientUpdateProhibited
Updated Date: 03-may-2010
Creation Date: 03-jun-2008
Expiration Date: 03-jun-2011

I didn’t like the idea that information about my visitors was being shared with anyone but the site I had signed up for, so I started looking through the Compete FAQs and found this:

Currently, the CompeteXL code tracks ONLY self-reported Audience Profile data through a partnership with the FOX Audience Network.

The CompeteXL code DOES NOT track traffic or user engagement metrics, that information continues to be provided through our multi-sourced panel and requires NO addition of code to your site.

http://www.compete.com/help/q225

What the hell? Why am I installing a tracking code if it’s not used to track traffic?

Oh, and this was a fun discovery, too:

The FOX Audience Network (FAN) is a unit of News Corporation that supports monetization efforts across the company’s online content portfolio, as well as third-party publisher sites.

FAN leverages proprietary advertising technology to create highly-targeted advertising campaigns for a wide range of marketers, while also delivering cutting-edge tools and services to third-party publisher partners. FAN works directly with hundreds of advertisers to develop customized marketing programs that optimize both branded and performance-based strategies.

http://www.foxaudiencenetwork.com/aboutus.php

I use a very popular HOSTS file to block a huge number of servers that are known to host advertisements, tracking scripts (including Google Analytics), parasites, hijackers and unwanted Adware/Spyware programs. The 404 error in Page Speed was caused by the inclusion of trgc.opt.fimserve.com in this custom HOSTS file, which then led me to finding all this out the next day. I’ve removed the tracking code because the information I wanted was on traffic – who’s coming to my site, why, and through what means – and not user demographics.

In May, 2011, the Xbox Forums were redesigned and some, but not all, of the existing content moved to a new subdomain: http://forumsarchive.xbox.com. I’ve updated the post to fix the links so they no longer 404, but the rest of the information is dated and probably inaccurate.

I was having some problems with Final Fantasy XIII freezing on my Xbox 360, so I posted a few times to the Xbox Forums at http://forums.xbox.com/. The posts contained a link to a blog post here on ardamis.com that explained my situation in greater detail. I started to wonder if the forums were searcheable in Google, so I checked and found that for the most part, they weren’t.

The regular forum thread URLs contain a nofollow, noindex robots meta tag and end with ‘ShowPost.aspx’, but if you click on the Print button, you’re taken to a URL that ends with ‘PrintPost.aspx’. These printer-friendly pages have no such tag.

If you Google this – site:forums.xbox.com – you’ll see what I mean. There are currently less than a thousand results in Google.

This post is just to test whether I can get a PrintPost.aspx page indexed in Google by linking to it, and whether I can create an inbound link from forums.xbox.com by linking to the printer-friendly version of a thread that contains a link back to a page on ardamis.com.

The URL to the normal thread: http://forumsarchive.xbox.com/31588531/ShowPost.aspx
The URL to the printer-friendly page: http://forumsarchive.xbox.com/31588531/PrintPost.aspx

The URL to the normal thread: http://forumsarchive.xbox.com/31685953/ShowPost.aspx
The URL to the printer-friendly page: http://forumsarchive.xbox.com/31685953/PrintPost.aspx

To test, as of 03.17.2010…
Your search – site:forums.xbox.com ardamis – did not match any documents.

Update: 03.18.2010

Both PrintPost.aspx pages are now indexed. Google Webmaster Tools still doesn’t know of any inbound links to my Final Fantasy post, although there are probably a handful by now.

Update: 03.19.2010

The PrintPost.aspx pages are now showing up as the first two results in Google for final fantasy xiii xbox freeze. My post is the third result.

Update: 03.31.2010

My post is now the first result for final fantasy xiii xbox freeze. The PrintPost.aspx pages are now showing up as the second and third results.

Update: 06.26.2010

The answer is Yes, it is possible to get inbound links that show up in Google Webmaster Tools from forums.xbox.com.

I’m often given copy for web sites as Word documents. As one would expect, these documents contain all sorts of symbols that should be converted to entities before they can be used in a web page.

For example, ‘smart quotes’ (curly quotes), trademark and registered symbols, em-dashes, and other symbols look great, but can cause problems if you just drop them into a page and don’t use the corresponding character encoding.

Rather than do a global find-and-replace in my HTML editor, I’ve written a few Word macros that replace these symbols.

The first macro, CleanupHTML, replaces smart quotes with straight quotes. The second macro, PrettyHTML, replaces smart quotes with the correct pretty quote, ala .

At some point, I want to extend the macro to replace accented characters, such as è, and also to wrap bold and italics in strong and em tags. Ideally, I’d also find a way of converting lists in Word to straight text wrapped in unordered list items, as lists seem to require the most cleanup when copying and pasting.

Sub CleanupHTML()
'
' CleanupHTML Macro
' Cleanup document for HTML by replacing special characters with entities.
'
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "&"
        .Replacement.Text = "&amp;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "©"
        .Replacement.Text = "&copy;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "®"
        .Replacement.Text = "&reg;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "™"
        .Replacement.Text = "&#8482;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "--"
        .Replacement.Text = "&#8212;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "—"
        .Replacement.Text = "&#8212;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "–"
        .Replacement.Text = "&#8211;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "…"
        .Replacement.Text = "..."
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "'"
        .Replacement.Text = "'"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    With Selection.Find
        .Text = """"
        .Replacement.Text = """"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
End Sub

Sub PrettyHTML()
'
' PrettyHTML Macro
'
'
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "&"
        .Replacement.Text = "&amp;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "©"
        .Replacement.Text = "&copy;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "®"
        .Replacement.Text = "&reg;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "™"
        .Replacement.Text = "&#8482;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "--"
        .Replacement.Text = "&#8212;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "—"
        .Replacement.Text = "&#8212;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "–"
        .Replacement.Text = "&#8211;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "…"
        .Replacement.Text = "..."
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = ChrW(8220)
        .Replacement.Text = "&ldquo;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = ChrW(8221)
        .Replacement.Text = "&rdquo;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "'"
        .Replacement.Text = "&rsquo;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    With Selection.Find
        .Text = "è"
        .Replacement.Text = "&egrave;"
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
End Sub

Many thanks to
http://celebritycola.blogspot.com/2004/09/preserving-formatting-when-posting.html for getting me started on this.

I’ve been collecting links to good WordPress templates for a long time. Because I’ve been asked a few times recently if I can recommend some templates, I’m putting them together here.

Huge, isolated, animated slideshow

I really like these themes for focusing almost exclusively on one’s portfolio. They feature a massive, animated slideshow that keeps the visitor’s attention on the images. In some cases, a shadow underneath the slideshow, combined with the animation, creates a 3-D effect to put the portfolio right in the visitor’s face.

Confined slideshow

These themes are a bit more modest than the first collection. They still accentuate a portfolio of images, but in a less flashy way. The slideshow exists within a defined banner area, so the look is a little more traditional. These templates would be a better fit for businesses.

Partial-banner image rotation

These themes use only part of the banner to display a rotating group of images, leaving an area for text next to the images. This allows a short message or tagline to get equal placement. These are the most conservative themes.

Creative themes

This last group of themes break with the conventions of the above themes and use full-screen-width or otherwise larger-than-normal images that rotate behind the other elements of the site. Undeniably attention-grabbing, but also slower to load.

Single page

These are single-page web sites. These types of pages are great as business cards or resumes, or as place holders while a larger site is developed.

The rest

I like these, too, but they don’t fit in any of the above categories.

For a new project, I needed to combine two or more associative arrays and sum the values of any keys that exist in common. I was a little surprised to find that there wasn’t a built-in function to do this in PHP. So I wrote my own. It can accept any number of arrays as arguments, and goes through each array one key at a time, comparing the keys to those in an output array. Where the keys match, the values are summed. If the key does not exist in the output array, it’s appended to it.

The function returns an array that contains all of the unique keys in the input arrays.

If anyone has any ideas about how to optimize this, please post a comment. One thought would be to use the first array as the output array, avoiding the key-by-key comparison of the first array against an empty array.

function array_mesh() {
	// Combine multiple associative arrays and sum the values for any common keys
	// The function can accept any number of arrays as arguments
	// The values must be numeric or the summed value will be 0
	
	// Get the number of arguments being passed
	$numargs = func_num_args();
	
	// Save the arguments to an array
	$arg_list = func_get_args();
	
	// Create an array to hold the combined data
	$out = array();

	// Loop through each of the arguments
	for ($i = 0; $i < $numargs; $i++) {
		$in = $arg_list[$i]; // This will be equal to each array passed as an argument

		// Loop through each of the arrays passed as arguments
		foreach($in as $key => $value) {
			// If the same key exists in the $out array
			if(array_key_exists($key, $out)) {
				// Sum the values of the common key
				$sum = $in[$key] + $out[$key];
				// Add the key => value pair to array $out
				$out[$key] = $sum;
			}else{
				// Add to $out any key => value pairs in the $in array that did not have a match in $out
				$out[$key] = $in[$key];
			}
		}
	}
	
	return $out;
}

If you want to test it out, here’s some additional code.

$a = array('abc' => '100.000', 'def' => '50', 'ghi' => '25', 'xyz' => '10');
$b = array('abc' => '100.333', 'def' => '75', 'ghi' => '50', 'jkl' => '25');
$c = array('abc' => '100.111', 'def' => '75', 'ghi' => '50', 'uvw' => '5');

echo "<pre>";
print_r(array_mesh($a, $b, $c));
echo "</pre>";

I hope you find it helpful.

I’ve been using Dreamweaver for years. One thing that has always bothered me is that it wants to change my lowercase JavaScript event attributes to camelCase, but it only does this after I close the file. So when I wrote the code, it was valid XHTML 1.0 Strict, but after I saved the file, it no longer was. Dreamweaver was messing up my validation and it was making me furious.

So, if I have typed

<body onload="init()">

Once I’ve closed the file, it’s changed to

<body onLoad="init()">

It will do this for all the tags: onload, onblur, onfocus, etc…

I’ve tried setting my default document type, the validation fallback, everything I could find to XHTML 1.0 Strict, which requires lowercase attributes, but Dreamweaver isn’t smart enough to realize that I mean for this to apply to all of my files, even .php includes.

Finally, I’d had enough and started looking for a solution. I found two different ways of getting this done.

First method: click on Edit -> Preferences -> Code Format. Change “Default tag case” to lowercase, “Default attribute case” to lowercase=’value’, and in “Override case of”, place checkmarks next to Tags and Attributes.

You can also modify each of these attributes in the Tag Library Editor.

Second method: click on Edit -> Preferences -> Code Hints, and under the Menus area, click on the Tag Library Editor link.

A Tag Library Editor window will open. If you wanted to change the body’s onload attribute, for example, you would expand the HTML folder, then the body folder. Highlight onLoad and in the “Attribute case:” menu, select Lowercase, then click “Set default”.

At long last, valid XHTML code from Dreamweaver, even if I shouldn’t be using body onload and inline event handlers in the first place.

For some time, I’ve felt that Ardamis.com was being pulled in two directions. It started out as something of a business card, then it landed me a job, and so the focus changed and it became more of a personal blog. A few years later, I found myself posting mainly code snippets from personal projects and announcements of new site launches.

So, I’ve determined that I’ll keep Ardamis.com as a place for experimentation and create a new site to handle the web development business – Aleph Studios. It’s so new that I have to think sometimes about how Aleph is spelled as I type it.

I’ll be migrating some of the pages away from Ardamis over the next week or two.

Update 2/22/2010: It looks like changing .htaccess is no longer necessary. After you select PHP 5.x, your site will begin using version 5.2.5 without any further configuration.

The following applies to older domains. As of early 2009, newly purchased linux hosting plans are running PHP 5.2.8, while older plans, once updated, only go up to PHP 5.2.5. I’ve had Ardamis.com hosted at GoDaddy since 2005, and quite awhile ago I thought I had upgraded to PHP version 5 from 4.3.11, but tonight I happened to check with phpinfo and found I was still on version 4.

In the unheard of ten minutes that I was on hold waiting for technical support, I figured out how to really run my pages on PHP 5.x (in this case, 5.2.5).

Log in and go to your Hosting Control Center. You must be running Hosting Configuration 2.0 to go any further, so if you haven’t touched your domain in years, do that first.

Click on Content, then Add-On Languages. Next to PHP Version, select PHP 5.x and click Continue. You’ll get a message that “Changing to PHP 5.x may make your PHP files run incorrectly.” Highly unlikely these days, but OK, you’ve been warned. Notice, too that it says “PHP 5.x will be activated“. Click Update.

It may take awhile for this change to be processed by the server, but once your Account Summary is displaying PHP Version: 5.x, it’s time for the really important part.

You see, you’ve only made PHP 5.x available at this point. Your *.php files are still running in 4.x. Go ahead and check phpinfo again.

Now, you could simply edit .htaccess to change the extensions, like so:

AddHandler x-httpd-php5 .php
AddHandler x-httpd-php .php4

More details at http://help.godaddy.com/article/1082

But if you’re squeamish about changing .htaccess yourself, there’s another way to set 5.x to be the default handler for *.php files. All the following does, strangely enough, is to add the AddHandler x-httpd-php5 .php to the beginning of your .htaccess file.

Back in the Hosting Control Center, click on Settings, then File Extension. If the change to 5.x has been completed, you’ll see at the bottom of the available extensions list, “Extension -> .php | Runs Under -> PHP 5.x” If it’s not there, stop here and come back in an hour or so.

Click on Custom Extensions at the left. This should be empty, with a message stating “No custom extensions have been created.”

Click on Default Extensions and then click on the Edit button (it looks like a piece of paper and a pencil) to the right of .php | PHP 5.x. Click on Continue.

Click again on the Custom Extensions button on the left, and you should now see “Extension -> .php | Runs Under -> PHP 5.x”. Check your phpinfo page one more time, and it should report PHP 5.x.

It’s unfortunate we even have to do this for our older domains, but I asked the tech support guy if I could somehow get on to PHP 5.2.8, and he said nope, that the newer servers have the more recent version but the older servers are stuck back in 2007.

A friend who does a lot of selling on eBay asked me to develop a web application for generating HTML templates that could be copied and pasted into his auctions. He wanted to be able to add a title, a description, and some terms and conditions, and also to be able to upload images so the auction template would display thumbnails that could be clicked to display full-sized versions in a new window or tab. The more we talked, the more it sounded like something that would be both useful for a good number of people, and potentially a source of ad revenue for us. And so was born Simple Auction Wizard, an online HTML template generator for auction websites like eBay.

I was looking for just this kind of a project because I wanted a reason to play with TinyMCE, an Open Source, platform independent, web-based JavaScript HTML WYSIWYG editor control, and SWFUpload, a small JavaScript/Flash library that does very neat things with file uploads.

The most difficult part was actually getting the templates to look good in a variety of browsers. Because the template HTML appears inside of a larger page, I couldn’t rely on Doctype switching to place IE into standards mode. This meant the templates would have to be developed so that they would look approximately the same in standards-compliant browsers like Firefox, Chrome, and Safari, as well as horrible browsers like IE6 in quirks-mode. I tried very hard to minimize the use of tables, but they eventually crept in. I was able to use CSS, for the most part, though if any more issues come up, I’m going to throw in the towel and just do inline styles.

The web app is open to the public, but realize that it’s very early in its development and I intend to continue making changes.