Rob Ruchte (Rob Ruchte)

Website: http://thirdpartylabs.com


I'm an interactive developer specializing in custom content management and application development. I'm currently the owner and CTO of Thirdparty Labs, a boutique interactive shop in Raleigh, NC.


 

Posts by Rob Ruchte (Rob Ruchte):


Smarty base href Modifier

It appears that Safari 5 will not respect the base href tag when used in a page served via https when the base href indicates a URL with the http protocol. There is probably more to this issue, but I ran across it while working on an Authorize.net integration using their SIM API. After a successful transaction, Authorize.net requests a URL of the developer's choosing, and proxies the response through their server to the user's browser. This allows developers to do callback processing of the successful transaction on their server, and display a custom "thank you" page. Brilliant. The problem is, if you have any included CSS, JavaScript, or images in the page, you either need to give them all fully qualified URLs, or use the base tag to specify a root URL to use for all relative link attributes in the page (href, src, action, etc.). I've done this same type of integration many times, going back about six years, and have never had a problem using the base href in this scenario. Now, Safari 5 will completely ignore the base href and display a big mess of un-styled markup when you reach the end of the Authorize.net transaction flow.

I do a lot of work with the Smarty template engine, and the site I'm working on now is large, complex, and has a fairly advanced layout with lots of CSS and jQuery goodies. Everything is so abstracted, it's just not practical to construct a single template for my Authorize.net relay pages, of which there are several. It's also not practical to change the templates to use fully qualified URLs for everything. So what I did was write a simple Smarty modifier that prepends a specified URL to all of the relative src, href, and action attributes within the supplied string. It also fixes background: url() inline CSS styles. On my relay templates, all I have to do is wrap the contents of the template in a {capture} function, assign the output to a variable, and run it through my modifier. Absolute paths for all. Here's the modifier code, hopefully it will save someone a few grey hairs.

<?php
/**
 * Smarty plugin
 * @package Smarty
 * @subpackage plugins
 */

/**
 * Smarty base_href modifier plugin
 *
 * Type:     modifier<br>
 * Name:     base_href<br>
 * Purpose:  apply a base URL to all relative
 *				src, href, and background:url() attributes in a string
 * @author   Rob Ruchte <rob care of thirdpartylabs com>
 * @param string
 * @param string
 * @return string
 */
function smarty_modifier_base_href($string, $base_href)
{
	$base_href = (substr($base_href, -1)!='/') ? $base_href.'/':$base_href;

	$patterns = array();
	$patterns[] = '/href=\"[^#]\/?([^\"]*)\"/i';
	$patterns[] = '/src=\"\/?([^\"]*)\"/i';
	$patterns[] = '/action=\"\/?([^\"]*)\"/i';
	$patterns[] = '/background: ?url\(\'?\/?([^\'|\)]*)\'?\)/i';
	$patterns[] = '/background-image: ?url\(\'?\/?([^\'|\)]*)\'?\)/i';

	$replacements = array();
	$replacements[] = 'href="'.$base_href.'$1"';
	$replacements[] = 'src="'.$base_href.'$1"';
	$replacements[] = 'action="'.$base_href.'$1"';
	$replacements[] = 'background: url(\''.$base_href.'$1\')';
	$replacements[] = 'background-image: url(\''.$base_href.'$1\')';

    return preg_replace($patterns, $replacements, $string);
}

/* vim: set expandtab: */

?>

Usage:

{capture assign=output}
{include file="include/header.tpl" title="Event Ticket Registration"}
	<div class="span-3" id="leftbar">
		[...]
	</div>
	<div class="span-11 last" id="content">
		<div style="background: url(/images/feature.jpg);">
			[...]
		</div>
		<div class="span-11 last" id="copy">
		<div id="breadcrumbs">
			{foreach from=$breadcrumbs item=currNav name=breadcrumbs}
				{if !$smarty.foreach.breadcrumbs.last}<a href="{$currNav.path}">{/if}
				{$currNav.title}
				{if !$smarty.foreach.breadcrumbs.last}</a>&nbsp;&raquo;&nbsp;{/if}
			{/foreach}
		</div>
        <h2>Registration: {$event->getTitle()|escape}</h2>
	    {$purchaseSuccess}
        </div>
    </div>
{include file="include/footer.tpl"}
{/capture}
{$output|base_href:$smarty.const.BASE_URL}

