kelvinluck.com

a stroke of luck

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…

Plugin to add GeSHi syntax highlighting to Textpattern - Part II


Update 2:

This is a very old page imported from my previous blog. If