kelvinluck.com

a stroke of luck

Error installing plugins in Eclipse on OSX


I’ve run into this problem twice in the last couple of days while downloading and installing new versions of Eclipse and trying out different packages for PHP editing (PDT and Aptana Studio). First time I googled around I couldn’t find a solution and so had to figure it out myself. Second time I thought that maybe I should make a note of my solution as other people might run into it as well.

The problem shows itself with the following error message:

An internal error occurred during: "Computing size".
Incompatible profile file name. Expected format is {timestamp}.profile but was ._1248736262197.profile.

This happens when trying to install a new plugin through the “Check for updates” or “Install New Software” dialog. The problem is because of the hidden ._* files which are created by OS X inside the Eclipse directory. The solution is pretty easy – open up a terminal and:

cd /path/to/your/eclipse/folder
find . -iname '._*' -exec rm -rf {} \;

WARNING – running commands like this on your computer can cause problems. I have run exactly the command above on my machine with no problem but if you manage to break your computer following the above instructions then I take no responsibility! That said, as long as you correctly cd into the Eclipse directory and type/ copy the command correctly then nothing should go wrong!

Hope that helps someone else who runs into the problem!



Google maps for flash marker clustering


I’ve recently been working on a project which makes extensive use of the Google maps API for flash (more about that once it launches). One of the things that was necessary for this project was clustering of markers when they were too close together. To understand what I mean by this click the image below to check out the example:

Google Maps for Flash Clustering Screenshot

As you can see, the capital cities of the world are all displayed on the map as small red dots. I got the list of capital cities from here and converted them to XML for the example – note that some of them (e.g. Rome) appear to be slightly incorrectly positioned. If the cities are too close to each other for the current zoom level then they are clustered into larger red dots with numbers in the middle.

This has of course been done before (e.g. here and here) but the solutions didn’t work for my situation. The first is grid based (rather than distance based) which can give some strange results. And more importantly for my project I needed to use custom markers (for both the individual markers and the clusters) and that didn’t seem possible without changing the actual library code. And with the second solution the markers seem to “jiggle” as you drag the map and I wasn’t sure about whether the license permitted use in a commercial project.

So I found a post from Mika Tuupola. In it he explains the advantage of distance based (as opposed to grid based) clustering algorithms. He then provides some PHP sourcecode to implement distance based clustering.

I started off with a fairly straightforward port of the code from the article but I found that it needed to be optimised quite heavily so that it would work performantly in Flash (especially since it needs to re-calculate the clustering on every zoom change). My final Cluster class looked like this:

package com.kelvinluck.gmaps
{
   import com.google.maps.overlays.Marker;

   import flash.geom.Point;
   import flash.utils.Dictionary;

   /**
    * Distance based clustering solution for google maps markers.
    *
    * <p>Algorithm based on Mika Tuupola's "Introduction to Marker
    * Clustering With Google Maps" adapted for use in a dynamic
    * flash map.</p>
    *
    * @author Kelvin Luck
    * @see http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google-maps
    */

   public class Clusterer
   {
     
      public static const DEFAULT_CLUSTER_RADIUS:int = 25;

      private var _clusters:Array;
      public function get clusters():Array
      {
         if (_invalidated) {
            _clusters = calculateClusters();
            _invalidated = false;
         }
         return _clusters;
      }

      private var _markers:Array;
      public function set markers(value:Array):void
      {
         if (value != _markers) {
            _markers = value;
            _positionedMarkers = [];
            for each (var marker:Marker in value) {
               _positionedMarkers.push(new PositionedMarker(marker));
            }
            _invalidated = true;
         }
      }

      private var _zoom:int;
      public function set zoom(value:int):void
      {
         if (value != _zoom) {
            _zoom = value;
            _invalidated = true;
         }
      }

      private var _clusterRadius:int;
      public function set clusterRadius(value:int):void
      {
         if (value != _clusterRadius) {
            _clusterRadius = value;
            _invalidated = true;
         }
      }

      private var _invalidated:Boolean;
      private var _positionedMarkers:Array;

      public function Clusterer(markers:Array, zoom:int, clusterRadius:int = DEFAULT_CLUSTER_RADIUS)
      {
         this.markers = markers;
         _zoom = zoom;
         _clusterRadius = clusterRadius;
         _invalidated = true;
      }

      private function calculateClusters():Array
      {
         var positionedMarkers:Dictionary = new Dictionary();
         var positionedMarker:PositionedMarker;
         for each (positionedMarker in _positionedMarkers) {
            positionedMarkers[positionedMarker.id] = positionedMarker;
         }
         
         // Rather than taking a sqaure root and dividing by a power of 2 to calculate every distance we
         // do the calculation once here (backwards).
         var compareDistance:Number = Math.pow(_clusterRadius * Math.pow(2, 21 - _zoom), 2);
         
         var clusters:Array = [];
         var cluster:Array;
         var p1:Point;
         var p2:Point;
         var x:int;
         var y:int;
         var compareMarker:PositionedMarker;
         for each (positionedMarker in positionedMarkers) {
            if (positionedMarker == null) {
               continue;
            }
            positionedMarkers[positionedMarker.id] = null;
            cluster = [positionedMarker.marker];
            for each (compareMarker in positionedMarkers) {
               if (compareMarker == null) {
                  continue;
               }
               p1 = positionedMarker.point;
               p2 = compareMarker.point;
               x = p1.x - p2.x;
               y = p1.y - p2.y;
               if (x * x + y * y < compareDistance) {
                  cluster.push(compareMarker.marker);
                  positionedMarkers[compareMarker.id] = null;
               }
            }
            clusters.push(cluster);
         }
         return clusters;
      }
   }
}