,

Serving a Flash Socket Policy File From Processing

Last night I spent way too long trying to get AS3 to communicate with a simple socket server I wrote in Processing. I've done this kind of thing before and seemed to recall that it was pretty simple. But in the meantime, Adobe, in an effort to be more secure, has changed the Flash player to require a "socket policy file". The socket policy file is very similar to the familiar crossdomain.xml file that defines security permissions for HTTP access. Unfortunately, the socket policy file must either be sent on demand from the sockets that the player is attempting to access, or from port 843 on the host that the player is attempting to connect to. If all you want to do is run a quick and dirty socket server and have Flash clients connect to it, this is all a huge PITA.

Since ports below 1024 require root permissions in order for processes to use them on OS X, and I didn't want to run some other server process just to serve policy files, I needed to kludge a way to send the policy file from my nice, elegant socket server every time a client connected and requested a policy file. The example below is a simplified version, as it only listens on one port - the project I was working on was basically a proxy from one SWF to another, so I had two socket servers that needed to listen for the requests for a policy file and respond. In this example, the server listens for connections on port 5208, and simply echoes incoming data from the client to System.out, and has some visual feedback in the window. When the incoming message contains the string "policy-file-request" (the entire message from Flash will be terminated with a null char), we simply spit back the XML for a wide-open socket policy, followed by a null char (This is required, or the Flash player will not accept the policy file. This is what tied me in knots last night. RTFM!)

import processing.net.*;

int bgLevel = 0;
int port = 5208;
Server server;

String flashDomainPolicy = "<?xml version=\"1.0\"?>"
                              +"<cross-domain-policy xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://www.adobe.com/xml/schemas/PolicyFileSocket.xsd\">"
                              +"<allow-access-from domain=\"*\" to-ports=\"*\" secure=\"false\" />"
                              +"<site-control permitted-cross-domain-policies=\"all\" />"
                              +"</cross-domain-policy>";

void setup ()
{
  size(200, 200);
  server = new Server(this, port);
  background(bgLevel);
}

void draw ()
{
  Client client = server.available();

  if (client !=null)
  {
    String message = trim(client.readString());

    if (message != null)
    {
      if (match(message,"policy-file-request") != null)
      {
        sendFlashPolicy(server);
      }
      else
      {
        server.write(message);
        System.out.println(message);

        // Change the background color to indicate message activity
        bgLevel = 255;
      }
    }
  }

  // Fade to black
  if (bgLevel>1)
  {
    bgLevel-=2;
    background(bgLevel);
  }
}

void sendFlashPolicy(Server socketServer)
{
    socketServer.write(flashDomainPolicy+char(0));
    System.out.println("Sending Flash policy file");
}

You can test this without a Flash client by firing up the sketch and telnetting to the port. Any strings you send will be echoed to System.out, unless you send the string "policy-file-request" in your message, which will result in the server sending the policy XML to you. You should be able to connect a Flash client to this server and start communicating right away.

, , , ,

Recently, Blogger began appending a tracking gif to the content of each entry in their Atom feeds. The URL used in the image src uses https, most likely to avoid warnings when it's rendered in a https context. For some reason, when rendering the feed content containing the tracking image, the Flash player can crash, taking the browser with it on certain platform/browser combinations. We found the problem in FireFox 3.0 on OSX, but only on PPC Macs. Go figure.

In our case, we are proxying the Atom feed through a PHP script so we can display the feed contents to user agents without the Flash player. This made it fairly easy to iterate through the entries, and with a simple bit of regex, strip out the offending markup from the contents.

Blogger is wrapping the image tag in a div with a very specific CSS class, which makes our job easy:

foreach($feed->entries as $currEntry)
{
    
$currEntry->content ereg_replace('<div class=\"blogger-post-footer\">.*</div>'''$currEntry->content);
}

Depending on what you're using to parse the feed, you may or may not need to be concerned about decoding and encoding html entities during this process.

, ,

I just ran across a really annoying problem with Thickbox after upgrading from jQuery 1.3.1 to 1.3.2. The gallery functionality in Thickbox broke after the upgrade - instead of opening with the first image, the loading animation displayed forever without loading any content at all.

