fill the void

WordPress Tip: Recent, Relevant, and Random Blog Posts

12 May 2010 Update: Interestingly, Google Analytics revealed that in the month since adding the sidebar, there’s been no significant change in the average time spent on the site (54s) or pages per visit (1.3). I still like it.

Recently, I thought it would be interesting to add a sidebar to my WordPress blog. I figured a short list of recent posts, relevant posts, and random posts would help surface information people might find useful. Here’s a quick rundown of how to add these with the PHP code below:

  • Recent Posts: one-liner with simple WP PHP call
  • Random Posts: a couple lines because I wanted random posts but sorted descending by date (not simply get_posts('numberposts=5&orderby=rand&order=desc');)
  • Relevant Posts: also called related posts, a template-based plugin worked best for me: Yet Another Related Posts Plugin (YARPP)
<div class="sidebar">
<b>Recent</b>
<ul>
<?php wp_get_archives('title_li=&type=postbypost&limit=5'); ?>
</ul>
<?php related_posts(); ?>
<b>Random</b>
<ul>
<?php
   // Get five random posts.
   $rand_posts = get_posts('numberposts=5&orderby=rand');
   // Sort them (by date).
   asort($rand_posts);
   // Reverse them.
   $rand_posts = array_reverse($rand_posts);
   foreach( $rand_posts as $post ) :
?>
    <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php endforeach; ?>
</ul>
</div>

Multicast Ping on a Mac

While networks are awesome in the abstract, actual implementations are messy. Take multicast traffic. Network engineers can simply disable it, and I’m sure they have perfectly valid reasons for it. But as an uninformed user, what do you do if a multicast-enabled app doesn’t work? Is the failure the app’s fault, or is the network to blame? Enter Multicast Ping, available as an app download (PPC/Intel 10.4+) and an Xcode 3.2 project on GitHub.

Multicast Ping is a tool for testing multicast connectivity. It tests both directions, sending multicast packets to the supplied address/port while also listening for multicast packets on that same address/port. Run it on two Macs, and the app will let you know if the computers see each other. Both copies should display connections; if they don’t, the problem is in the network.

The icon is courtesy of Artua Design Studio via Iconspedia.

Multicast Code in Objective-C

There are a couple great tools for testing multicast connectivity: ssmping for Windows and Linux and mctester for python (which means most modern systems). However, I couldn’t find any equivalent implementations in Objective-C for the Mac, so I used mctester as a reference to write this Objective-C/Cocoa version.

The code (see below) is straight-forward: two methods for creating send and receive sockets and two methods for sending and receiving the multicast traffic. Take a look at the GitHub repo to see the code in context.

#pragma mark -
#pragma mark Send

- (void)createMulticastSendSocket {
	sendSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	result = setsockopt(sendSocket, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,(socklen_t)sizeof(ttl));
	result = setsockopt(sendSocket, IPPROTO_IP, IP_MULTICAST_LOOP, &loop,(socklen_t)sizeof(loop));
}

- (void)sendMulticast:(id)param {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

	int seqno = 0;
	while (sendSocket != 0) {
		// Initialize address.
		struct sockaddr_in serverAddress;
		size_t namelen = sizeof(serverAddress);
		bzero(&serverAddress, namelen);
		serverAddress.sin_family = AF_INET;
		result = inet_aton([address cStringUsingEncoding:NSASCIIStringEncoding], &serverAddress.sin_addr);
		serverAddress.sin_port = htons(port);

		// Send packet.
		result = sendto(sendSocket, [message cStringUsingEncoding:NSASCIIStringEncoding], [message length], seqno, (struct sockaddr *)&serverAddress, namelen);
		seqno++;

		// Sleep for 1s.
		[NSThread sleepUntilDate:[[NSDate date] addTimeInterval:1]];
	}

	[pool drain];
}

#pragma mark -
#pragma mark Receive

- (void)createMulticastReceiveSocket {
	int flag = 1;

	receiveSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	struct sockaddr_in serverAddress;
	size_t namelen = sizeof(serverAddress);
	bzero(&serverAddress, namelen);
	serverAddress.sin_family = AF_INET;
	serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
	serverAddress.sin_port = htons(port);
	result = setsockopt(receiveSocket, SOL_SOCKET, SO_REUSEADDR, &flag,(socklen_t)sizeof(flag));
	result = bind(receiveSocket, (struct sockaddr *)&serverAddress, (socklen_t)namelen);
	if (result != 0) {
		// Couldn't bind. Port probably in use. Let the parent process know then terminate.
		[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:[@"port in use" dataUsingEncoding:NSUTF8StringEncoding]];
		exit(1);
	}

	struct ip_mreq  theMulti;
	result = inet_aton([address cStringUsingEncoding:NSASCIIStringEncoding], &theMulti.imr_multiaddr );
	result = inet_aton([LOCAL_ADDRESS cStringUsingEncoding:NSASCIIStringEncoding], &theMulti.imr_interface );
	result = setsockopt(receiveSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &theMulti,(socklen_t)sizeof(theMulti));
}