import com.google.maps.LatLng;
import com.google.maps.overlays.Marker;

import flash.geom.Point;

internal class PositionedMarker
{

   public static const OFFSET:int = 268435456;
   public static const RADIUS:Number = OFFSET / Math.PI;
   
   // public properties are quicker than getters - speed is important here...
   public var position:LatLng;
   public var point:Point;

   private var _marker:Marker;
   public function get marker():Marker
   {
      return _marker;
   }

   private var _id:int;
   public function get id():int
   {
      return _id;
   }

   private static var globalId:int = 0;

   public function PositionedMarker(marker:Marker)
   {
      _marker = marker;
      _id = globalId++;
      position = marker.getLatLng();
     
      var o:int = OFFSET;
      var r:Number = RADIUS;
      var d:Number = Math.PI / 180;
      var x:int = Math.round(o + r * position.lng() * d);
      var lat:Number = position.lat();
      var y:int = Math.round(o - r * Math.log((1 + Math.sin(lat * d)) / (1 - Math.sin(lat * d))) / 2);
      point = new Point(x, y);
   }
}

You can download the class and the code for the example from it’s github repository. Note that the Clusterer class is all that you need to use – the rest of the classes are just for the sake of the example. I hope it’s useful – if you make anything cool with it then please post in the comments.



Custom 404 error messages with CodeIgniter


CodeIgniter is an “open source PHP web application framework that helps you write kick ass PHP programs”. It’s a nice MVC framework which makes writing PHP bearable by enforcing some organisation and structure on your project. I’ve used it a few times, most recently when I built the Sharify website.

CodeIgniter provides helpful templates for certain error states of your application including a 404 Page not found. And you can edit these templates to suit your needs. Unfortunately these templates are static view code and in an error situation you can’t easily query a database or access most of the functionality of CodeIgniter. This is quite limiting – in my case I couldn’t render my error view into the standard template view I use on the rest of the site. And so I came up with a bit of a hack to make a 404 error behave like any other page request.

I started by creating a method on my default controller (home) called “page_not_found”. Inside this method I generate my error page (by inserting the relevant view into my main template view as I do throughout the rest of the site). Since this is a normal controller method I can interact with any models and views I desire (for example if I needed to grab stuff from the database to build my navigation I could) to produce my output page.

Then I need to override the CI_Exceptions class. So I create the file MY_Exceptions.php inside CI_DIR/application/libraries (where CI_DIR is the path to your codeigniter system folder). Inside that file I override the show_404 method and instead of simply outputting a view I use cURL to request the page I previously set up. I also POST the originally requested URI along with this request which allows me to output it on the generated page if desired.

The code looks like this:

class MY_Exceptions extends CI_Exceptions {
   
