Web Development

Fixing update_sql() to accept parameters

Tags: 

A pretty simple yet cool feature of Drupal's db_query() function is that you can pass in parameters that will make it automatically adjust the query to correctly escape the arguments. This is one of the simple security features in Drupal as it will properly escape the string to avoid SQL injection attacks, and just simply safe you hassle. Good stuff!

When it comes to module development you'll occasionally have to update the database schema for one reason or another, so you'd figure the corresponding update_sql() function, which has some extra purpose for update scripts, would also handle parameters. Not so, unfortunately. In Drupal 5 and 6 the only argument that update_sql() accepts is the query itself, so there's no direct way of doing the parameter auto-magic gravy. Thankfully, all is not lost.

If you look at the code in update_sql(), it's actually very simple - it just runs the standard db_query() string and compiles a nice array to let the update script give a reasonable return message. So, simply put, to do the parameter processing in Drupal 5 and 6 all you have to do is replicate the update_sql() function inline, e.g.:

<?php
/**
 * Change the primary color from blue to green.
 */
function funkychicken_update_6001() {
  $ret = array();
  
  $sql = "UPDATE {funkychicken} SET color='%s' WHERE color='blue'";
  $result = db_query($sql, 'green');
  $ret[] = array('success' => $result !== FALSE, 'query' => check_plain($sql));

  return $ret;
}

So that's all there is to it!

Going a step further, here's an alternative to update_sql() to make it even easier. This can be added to your module's update file, e.g.:

<?php
/**
 * Change the primary color from blue to green.
 */
function funkychicken_update_6001() {
  $ret = array();
  
  $ret[] = update_sql2("UPDATE {funkychicken} SET color='%s' WHERE color='blue'", 'green');

  return $ret;
}

/**
 * An alternative to update_sql() that accepts parameters.
 * Params:
 *   $sql - The query to execute, as a string.
 *   $args - The parameters to pass to the query.
 * Return:
 *   An array containing the success status (boolean) and the query.
 */
if (!function_exists('update_sql2')) {
  function update_sql2($sql, $args = NULL) {
    $result = db_query($sql, $args);
    return array('success' => $result !== FALSE, 'query' => check_plain($sql));
  }
}

FYI there's a patch available for Drupal 6 that might eventually make it into a release, it just needs to be worked on a bit more. Either way, hopefully a fix for this will work its way into Drupal 7.

Managing Drupal Views, the proper way

Tags: 

One of the most powerful and most useful modules on Drupal is Views. With one screen you can build custom pages & blocks based around your content, select the exact fields you need, add filters and arguments, and relatively easily customize the display, and that's just scratching the surface. In fact, Views is so flexible that I've built sites which have 90% of their architecture based solely around taxonomies and Views.

The main trick to making using Views manageable is to save them out as files and load them through a module. It sounds really easy, and it is really, but there's a few steps to it.

The first step is to have a per-site module specially for holding these kinds of things, a general bucket for your random chaos.

200905262332.jpg

The second step is to go to each View in turn and export it to a file in your new module's directory. To do that, go to your site's Views list page and for each of the custom ones you've built or modified, click the Export link next to it to bring you to a page with the view's code listed out:

200905262336.jpg  

What you need to do next is save this text out to a file. By default all of the text is selected, but you're not ready for that yet. You first need to create the file to store the text. I like to use the following filename structure:

view.[viewname].inc

This both groups all views together and makes them easy to identify at a quick glance. Going by this, the above view would be saved with the filename:

view.taxonomy_term.inc

Now, just copy all of the code from the Export page into the newly created file. Simple, huh? Well, there's a little gotcha - you need to add "<?php" to the top of the file otherwise the next steps won't work right, e.g.:

200905262341.jpg

Well, we're not done yet, we still have to tell the site that there are views in this new module. This involves two further steps..

The first part is to tell the system that this module uses the Views API by adding some lines to your module:

200905262343.jpg

The last part is to load the views into the system. To do this you must add another file to your module directory named:

[modulename].views_default.inc

Then add the following to the file:

200905262346.jpg

And now you're done!

Well, almost. Your site is still going to load the view that's already in your database rather than the one in your module. To fix this just go back to the main Views list page, find the view you just exported and click the "Revert" link to revert it from the database-stored version to what was in your file; don't be afraid when it says "Are you sure you want to revert the view?" because yes, that's what you want to do. So do it.

And now you really are done. Enjoy. And make sure you save it into your site's SVN server promptly.

Learning Exercise:

To make more of this, and to test your PHP skills, try changing funkychicken_views_default_views() to automatically load all files named 'view.[something].inc' instead of having to manually list each one.

Make a special module for each Drupal site

Tags: 

One thing I've learned from my Drupal development time is that every site should have a new module created for it to house all of the small custom functions and things you'll use. Common things I've added to these per-site modules include:

  • Default views (more on those later),
  • Odd string modifications, e.g. shortening a node summary to fit a certain amount of space,
  • Hacks to modify forms in unusual ways, e.g. pre-assign taxonomy terms to simplify the editorial process,

I've found it much easier to manage these in one single module than e.g. hacking blocks with funky PHP code, etc, and you also gain the benefit of having it all in code, so you can track changes in a code versioning system like svn or git.

Bonus Tip

One final tidbit on this is to create a module "package" for all of the modules you create, i.e. add the following line to your mymodule.info file:

package = My Stuff

Now when you view the module admin page they'll all be grouped together, making them easier to find.

The future of blogging with Drupal via XML-RPC?

Tags: 

One of the benefits of having a content management engine that supports XML-RPC-based content creation is that you can write your content in more usable tools and publish when finished. Drupal is one such system and is an amazingly powerful tool in its own right, but you still want to be able to write offline..