Turns out, the @ selector syntax was deprecated in jQuery 1.3, and was removed in 1.3.2. Simply removing the single occurrence of the @ character in the Thickbox js solved the problem.

ClipStation Clipboard Writer 2.0 Released

ClipStation is a free lightweight solution for writing to your user’s clipboard from an HTML page. Using a small SWF that is embedded dynamically via JavaScript, you can pass an unlimited number of content clips onto the clipboard.

ClipStation is designed to be lightweight, flexible, and easy to implement. What makes ClipStation different from other clipboard SWF solutions is the ability to decode HTML character entities, allowing you to pass complex HTML markup to the clipboard from within form elements, divs, pre tags, etc. We developed ClipStation for use on a widget sharing page we've implemented for a client. After looking around for a good lightweight cross-browser solution and coming up empty handed, we decided to build our own. We're now happy to offer it to you at the low, low price of free.

Version 2.0 includes changes to allow access to the clipboard in Flash Player 10. Adobe changed the security requirements for clipboard access in version 10 of the player; now a user action is required before a SWF may access the clipboard. Instead of using a single hidden instance of the ClipStation SWF, we embed an instance for every clip that the user clicks to perform the clipboard copy. A source distribution is available, so you can change the design to fit your needs.

More information and the release package can be found at thirdpartylabs.com/clipstation/

»Download ClipStation 2.0

, , ,

Smarty File Size Modifier

Here's a simple Smarty modifier that will format an integer that represents the number of bytes in a file as a human readable string.

Usage:
{$fileSizeInBytes|file_size}

Example:
{assign var=fileSizeInBytes value=10485760}
{$fileSizeInBytes|file_size}

{assign var=fileSizeInBytes value= 768000}
{$fileSizeInBytes|file_size}

{assign var=fileSizeInBytes value=303}
{$fileSizeInBytes|file_size}

Output:
10 MB
750 Kb
303 bytes

<?php
/**
 * Smarty plugin
 * @package Smarty
 * @subpackage plugins
 */

/**
 * Smarty file_size modifier plugin
 *
 * Type:     modifier<br>
 * Name:     file_size<br>
 * Purpose:  format file size represented in bytes into a human readable string<br>
 * Input:<br>
 *         - bytes: input bytes integer
 * @author   Rob Ruchte <rob at thirdpartylabs dot com>

 * @param integer
 * @return string
 */
function smarty_modifier_file_size($bytes=0)
{
    $mb = 1024*1024;

    if ($bytes > $mb)
    {
        $output = sprintf ("%01.2f",$bytes/$mb) . " MB";
    }
    elseif ( $bytes >= 1024 )
    {
        $output = sprintf ("%01.0f",$bytes/1024) . " Kb";
    }
    else
    {
        $output = $bytes . " bytes";
    }

    return $output;
}

/* vim: set expandtab: */

?>

,

Helpful Automator Workflows for Developers

I love OS X. For me, it's the ideal development platform. I primarily build web applications that run on the LAMP stack, so having a POSIX compliant UNIX like OS for my daily driver is extremely convenient. My development environment works just like the environments I deploy to, so I don't have to worry very much about platform inconsistencies. OS X is the perfect balance of elegance and power. I only wish Finder didn't suck so hard.

There are some actions I need to perform on files on a regular basis that are not supported in Finder. For example, recursively deleting files in a tree of directories, but leaving the directory structure intact. I have a code generation tool that scaffolds out a boatload of PHP classes based on MySQL database. During heavy development, I need to blow away the files on a regular basis without deleting the output directories. Finder simply cannot do this. Luckily Apple has provided a way to add Automator actions to the context menu in Finder. I put together a simple workflow to recursively delete files in a tree of folders, here is is for your convenience:

DeleteFolderContents.zip - MD5: 5d0ad832e9e998dbefd70d49f2a07b52

Unzip, and copy the workflow file to Library/Workflow/Applications/Finder/ in your home directory. If all has gone well, you should have a new option in the context menu when you right-click a folder in Finder:

Automator Workflow As Finder Plugin

Automator Workflow As Finder Plugin