- (void)receiveMulticast:(id)param {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

	char receivedLine[1000];
	while (receiveSocket != 0) {
		bzero(&receivedLine, 1000);
		struct sockaddr_in receiveAddress;
		socklen_t receiveAddressLen = sizeof(receiveAddress);

		// Receive packet.
		result = recvfrom(receiveSocket, receivedLine, 1000, 0, (struct sockaddr *)&receiveAddress, &receiveAddressLen);
		if(result > 0) {
			// Extract address, port, message.
			UInt16 sentPort = ntohs(receiveAddress.sin_port);
			char addressBuffer[INET_ADDRSTRLEN];
			inet_ntop(AF_INET, &receiveAddress.sin_addr, addressBuffer, sizeof(addressBuffer));
			NSString *sentHost = [NSString stringWithCString:addressBuffer encoding:NSASCIIStringEncoding];
			NSString *receivedMessage = [NSString stringWithCString:receivedLine encoding:NSASCIIStringEncoding];

			// Write out the new message to the pipe.
			NSString *line = [NSString stringWithFormat:@"%@,%d,%@", sentHost, sentPort, receivedMessage];
			[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:[line dataUsingEncoding:NSUTF8StringEncoding]];
		}
		else {
			NSLog(@"result %d", result);
		}

		// Sleep for 1s.
		[NSThread sleepUntilDate:[[NSDate date] addTimeInterval:1]];
	}

	[pool drain];
}

Tracking and Staging WordPress Changes

Making template/theme changes to a live blog is a bit frustrating. While mucking around with CSS or a theme header, I occasionally render this blog completely illegible or obviously broken, then I scramble to revert my changes, hoping I remember the original code correctly. Editing is a pain without source control or a staging process and very different from app development. Too different. So, I figured out how to track all my WordPress changes with git and stage them on a subdomain on DreamHost.

Tracking Changes

I’m using git and GitHub, but any scm would be fine. I’ll run through the steps here. (Keep in mind I use WordPress’s update system for version and plugin updates; I just want to track my own changes.)

  1. Removed unused themes and plugins to reduce installation size to 25MB (du -h)
  2. Created a private repo on GitHub
  3. Setup git in blog’s root directory on DreamHost:
    git init
    # Didn't bother ignoring any files.
    git add .
    git commit -m "current state of blog"
    git remote add origin git@github.com:username/myblog.git
    git push origin master
    
  4. Edited the CSS file using WordPress’s editor then listed the changes (git status) and diff’d the changed file using git (git diff style.css)
  5. Decided my edits didn’t look good on the live blog and reverted them (git checkout style.css or git reset HEAD --hard)
  6. Ran mysqldump then gzip on the blog db and added it to git also
  7. Updated .htaccess to hide the new .git directory (DreamHost): RedirectMatch 403 /\..*$

Seeing the changed files in git is fantastic, and at this point, I wonder how I got by for so long without versioning my WordPress changes. But tracking changes isn’t enough.

Staging Changes

Now, I can easily revert horrible template changes, but anyone hitting the blog during that time will still see it. It reminds me of how the early Facebook team used to directly edit the HTML on the production site when they were adding features. Debugging changes on a live site is annoying and unnecessary. Instead, just use a subdomain. Here are my steps:

  1. Created a subdomain on DreamHost for the staging area and assigned it a private IP/enabled SSL (I require SSL for administration)
  2. Pulled the current repo in that new subdomain’s root directory on DreamHost:
    git init
    git remote add origin git@github.com:username/myblog.git
    git pull origin master
    
  3. Pointed this instance of WordPress to the new subdomain by adding WP_HOME and WP_SITEURL in /wp-config.php according to WordPress’s “Changing The Site URL” page (I want to use the same database so I didn’t change those entries)
  4. Waited a couple hours while the DNS changes propagated for the new subdomain
  5. Logged into WordPress on the subdomain and changed a couple lines of CSS
  6. Checked out the result on the subdomain and committed it to GitHub
  7. Pushed the commit to GitHub and pulled it down in the live blog
  8. Verified the change on the live blog