   function show_404($page = '')
   {
      $code = '404';
      $text = 'Page not found';
     
      $server_protocol = (isset($_SERVER['SERVER_PROTOCOL'])) ? $_SERVER['SERVER_PROTOCOL'] : FALSE;
   
      if (substr(php_sapi_name(), 0, 3) == 'cgi')
      {
         header("Status: {$code} {$text}", TRUE);
      }
      elseif ($server_protocol == 'HTTP/1.1' OR $server_protocol == 'HTTP/1.0')
      {
         header($server_protocol." {$code} {$text}", TRUE, $code);
      }
      else
      {
         header("HTTP/1.1 {$code} {$text}", TRUE, $code);
      }
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, 'http://' . $_SERVER['HTTP_HOST'] .'/home/page_not_found/');
      curl_setopt($ch, CURLOPT_HEADER, 0);
      curl_setopt($ch, CURLOPT_POST, 1);
      curl_setopt($ch, CURLOPT_POSTFIELDS, 'originalURL=' . urlencode($_SERVER['REQUEST_URI']));
      curl_ex ec($ch); // WordPress won't let me post the word e x e c - remove the space in the previous code to make it run...
      curl_close($ch);
   }
}

As you can see, I make sure that the correct HTTP header is outputted and then I pass through the page loaded from my “page_not_found” method on my default “home” controller. This means if you visit a non existent page on the Sharify site (like this one) then you get a pretty page which looks like the rest of the site and tries to help you find what you are looking for.

In a perfect world there would be a cleaner way to do this within the CodeIgniter framework but I did some searching when I started building the site and I couldn’t find any clean solutions. There were a couple of other suggestions on the forums but they seemed to involve editing more files which shouldn’t be related to error handling. While I don’t like the hacky nature of the extra curl request I think this solution is relatively simple and clean.



krl_flickr_photoset released


Update:

This is a very old page imported from my previous blog. If there is missing content below or anything that doesn’t make sense then please check the page on my old blog.

A little while ago I added some code to display my Flickr photosets on my photos page. I got an email requesting the code so I just released it in my projects section. It takes the form of a Textpattern plugin but I’ve also released the raw PHP code for people without Textpattern. Enjoy!



Source for tag complete in del.icio.us example


Update:

This is a very old page imported from my previous blog. If there is missing content below or anything that doesn’t make sense then please check the page on my old blog.

When I released my tool for Tag autocompletion when posting to del.icio.us it didn’t occur to me that people might not be comfortable posting their username and password to my server. But thinking about it this is a reasonable concern. Rather than just ask you to take my word for it that I’m not harvesting usernames and passwords I decided to release the source for the app and make it so that you can host it on your own server…

First up, the PHP. When you log into the app it connects to a PHP script for two reasons:

  • It checks that del.icio.us is contactable and that your login details are correct.
  • It retrieves a list of your tags for use in the autocomplete

This script is called get_tags.php and looks like this:

/**
 * get_tags.php
 *
 * Connects to the del.icio.us server and retrieves a list of tags for a given user
 * and returns this or a sensible error code.
 *
 * Kelvin Luck < kelvin at kelvinluck dot com >
 * This code is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike License
 * <http ://creativecommons.org/licenses/by-nc-sa/2.0/>
 */


define("_URL_GET_TAGS", "http://del.icio.us/api/tags/get?");

$username = isset($_POST["username"]) ? $_POST["username"] : "nobody";
$password = isset($_POST["password"]) ? $_POST["password"] : "thisiswrong";

$cu = curl_init(_URL_GET_TAGS);
curl_setopt($cu, CURLOPT_RETURNTRANSFER, true);
curl_setopt($cu, CURLOPT_USERPWD, "$username:$password");
curl_setopt($cu, CURLOPT_USERAGENT, "kelvinluck.com del.icio.us poster (PHP-" . phpversion() . ")");

$method_result = curl_exec($cu);
$result_info = curl_getinfo($cu);
curl_close($cu);

switch ($result_info["http_code"]) {
    case 200:
        $p = strpos($method_result, "< ", 1);
        $content = substr($method_result, 0, $p)."<result status=\"1\">\n".substr($method_result, $p)."";
        break;
    case 401:
        $content = "< ?xml version='1.0' standalone='yes'?><result status=\"0\" message=\"HTTP401 - Bad login details.\" />";
        break;
    case 503:
        $content = "< ?xml version='1.0' standalone='yes'?><result status=\"0\" message=\"HTTP503 -  Gateway timeout.\" />";
        break;
    default:
        $content = "< ?xml version='1.0' standalone='yes'?><result status=\"0\" message=\"HTTP".$result_info["http_code"]." -  Unknown.\" />";
        trigger_error("I don't know what to do with http code:".$result_info["http_code"], E_USER_ERROR);
        break;
}

