Hackathon 2011!

We've still got a little hacking time left in the 2011 Horde Hackathon, but we're about to relax for dinner for a bit, so now is a good time to wrap up some of the things that have been accomplished already:

  • We have a plan for making the Horde UI consistent between all applications and application views, as well as to make it look much better. Also to reduce the number of views we maintain, hopefully increasing our developer productivity
  • Some really snazzy automated updates coming for the demo server
  • The develop branch of nag now supports ajax completion of tasks, creating tasks, and basic viewing of tasks
  • Trean finally has a migration from folders to tags, with basic working functionality for viewing bookmarks with tags. This was on my list FOREVER.
  • Lots of good discussion about how we fit into the larger PHP and groupware ecosystem
  • New timezone support from Jan
  • New weather support to replace the now-commercial weather.com feed
  • New documentation tools

... and almost certainly a ton more that is slipping my mind right now. Going to miss everyone once we head our separate ways again tomorrow, but hopefully the momentum will keep up, and I'm really looking forward to next year!

Improved URL auto-linking in Horde

Horde now uses John Gruber's regex pattern for matching URLs in text. This regex is used in the Horde_Text_Filter_Linkurls class, which already had a solid pattern, but the new one improves it in several ways:

  • Much better support for unicode characters
  • We now auto-link URLs like example.com/foo
  • Support for some limited parentheses matching so that URLs that contain matching parentheses are properly matched, even if a parentheses is the last character in the URL

Horde's pattern differs from the one posted by Gruber in a few ways, as well:

  • It does not match mailto: URLs. This is to better match how Horde has historically separated linking of web addresses from email addresses; mailto: links are handled by Horde_Text_Filter_Emails.
  • It matches URLs that start with ://
  • URL protocols are limited to 20 characters to avoid excessive memory use when PCRE does backtracking
  • "+" is allowed in a protocol (so svn+ssh:// works)

A good-sized list of test data has been incorporated into the Horde_Text_Filter test suite, but let me know if there are kinds of URLs that used to work but now don't, or if you see any other problems with the new auto-linking. Hopefully the new pattern will help polish the experience for those using the new Horde 4 alphas!

Stream wrapper for strings

I've added a StringStream class to the horde/Support package that lets you access a string as a stream, using fgets, fread, fseek, ftell, etc. - all of the standard stream functionality. 

The neat thing about this implementation is that it doesn't duplicate the string, and it also doesn't use global scope to pass the string to the stream wrapper. The first is accomplished through references; not such a huge trick. The second is harder, since there isn't an obvious way to pass arbitrary data to a stream wrapper implementation. You can pass an object to fopen() as the $path, and if it has a __toString() method it will be used - an interesting trick that I'm still looking for a good use for. Unfortunately the string-ified value is what gets passed to stream_open() inside the stream wrapper implementation.

But! You can use the stream context to pass arbitrary parameters. So the $context parameter becomes the vehicle to pass the Horde_Support_StringStream object to the stream methods, which are implemented in Horde_Stream_Wrapper_String.

Here's an example:

$str = 'large data passed as a string';
$stream = new Horde_Support_StringStream($str);

$fp = $stream->fopen();
while (!feof($fp)) {
    $chunk = fread($fp, 1024);
    // Do something chunked
}

Using Horde_Xml_Element to quickly generate XML from arrays

Horde's Xml_Element package, part of the PHP 5 Horde 4 framework, is a SimpleXML-like wrapper around the DOM extension that gives programmers a midpoint between DOM's power and SimpleXML's simplicity. Most of the syntax is just like SimpleXML - access attributes as array offsets and elements with object accessors - but you can also add new nested elements with object syntax and use namespace prefixes in element names. The biggest extra feature, though, is probably the fromArray() method.

Horde_Xml_Element::fromArray lets you very easily convert an array into a Horde_Xml_Element object, which you can then work with or dump directly to XML:
<?php

// To populate an <entry> element:
$e = new Horde_Xml_Element('<entry/>');
$e->fromArray(array(
    'id' => 1,
    'title' => 'Hi neighbor!',
    'author' => array('name' => 'Chuck', 'email' => 'chuck@horde.org'),
));
echo $e->saveXmlFragment();
Produces:
<entry><id>1</id><title>Hi neighbor!</title><author><name>Chuck</name><email>chuck@horde.org</email></author></entry>
With the Horde/Feed package, which uses Horde_Xml_Element internally, this can be simplified further:
<?php

$e = new Horde_Feed_Entry_Atom(array(
    'id' => 1,
    'title' => 'Hi neighbor!',
    'author' => array('name' => 'Chuck', 'email' => 'chuck@horde.org'),
));
echo $e->saveXmlFragment();
Which produces the same output. Horde_Xml_Element will also let you set attributes and namespaces with a simple syntax. "#" denotes an attribute, and ":" separates namespaces:
$e = new Horde_Xml_Element('<atom:feed/>');
$e->registerNamespace('dc', 'http://purl.org/dc/elements/1.1/');
$e->fromArray(array(
    'atom:entry' => array(
        'atom:link' => array(
            '#rel' => 'self',
            '#title' => 'Feed title',
            '#dc:date' => '2011-01-31 01:00:00',
            '#href' => 'http://planet.horde.org/atom/',
        ),
    ),
));
echo $e->saveXmlFragment(true) . "\n";

Which produces:

<atom:feed xmlns:atom="http://www.w3.org/2005/Atom">
  <atom:entry>
    <atom:link xmlns:dc="http://purl.org/dc/elements/1.1/" rel="self" title="Feed title" dc:date="2011-01-31 01:00:00" href="http://planet.horde.org/atom/"/>
  </atom:entry>
</atom:feed>