For offline / desktop-based writing I use ecto, which is a MacOSX-based editor that works pretty well, though many prefer other tools like MarsEdit, etc. So lets see how well it works..

The first step is to enable the BlogAPI module and then doing some basic configuration as mentioned my a previous post. That's the easy part.

One of the most powerful aspects of Drupal is being able to build a very powerful taxonomy structure by building different vocabularies of terms, e.g. a master category, a general-purpose free tagging / folk taxonomies aka "tags", maybe one for the related country, etc.. it can be amazingly powerful and is well worth learning how to effectively using.

To match that, the XML-RPC blogging standards have ways of querying the list of categories, usually the MetaWebog API call metaWeblog.getCategories or the Moveable Type API call mt.getCategoryList. Additionally, the Wordpress folks have added their own XML-RPC API and have given the world wp.getTags which is for listing tags. Ecto gives two different lists, one for categories and one for tags, which can work well in the right circumstances.

A bit of a limitation with Drupal's BlogAPI module, however, is that you are not given much control over how it handles vocabularies. By right you should be able to select which one is the master vocabulary to be used as the standard category and which is used as tags, or even add a way to list all vocabularies with their terms in a nested structure. Unfortunately, it currently only supports the categories functions and lists all of the site's terms in one long list. With today's sophisticated desktop blogging tools, and Drupal's amazing taxonomy system, this is an unfortunate limitation.

For the forthcoming Drupal 7 I think the BlogAPI really needs expanding. There are two ways of looking at it:

  1. Just add some basic support for existing APIs:
    • Add controls to the BlogAPI module to let you decide which vocabulary or vocabularies are made available through the existing blogapi_metaweblog_get_category_list() function.
    • Add support for the wp.getTags function and add a similar configuration block to decide which vocabularies are made available through it.
  2. Push beyond the existing APIs, lead the field:
    • Define an extension of one of the existing APIs which returns a nested array of all vocabularies.
    • Work with other blogging software and desktop tools to get this feature supported, for example Wordpress is working towards being able to support user-created taxonomies (like Drupal).

Obviously, being an open-source system this functionality won't fall off the trees, it has to be built by someone. So who's going to build it? You? Me? Maybe.

 


Update: It should also be mentioned that the combination of Ecto and Drupal is still a bit buggy - when I initially published this article it missed all of the taxonomy selections.  Bummer.

 

Posting to Drupal through XMPRPC / BlogAPI

Tags: 

When I migrated my blog from Wordpress to Drupal one key thing I still wanted to do was be able to use ecto to post messages from my laptop rather than through the web interface - it can be just easier at times. Thankfully Drupal has a plugin/module built in which does this for you, the BlogAPI module. So I activate the module then try to connect using ecto only to be greeted with the lovely message:

The response from the server did not contain valid data.

This was frustrating, it should just work, right? Thankfully ecto has a console feature which displays all communications with the server, and I was able to see it submit:

<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>blogger.getUsersBlogs</methodName>
<params>
        <param>
                <value><string>ignore</string></value>
        </param>
        <param>
                <value><string>Damien</string></value>
        </param>
        <param>
                <value><string>*****************</string></value>
        </param>
</params>
</methodCall>

This is a normal Blogger API call to find out what blogs a given user (me) has permission to write on at a given blog installation, and should have returned some valid data. Here's what came back:

<?xml version="1.0"?>

<methodResponse>
  <params>
  <param>
    <value><array><data>
</data></array></value>
  </param>
  </params>
</methodResponse>

Obviously this isn't quite right.. I dug through the code, adding print and print_r statements where I thought they might help, until I came to blogapi.module line 937 which says:

$available_types = array_keys(array_filter(variable_get('blogapi_node_types', array('blog' => 1))));

This is supposed to grab the blogapi_node_types system variable which stores a list of the Drupal content types you told it were allowed to be written to using the BlogAPI.. and I had set them, right?

200904201148.jpg

No, as it turned out I hadn't, though I thought I had.

Once I went to /admin/settings/blogapi I was able to mark a few content types to be editable, hit save, go back to ecto and finally see what I wanted:

200904201147.jpg

Success!


Ok, so next problem.. I wrote the above, including the two pasted images, hit Publish but started getting the following error:

<?xml version="1.0"?>
<methodResponse>
  <fault>
  <value>
   <struct>
   <member>
   <name>faultCode</name>
   <value><int>1</int></value>
   </member>
   <member>
   <name>faultString</name>
   <value><string>It is not possible to upload the file, because it exceeded the maximum filesize of 0 bytes.</string></value>
   </member>
   </struct>
  </value>
  </fault>
</methodResponse>

Obviously a problem persisted.

I cranked open the files and inserted some extra code that made it say::

<?xml version="1.0"?>
<methodResponse>
  <fault>
  <value>
   <struct>
   <member>
   <name>faultCode</name>
   <value><int>1</int></value>
   </member>
   <member>
   <name>faultString</name>
   <value><string>It is not possible to upload the file, because it exceeded the maximum filesize of 0 bytes and your file was 12.04 KB.</string></value>
   </member>
   </struct>
  </value>
  </fault>
</methodResponse>

From this you can see that it was getting the file, but it was basically saying that my account was not set up to accept attachments, which was not true as I was the administrator and could do everything, right?

I went back to the BlogAPI settings page, ensured that the file settings looked correct..

200904201211.jpg

.. scratched my head. Then I went to look at the permissions.. as it turned out I had not enabled "administer content with blog api" for my user group.

200904201212.jpg

Doh. I enabled that permission, saved it, and now when I went back to the BlogAPI settings page there was a new item waiting for me:

200904201210.jpg

I expanded that, set the same 1MB and 5MB... and saved..

Et voila! That did the trick, I can now post embedded images too!

Pages

Subscribe to Web Development