header("Content-Type: text/xml");
echo $content;

Then when you actually post your page the app connects to another PHP script called post_page.php which looks like this:

/**
 * post_page.php
 *
 * Connects to the del.icio.us server and posts a given page to the given
 * user's account.
 *
 * Kelvin Luck < kelvin at kelvinluck dot com >
 * This code is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike License
 * <http ://creativecommons.org/licenses/by-nc-sa/2.0/>
 */


define("_URL_POST", "http://del.icio.us/api/posts/add?");

$username = isset($_POST["username"]) ? $_POST["username"] : "nobody";
$password = isset($_POST["password"]) ? $_POST["password"] : "thisiswrong";

$post_data = array();
$post_data["url"] = isset($_POST["url"]) ? $_POST["url"] : "";
$post_data["description"] = isset($_POST["description"]) ? $_POST["description"] : "";
$post_data["extended"] = isset($_POST["extended"]) ? $_POST["extended"] : "";
$post_data["tags"] = isset($_POST["tags"]) ? $_POST["tags"] : "";

$ch = curl_init(_URL_POST);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
curl_setopt($ch, CURLOPT_USERAGENT, "kelvinluck.com del.icio.us poster (PHP-" . phpversion() . ")");
curl_setopt($ch, CURLOPT_POST, 1 );
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);

$method_result = curl_exec($ch);
$result_info = curl_getinfo($ch);
curl_close($ch);

switch ($result_info["http_code"]) {
    case 200:
        $p = strpos($method_result, "< ", 1);
        $content = substr($method_result, 0, $p)."<result status=\"1\">\n".substr($method_result, $p)."";
        break;
    case 401:
        $content = "< ?xml version='1.0' standalone='yes'?><result status=\"0\" message=\"HTTP401 - Bad login details.\" />";
        break;
    case 503:
        $content = "< ?xml version='1.0' standalone='yes'?><result status=\"0\" message=\"HTTP503 -  Gateway timeout.\" />";
        break;
    default:
        $content = "< ?xml version='1.0' standalone='yes'?><result status=\"0\" message=\"HTTP".$result_info["http_code"]." -  Unknown.\" />";
        trigger_error("I don't know what to do with http code:".$result_info["http_code"], E_USER_ERROR);
        break;
}

header("Content-Type: text/xml");
echo $content;

As you can see, the scripts use the CURL libraries to connect to the del.icio.us server with the relevant access details. They then return some valid XML to the calling flash file.

Now for the Flash file… Since I used components from a commercial library (the BJC Bit Component Set) I can’t actually post the fla file. But you can download the swf in the zip file below and can use it from your own server.

It also accepts an additional parameter: serverPath via FlashVars. This tells it which server to connect to.

If you leave this parameter out or set it to “” then the app will look in the same directory as itself for the get_tags.php and post_page.php pages. If you provide a value for this parameter then the app will prepend this path to it’s requests for the php files.

Here are the files for you to download:

kelvinluck_delicious_poster.zip

You will need one more thing to make the files in the zip work. I use Geoff Stern’s standards compliant JavaScript method of embedding Flash in the page. You will need to download the flashobject.js file from him and place it in /includes/flashobject.js on your web server or else edit line 15 of post.php.

And of course you will need to edit the bookmarklet to point to your server rather than mine.

Good luck and please leave comments with any ideas for improvement or bugs you find.



Tag autocompletion when posting to del.icio.us


Update:

This is a very old page imported from my previous blog. If there is missing content below or anything that doesn’t make sense then please check the page on my old blog.

I use del.icio.us to manage my bookmarks and love the way that I can get to my bookmarks from anywhere and can tag them freely and access groups of them from any browser on any computer based on my tags.

However, I found that my tags were becoming a bit of a mess for two reasons:

  • Misspellings. My spelling isn’t the best and I’m sometimes a little sloppy… It would be good if I hadn’t ended up with separate tags for “inspiration” and ” inspriation”.
  • Not remembering exact tags I’ve used before. I have some sites tagged with “photo” and some with “photos”. I meant the same thing when I used both tags and would prefer that I only had one of them so that I could see all the relevant sites on one page.

It occurred to me that a good way to avoid these problems would be if there was an autocomplete option (similar to “code hinting” or “intellisense” in an IDE) as you were posting pages to del.icio.us.

The same idea obviously occurred to other people and there was some philosophical debate on the del.icio.us mailing list about whether you should have something like this which “encouraged people to tag things in a way they may not see personally fit” but I think what I have built is quite useful.