Staging changes is so much more comforting. Before, I wondered how many visitors hit my site mid-change (which sometimes took hours) and left annoyed. At least now I can think, “Actually, I designed it that way intentionally…”


ibtool “Missing object” Confusion

This week, I ran one of my ibtool ruby scripts to localize an app. During the process, it generated the following error:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.ibtool.errors</key>
	<array>
		<dict>
			<key>description</key>
			<string>Missing object referenced from oid-keyed mapping. Object ID 538</string>
		</dict>
	</array>
</dict>
</plist>

The error sounds innocuous. I searched on Google for it and got literally no results. Unfortunately, the result is quite bad: a partially localized XIB/NIB. The error means ibtool has encountered a string in the strings file with an ObjectID that isn’t present in the source XIB file. In my case, I had deleted a placeholder string from an NSTextFieldCell element. When ibtool is unable to find this ObjectID in the source XIB, it stops localizing the new XIB. Not a pretty bug (rdar bug #7801770).

To show the confusing result, I reproduced this problem in a new project:

  1. Create a new Mac OS X app project in Xcode.
  2. Open Interface Builder and add a button to the main window in MainMenu.xib.
  3. Generate MainMenu.strings: ibtool --generate-strings-file MainMenu.strings English.lproj/MainMenu.xib.
  4. Open MainMenu.strings and do a search/replace "; for 1"; so that every element name is touched.
  5. Generate the localized XIB with a button: ibtool --strings-file MainMenu.strings --write MainMenu.with.button.xib English.lproj/MainMenu.xib.
  6. Open English.lproj/MainMenu.xib and remove the button. Save that.
  7. Generate the localized XIB with a button: ibtool --strings-file MainMenu.strings --write MainMenu.without.button.xib English.lproj/MainMenu.xib. This command should generate the error.
  8. Dump the strings for first localized XIB: ibtool --generate-strings-file MainMenu.with.button.strings MainMenu.with.button.xib.
  9. Dump the strings for second localized XIB: ibtool --generate-strings-file MainMenu.without.button.strings MainMenu.without.button.xib.
  10. Diff the strings files with Changes: chdiff MainMenu.with.button.strings MainMenu.without.button.strings.
  11. Diff the XIB files with Changes: chdiff MainMenu.with.button.xib MainMenu.without.button.xib.

The problem is clearly visualized in the two screenshots below, taken from Changes. Diff’ing the XIB files shows that ibtool stops localizing the new XIB when it cannot find the missing ObjectID. Look at Changes’ scrollbar marks.

However, diff’ing the strings files makes it far less clear how ibtool failed. Changes’ scrollbar marks are distributed across the entire file.

Yet another reason to always verify the newly-created localized XIBs by comparing strings files… :)


iPhone Tip: Larger Hit Area for UIButton

2010-08-18 Update: I added a section explaining how to programmatically grow the touchable area of a UIButton via hitTest.

Apple’s iPhone HIG estimates 44×44 pixels is a good hit area for a UI element. If the button isn’t that big, don’t let the user realize that: make the hit area larger than the button.

The UINavigationItem button is an excellent example of this larger hit area. Try touching near (but not on) the standard “Back” button in any navigation-based iPhone app. It responds. I never even noticed this interaction until I started this post, and that’s precisely the goal.

But when Apple doesn’t auto-extend the hit area, the developer should. There are two approaches:

A Larger Target

I can programmatically grow the area of the button by subclassing UIButton and overriding UIView::hitTest:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
	int errorMargin = 30;
	CGRect largerFrame = CGRectMake(0 - errorMargin, 0 - errorMargin, self.frame.size.width + errorMargin, self.frame.size.height + errorMargin);
	return (CGRectContainsPoint(largerFrame, point) == 1) ? self : nil;
}

An Invisible Button

For the invisible button, use Interface Builder to create a large UIButtonTypeCustom UIButton, hook up its touchDown, touchUpInside, touchUpOutside events to custom selectors in the controller, and use UIButton::setHighlighted on the original button to mimic a real tap, as described in this spot-on StackOverflow comment. (And don’t try setting the button’s alpha to zero; the button will no longer respond to touch events.)

Either way, an oversized hit area is especially necessary for smaller buttons, like the 29×31 “Detail Disclosure” button (UIButtonTypeDetailDisclosure) or the 18×19 “Info Light” button (UIButtonTypeInfoLight). Try simply adding the “Info Light” i button to a blank screen, running it on a device, and tapping it; it’s frustratingly small.