debuggable

 
Contact Us
 

Handling inline links to dynamic resources

Posted on 6/11/06 by Felix Geisendörfer

Deprecated post

The authors of this post have marked it as deprecated. This means the information displayed is most likely outdated, inaccurate, boring or a combination of all three.

Policy: We never delete deprecated posts, but they are not listed in our categories or show up in the search anymore.

Comments: You can continue to leave comments on this post, but please consult Google or our search first if you want to get an answer ; ).

First of all: Yeahhh, today has been my first really productive day since I've overcome my recent issues, lot's of things got done and I'm exited to have even found time to write a little post on my blog here ; ).

One thing I recently had to figure out was how to let the users of the CMS I'm writing link to other pages, pictures, etc. within the system. My first idea was to create use a fancy JS Popup (aka Thickbox) and to let the user select what pictures or pages he wants to insert and then paste the code in via js. But eventually I decided that this would take too much time and would add too much complexity to the backend. So I thought of something that would be both, easy to implement and easy to use. What I finally came up with is tightly coupled to my new url system that I'm using, so make sure to check it out in case you've missed it.

The basic idea was this: My url's (for pages) look like this: "/pages/12:my-title", where 12 is the unique id that this page has. I display those id's throughout the system, so the user is aware of them. The logical consequence was that it would be nice to expand statments like [page:12] within text fields to the real link ("/pages/12:my-title"). Since I'm using a textile flavor for markup, I regenerate the html of a page for every edit, so parsing those statements would be a no-brainer performance wise. Another nice thing that was to my favor was that I already had added a function called getUrl to all my Models that were using url_suffix'es in them.

For example my Page Model looked like this:

class Page extends AppModel
{
    var $name = 'Page';                                            
    var $validate = array('title'      => VALID_NOT_EMPTY,
                          'url_suffix' => VALID_NOT_EMPTY,
                          'textile'    => VALID_NOT_EMPTY);
     
    function getUrl($page = null)
    {
        if (is_numeric($page))
        {
            $id = $page;
            $page = $this->findById($id);
            if (empty($page))
                return '/pages/0:page-'.$id.'-does-not-exist';
        }
        elseif (empty($page))
            $page = $this->data;
               
        return '/pages/'.$page['Page']['id'].':'.$page['Page']['url_suffix'];
    }
}

So I figured it would save me quite some work and repition if I could connect those [<ResourceType>:<id>] statements with those getUrl functions. The result was a very nice function I threw into my CommonsComponent for now, but I might move it later on:

class CommonComponent extends Object
{
    var $resourceTypes = array('/\[picture:([0-9]+)\]/' => array('Picture', 'getUrl'),
                               '/\[page:([0-9]+)\]/' => array('Page', 'getUrl'));
   
    function parseResources($text, $cache = true)
    {
        static $cachedResources;

        foreach ($this->resourceTypes as $resourceType => $resourceCallback)
        {
            preg_match_all($resourceType, $text, $matches, PREG_SET_ORDER);
            if (empty($matches))
                continue;
           
            list($resourceModel, $resourceFct) = $resourceCallback;
           
            if (!isset($this->{$resourceModel}))
                $this->{$resourceModel} =& new $resourceModel();
               
            foreach ($matches as $match)
            {
                list($resourceString, $resourceId) = $match;
               
                if (!$cache || !isset($cachedResources[$resourceModel][$resourceFct][$resourceId]))
                {
                    $resource = call_user_func(array(&$this->{$resourceModel}, $resourceFct), $resourceId);
                    $cachedResources[$resourceModel][$resourceFct][$resourceId] = $resource;
                }
                else
                    $resource = $cachedResources[$resourceModel][$resourceFct][$resourceId];
                   
                $text = str_replace($resourceString, $resource, $text);
            }
        }
       
        return $text;
    }
}

In case you wonder what's going on in there: First of all I defined the available $resourceTypes as regexs that belong to a Model => Function. In this example it's always the getUrl function of the corresponding Model, but I could easily create more advanced mappings with this. After this I loop through those $resourceTypes and see if they are inside the $text I'm parsing, and if so I call up the corresponding Model's function to get the real url back. Now that I got the url, I simply perform a str_replace to remove the resource links and replace them with real ones. And in order to make the optimization people out there a little happier as well, I added some caching for already looked up resources, that can be disabled via the $cache parameter.

As you can see in this little code example, this makes it very easy for me (development wise) & the client (time wise) to link to things within the system. Here is how I parse my normal page input:

$page['Page']['html']   = $this->Common->parseResources($page['Page']['textile']);
$page['Page']['html']   = $this->Textile->toHtml($page['Page']['html']);

This should also show why it's useful to use RESTful url's while not going for the domain.com/pages/my-pretty-title approach. In case the title (url_suffix) of a page ever changes all the internal links will still be working (as they are identified via the id part of the url) and the next time the page get's updated the url_suffix part of the link will be corrected automatically. Simple, yet beautiful.

Let me know what you guys think about this little function.

--Felix Geisendörfer aka the_undefined

 
&nsbp;

You can skip to the end and add a comment.

This post is too old. We do not allow comments here anymore in order to fight spam. If you have real feedback or questions for the post, please contact us.