Basically it will connect to delicious (through it’s api) and retrieve a list of your tags and will give you the option to autocomplete using these. You are free to use or ignore the autocomplete suggestions as you like. I feel this adds utility to the posting experience without constraining the user in any way.

To use my posting interface just create a bookmark using the bookmarklet. Then use this bookmark when you are on a page you want to add to your del.icio.us bookmarks.

You will need the Flash player (version 6+) installed to use this posting form since I programmed it in Flash. I’ve tested the bookmarklet in Firefox 1 and IE6 on PC and it seems to work fine – please let me know if there is any problems on other systems.

Also let me know of any bugs you encounter with the system and any suggestions for improvement (this is very much a first draft). Especially please ignore the look of the form – functionality over beauty – at least at this stage :)



Simulating access to the Flickr API offline


Update:

This is a very old page imported from my previous blog. If there is missing content below or anything that doesn’t make sense then please check the page on my old blog.

As I recently mentioned on the Flickr API mailing list, I’m just about to head off on a road trip where I will have my laptop and maybe a little spare time but no Internet connection. And I was thinking it would be good to carry on putting together my Flickr API library for AS2. But – to test my methods requires an internet connection to connect to the Flickr servers… Or does it?

I wrote the script below to use as a proxy between Flash and Flickr. You will need to use a proxy of some kind anyway as you cannot connect directly from a Flash movie on one domain to servers on another domain (due to the cross domain security sandbox in the Flash Player). This proxy saves all the XML it receives from Flickr as files on your file system. Then if it can’t reach the Flickr servers (e.g. when you are offline) it will serve up the contents of these files instead. Well – it will if you make a call to the API which is identical to one which has been cached. Otherwise it will serve up a custom error document which is similar to the standard Flickr error document except it contains a unique error code so you can deal with it in your application.

Without further ado, here is the code:

// CONFIGURATION
define("_SAVE_PATH", dirname(__FILE__)."/downloaded/");
define("_ERROR_MESSAGE", "< ?xml version=\"1.0\" encoding=\"utf-8\" ?><rsp stat=\"fail\"><err code=\"999\" msg=\"No access to flickr.com and no cached result for this request\" /></rsp>");
define("REST_ENDPOINT", "http://www.flickr.com/services/rest/");

if ($fp = @fopen("http://flickr.com", "r")) {
    // we are online and can see flickr.com - cache any requests that come in
    fclose($fp);
    define("_ONLINE", 1);
} else {
    // we can't see flickr.com - try an serve up cached pages instead...
    define("_ONLINE", 0);
}

$querystring = array_pop(explode("?", $_SERVER["REQUEST_URI"]));
$query_parts = array();
parse_str($querystring, $query_parts);
ksort($query_parts);

$filename = "";
foreach($query_parts as $key=>$value) {
    $filename .= $key."|".$value."||";
}

$filename = urlencode($filename);

if (_ONLINE) { // we are online - go get the info and save it!
    $fp = fopen(_SAVE_PATH.$filename, "w");
    $content = file_get_contents(REST_ENDPOINT."?".$querystring);
    fwrite($fp, $content);
    fclose($fp);
} else { // we are offline - serve up the saved file if it exists or the error message if you can't find a file
    if (file_exists(_SAVE_PATH.$filename)) {
        $content = file_get_contents(_SAVE_PATH.$filename);
    } else {
        $content = _ERROR_MESSAGE;
    }
}
header("Content-Type: text/xml");
echo $content;

As you can see, it’s pretty simple really. It’s not tested all that comprehensively yet but it should work. The next thing to do is to write a script which automatically calls this script once for each possible outcome of each call to the Flickr API so that you have a complete library of the possible returns on your machine.

One side note… I wouldn’t recommend running this script on a public webserver. It doesn’t feel too secure to me to be writing files which are named based on the querystring to your webserver. At least change the _SAVE_PATH so that people don’t know where to look for the files. Probably a bit paranoid but better safe than sorry, ey? And anyway – you should be running the it on your laptop anyway so this shouldn’t be an issue :)

If you want to download the complete script with commenting etc then please do: flickr_cache.phps. Any comments, ways to improve it or ideas on how to easily implement the “call every method once” script please use the form below…



Embedding flash in TXP


Update:

This is a very old page imported from my previous blog. If there is missing content below or anything that doesn’t make sense then please check the page on my old blog.