Another Finder deficiency that was bugging Jon recently is the inability to copy the filesystem path of a folder in Finder to your clipboard. I put together a little workflow that does just that:

CopyPathToClipboard.zip - MD5: b64558cf3afb7aecdec63faccb5986ba

Follow the same steps outlined above to copy the workflow to the proper location.

Note that these workflows are compatible with OS X version 10.5, and will not run in Automator on 10.4 or previous systems.

, , ,

SEO Presentation for NCSU Web Developer Group

Jon and I delivered a presentation about SEO to the NCSU Web Developer Group this afternoon. If you were in attendance, thanks very much for coming to see us, we hope you found it informative. We talked about a lot of useful online resources during the presentation, all of the links are in the slide deck that we've posted for your convenience. You can download a PDF of the slides here, or view them on SlideShare

Please feel free to post comments and questions here.

One of my partners just informed me that Microsoft's Windows Mail that currently ships with Vista suffers from the same URI encoding issues that I discussed in a previous post.

"I have just discovered that the MS Mail client on Vista has the same problem as Apple Mail, when it comes to handling urls that include a "hash" component.  The hash-sign gets url encoded before it is sent out to the browser, and so the browser thinks it's part of the url and sends it on to the server, rather than treating it as a hash.

I sent someone a link to the [***] stuff I did, and it got busted by their mail -- When I finally figured out what was happening, I had to pause briefly and confirm that they weren't using a Mac.

It's so simple it kills me... and MS and Apple are both supposed have the best minds in the world working on this stuff !?

If you ask me, this is pretty good proof that Vista is heavily based on on OSX (conceptually, that is).  I mean... they've even copied the bugs!"

, , , ,

Lazy developers, good libraries, and The 80/20 Rule

I was searching for something or other related to SWFAddress the other day, and ran across a blog post talking about the launch of SWFObject2 and SWFAddress2, and how handy they were for building usable Flash sites. No surprises there, they are in fact very handy. What caught my attention was this comment on the post:

"We were looking at SWF Address a while ago. While it’s very cool it was useless for what we wanted. By using HTML anchors (# in the URL) the deep links are only relevant to client side logic inside the browser (JavaScript, HTML and Flash).

If you serve HTML content generated server side, in addition to flash there is no way to extract the deep link from the URL (after the #) as it’s not passed to the server in the request!

I will be interested to see if they have updated this in the new version… and also see what changes have been made to SWFObject2, or swffix or whatever it’s called these days."

I had to read that twice.

SWFAddress is not a solution for implementing multiple content types. It's purpose in life is to keep the browser informed about the user's movements within a rich application. It does that job very well. One of the things you can do with SWFAddress is implement your own solution for managing your URLs across multiple content types.

Jon and I are just finishing up a big client site for which we have a nifty flash client and an SEO/Usability optimized HTML version of all content on the site. SWFAddress allowed us to maintain a similar URI structure for both, and a little bit of custom javascript handles the translation of SWFAddress URIs, which all start with # to the standard URIs, and vice-versa. SWFObject handles the embedding of the Flash client. It's a really simple solution that took less than an afternoon to prototype and refine into a production-ready solution. I'm not saying this to make myself out to be a bad-ass, it's just not rocket science. All it takes is a general understanding of how the tools work, and the willingness to craft your own solutions to your specific problems.

There are well built, elegant, open source libraries to do just about anything these days. The thing is, they're libraries, tools, not complete solutions. They will solve the tough 80% of your problem for you. It's up to you to do the last 20% and make the tool work for you. Enter the 80/20 rule, or, more specifically, the 2080 concept.

The 20 missing percent of functionality will take up 80% of the build time.

If you expect canned solutions to solve your problems 100%, you will constantly be disappointed, and, most likely, be building poor software. If you go into your project knowing that a tool you will rely on provides a limited set of functionality, and you will need to do devote time and energy to make it fit, you will be more likely to succeed.

"There is something to be learned from a rainstorm. When meeting with a sudden shower, you try not to get wet and run quickly along the road. But doing such things as passing under the eaves of houses, you still get wet. When you are resolved from the beginning, you will not be perplexed, though you still get the same soaking. This understanding extends to everything."

-Yamamoto Tsunemoto, Hagakure

, , , , ,