Not only does kml_flashembed make it nice and easy to embed Flash files in your textpattern pages – it also produces validating markup and deals nicely with detecting the Flash Player and displaying a message to the user if it isn’t installed. This is thanks to Geoff Stern’s JavaScript which it uses to embed the Flash files.
But… There was a couple of little problems with the plugin. I have error_reporting set to E_ALL on my system and the plugin was throwing a couple of notices due to unset variables.
So I made a few little changes to the code – here it is in it’s new state:

/**
 * Original code from Michael Bester:
 * http://www.kimili.com/journal/32/textpattern-plugin-kimili-flash-embed
 *
 * Slightly adjusted to work when error_reporting is set to E_ALL
 */

function kml_flashembed($atts) {

    if (is_array($atts)) extract($atts);
    $out = array();
    if (!empty($movie) && !empty($height) && !empty($width)) {
        $fversion     = (!empty($fversion)) ? $fversion : 6;
        $target       = (!empty($target)) ? '"'.$target.'"' : '' ;
        $fvars        = isset($fvars) ? explode(";", $fvars) : array();
        $height       = ($height{strlen($height) - 1} == "%") ? '"'.$height.'"' : $height;
        $width        = ($width{strlen($width) - 1} == "%") ? '"'.$width.'"' : $width;
        $id           = isset($id) ? $id : "fm_".$movie; // if no id is provided then default to the name of the swf being embedded which is hopefully unique
        # Convert any quasi-HTML in alttext back into tags
        $alttext      = preg_replace("/{(.*?)}/i", "< >", isset($alttext) ? $alttext : "");

        $out[] = '';
        $out[] = '<script type="text/javascript">';
        $out[] = '    // < ![CDATA[';
        $out[] = '';
        $out[] = '    var flashObject = new FlashObject("'.$movie.'", "'.$id.'", '.$width.', '.$height.', '.$fversion.', "'.(isset($bgcolor) ? $bgcolor : "").'");';
        if (!empty($alttext))        $out[] = '    flashObject.altTxt = "'.$alttext.'"';
        if (!empty($play))           $out[] = '    flashObject.addParam("play", "'.$play.'");';
        if (!empty($loop))           $out[] = '    flashObject.addParam("loop", "'.$loop.'");';
        if (!empty($menu))           $out[] = '    flashObject.addParam("menu", "'.$menu.'");';
        if (!empty($scale))          $out[] = '    flashObject.addParam("scale", "'.$scale.'");';
        if (!empty($quality))        $out[] = '    flashObject.addParam("quality", "'.$quality.'");';
        if (!empty($wmode))          $out[] = '    flashObject.addParam("wmode", "'.$wmode.'");';
        if (!empty($align))          $out[] = '    flashObject.addParam("align", "'.$align.'");';
        if (!empty($salign))         $out[] = '    flashObject.addParam("salign", "'.$salign.'");';
        // Loop through and add any name/value pairs in the  attribute
        for ($i = 0; $i < count($fvars); $i++) {
            $thispair    = trim($fvars[$i]);
            $nvpair      = explode("=",$thispair);
            $name        = trim($nvpair[0]);
            $value       = trim($nvpair[1]);
            $out[]       = '    flashObject.addVariable("'.$name.'", "'.$value.'");';
        }
        $out[] = '    flashObject.write('.$target.');';
        $out[] = '';
        $out[] = '    // ]]>';
        $out[] = '</script>';
        // Add NoScript content
        if (!empty($noscript)) {
            $out[] = '<noscript>';
            $out[] = '    ' . $noscript;
            $out[] = '</noscript>';
        }
        $out[] = '';
    }
    return join("\n",$out);
}

As you can see, the changes are very simple – just making sure that $fvars, $id, $alttext and $bgcolor are defined before they are used.
Here it is in action – embedding a little Flash movie I wrote which connects to the Flickr API to download the 6 most recent photos uploaded to Flickr. Refresh to see more photos – they are uploaded at an alarming rate!

Once again – props to Geoff Stern for his work on this cool and valid way to embed Flash in a page and to Michael Bester for bringing it to TXP in the form of an easy to use plugin.



krl_geshiSyntaxHighlight released


Update:

This is a very old page imported from my previous blog. If there is missing content below or anything that doesn’t make sense then please check the page on my old blog.

So – it’s finally deserving of a 0.1 release… There are still a few little teething problems but in general the krl_geshiSyntaxHighlight plugin is ready to go. For downloads, installation instructions please check out my project page for it, here.
Please leave any comments or suggestions for improvement at the bottom of this article…



Hacking TXP into submission


Update:

This is a very old page imported from my previous blog. If there is missing content below or anything that doesn’t make sense then please check the page on my old blog.

So, as you will see from my previous two blog entries, under the misunderstanding that there was no plug-in to allow TXP to display nicely formatted code in-line I started work on a plug-in to do just that.

I’ve since found out that there was already a plug-in (glx_code) but as part of my learning experience with TXP I decided to push ahead and complete my plug-in (which uses the excellent GeSHi Generic Syntax Highlighter).

My problem last time was that I wanted my plug-in to allow you to place your code in-line within the tag like so:

<txp:krl_geshiSyntaxHighlight language=”php”>
function codeThatIsHighlighted() {}
</txp:krl_geshiSyntaxHighlight>

But – even if I wrapped the code in code or notextile tags then the over zealous Textile engine still replaced certain characters with their textile equivalent (so “__” on either side of something would make it italic). Some of the Textile engine was respecting my notextile tags though – the quotes were no longer being swapped out for their pretty html equivalent.

Thus started my trawl through the TXP source to figure out what was going on and how to stop this. My first mistake was that I presumed that the substitution was occurring whenever the page was rendered and was something I could control from within my plug-in. It took a fair amount of digging to track it down and find out that the Textile engine actually does a pass over an article as you save it and inserts the parsed text into the Body_html row of the textpattern table.

Once I had this figured out it became apparent I would need to make Textile (more) aware of notextile tags. It did seem to pay some attention to them and when I looked through the source (of textpattern/lib/classTextpattern.php) I found that the glyphs function was aware of notextile, pre, kbd and code tags and purposefully didn’t apply it’s transformations to them. However, the span function just applied it’s transformations regardless. So I borrowed some code from glyph and modified it slightly to work in the span function.

Here is my modified version of the span function:

function span($text)
{
$qtags = array('\*','\*\*','\?\?','-','__','_','%','\+','~');
// KL 2005-01-26 - Borrowed some code from the glyphs function to make span aware of notextile, pre, kbd and code tags
$codepre = false;
/*  if no html, do a simple search and replace... */
if (!preg_match("/&lt; .*&gt;/", $text)) {
foreach($qtags as $f) {
$text = preg_replace_callback("/
(?&lt; =^|\s|[[:punct:]]|[{([])
($f)
($this-&gt;c)
(?::(\S+))?
([\w&lt; &amp;].*[\w])
([[:punct:];]*)
$f
(?=[])}]|[[:punct:]]+|\s|$)
/xmU"
, array(&amp;$this, "fSpan"), $text);
}
return $text;
}
else {
// codepre = we are in a code / pre / kbd tag - don't replace the things from $glyph_search
// with their html alternatives but do replace other htmlspecialchars
// codepre2 = we are in notextile tags. That means NO textile. So leave everything - including
// the things from $glyph_search well alone...
$codepre = $codepre2 = false;
$text = preg_split("/(&lt;.*&gt;)/U", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
foreach($text as $line) {
$offtags = ('code|pre|kbd');
$offtags2 = ('notextile');&lt;/code>

/*  matches are off if we're between &lt;code>, &lt;/code>
<pre> etc. */

            if (preg_match('/&lt; (' . $offtags . ')&gt;/i', $line)) $codepre = true;
            if (preg_match('/&lt; (' . $offtags2 . ')&gt;/i', $line)) $codepre2 = true;
            if (preg_match('/&lt; \/(' . $offtags . ')&gt;/i', $line)) $codepre = false;
            if (preg_match('/&lt; \/(' . $offtags2 . ')&gt;/i', $line)) $codepre2 = false;

            if (!$codepre &amp;&amp; !$codepre2) {
                foreach($qtags as $f) {
                    $line = preg_replace_callback("/
                        (?&lt; =^|\s|[[:punct:]]|[{([])
                        ($f)
                        ($this-&gt;c)
                        (?::(\S+))?
                        ([\w&lt; &amp;].*[\w])
                        ([[:punct:];]*)
                        $f
                        (?=[])}]|[[:punct:]]+|\s|$)
                    /xmU"
, array(&amp;$this, "fSpan"), $line);
                }
            }
            /* do htmlspecial if between <code> */
            if ($codepre &amp;&amp; !$codepre2) {
                $line = htmlspecialchars($line, ENT_NOQUOTES, "UTF-8");
                $line = preg_replace('/&lt;(\/?' . $offtags . ')&gt;/', "&lt; &gt;", $line);
            }

            $span_out[] = $line;
        }
        return join('', $span_out);
    }
}

I then noticed that there was still an issue with quotes being encoded when they were appearing within notextile tags when they shouldn’t have been… So I added this hack to the glyph function (as already illustrated in the span function above):

function glyphs($text)
{
// fix: hackish
$text = preg_replace('/"\z/', "\" ", $text);
$pnc = '[[:punct:]]';

$glyph_search = array(
'/([^\s[{(&gt;_*])?\'(?(1)|(?=\s|s\b|'.$pnc.'))/',      //  single closing
'
/\'/',                                              //  single opening
'/([^\s[{(&gt;_*])?"(?(1)|(?=\s|'.$pnc.'))/',           //  double closing
'/"/',                                               //  double opening
'/\b( )?\.{3}/',                                     //  ellipsis
'/\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/',        //  3+ uppercase acronym
'/\s?--\s?/',                                        //  em dash
'/\s-\s/',                                           //  en dash
'/(\d+) ?x ?(\d+)/',                                 //  dimension sign
'/\b ?[([]TM[])]/i',                                 //  trademark
'/\b ?[([]R[])]/i',                                  //  registered
'/\b ?[([]C[])]/i');                                 //  copyright

$glyph_replace = array('’',   //  single closing
'‘',                          //  single opening
'”',                        //  double closing
'“',                          //  double opening
'…',                        //  ellipsis
'<acronym title=""></acronym>', //  3+ uppercase acronym
'—',                          //  em dash
' – ',                        //  en dash
'×',                       //  dimension sign
'™',                          //  trademark
'®',                           //  registered
'©');                          //  copyright

/*  if no html, do a simple search and replace... */
if (!preg_match("/&lt; .*&gt;/", $text)) {
$text = preg_replace($glyph_search, $glyph_replace, $text);
return $text;
}
else {
// codepre = we are in a code / pre / kbd tag - don't replace the things from $glyph_search
// with their html alternatives but do replace other htmlspecialchars
// codepre2 = we are in notextile tags. That means NO textile. So leave everything - including
// the things from $glyph_search well alone...
$codepre = $codepre2 = false;
$text = preg_split("/(&lt; .*&gt;)/U", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
foreach($text as $line) {
$offtags = ('code|pre|kbd');
$offtags2 = ('notextile');

/*  matches are off if we're between &lt;code>, &lt;/code>, &lt;/pre>&lt;pre> etc. */
            if (preg_match('/&lt; (' . $offtags . ')&gt;/i', $line)) $codepre = true;
            if (preg_match('/&lt; (' . $offtags2 . ')&gt;/i', $line)) $codepre2 = true;
            if (preg_match('/&lt; \/(' . $offtags . ')&gt;/i', $line)) $codepre = false;
            if (preg_match('/&lt; \/(' . $offtags2 . ')&gt;/i', $line)) $codepre2 = false;
            if (!preg_match("/&lt; .*&gt;/", $line) &amp;&amp; !$codepre &amp;&amp; !$codepre2) {
                $line = preg_replace($glyph_search, $glyph_replace, $line);
            }

            /* do htmlspecial if between &lt;/code>&lt;code> */
            if ($codepre &amp;&amp; !$codepre2) {
                $line = htmlspecialchars($line, ENT_NOQUOTES, "UTF-8");
                $line = preg_replace('/&lt;(\/?' . $offtags . ')&gt;/', "&lt; &gt;", $line);
            }

            $glyph_out[] = $line;
        }
        return join('', $glyph_out);
    }
}

[note: line numbers are after the above hack has been applied - you should be able to find the right function to replace anyway]

With these hacks in place I am finding that my plug-in is able to work more or less how I want it to… I am interested to find out if people think that these hacks will break other functionality or incur a performance hit or if there was a reason that textile ignored notextile tags when replacing “span” style tags in the first place.

Anyone who can answer those questions or can suggest a better way I can solve my problem please leave a comment and let me know… I’d rather not be hacking the TXP core – especially with version 1 hopefully out soon. But it seems nice to be able to drop little highlighted code snippets into your blog without requiring any uploading of files or anything…