drupal planet

It's Amazing What You Find: Crusty Bits of the Menu System

I've been poking in the innards of the menu system the last few days. This is due to yet another client wanting to do something that goes completely against the grain of Drupal.

In this case, it's the way Drupal only shows you menu links you have access to. This to me seems perfectly reasonable good usability: why show you something you can't use? On the other hand, there is a long-standing feature in Comment module that shows anonymous users a 'Login or register to post comments' link on nodes, so 'incitements to action' or whatever the social media buzzword is do exist in Drupal.

So the challenge was to show a link that the anonymous user can't use, send them to login, and back to the link they wanted in the first place. The last part is just some hook_form_alter() work with form redirection (though it did allow me to discover that everything drupal_get_form() is passed is available to the alter hooks, just in a funny place). The access to the menu item is done by intercepting in hook_menu_item_alter() to save a twin of the menu item, and a checkbox in the menu edit form to trigger this. Even registering the path is easy: a custom menu access callback which takes as access arguments those of the original item plus its access callback and negates whatever the original item would return for access. The part I'm (so far) stuck on is getting hook_menu_alter() to know about what the admin user has done in hook_menu_item_alter(): the next job will probably involve creating a truly ugly query that uses %LIKE% to grab menu items based on their options array.

But that's not what I came here to tell you about today.

In my prodding around of the menu system, I found that menu router items have a 'block_callback' property, with its own database field and everything. Now get this: only one item in the entire {menu_router} table on D6 has this filled, and to boot, it has absolutely no effect. (It's the admin theme page, by the way.) This property is something to do with the way that the root admin page is made out of things that are sort of blocks, but not quite. If the property exists on the router item, then the callback is called to add (!!) to the content. (The admin theme page of course plays no part in the root admin page.)

So we had here a completely useless database field, probably left over from Drupal 5 or even earlier. By the way, in a standard Drupal 7 install, the database field is completely unused. I made a dummy patch to get the issue queue testbot confirm that we never get to this particular piece of code, and the superfluous field has now been removed. It's amazing what you find!

By the way, if anyone fancies some bikeshedding, I still don't have a name better than 'menu_login' for this module. To your paintbrushes!

The Oxford Comma

Here's a little function I wrote today because I needed to be able to turn a list of between one and three items into a string like 'apples, oranges, and pears', 'apples and pears', or just 'stairs'.

I figured I might as well handle everything in one place, and throw in the option to have an 'or' instead of an 'and'. There may be occasions you don't want the Oxford comma, but I can't think of any.

/**
* Grammatically fun helper to make a list of things in a sentence, ie
* turn an array into a string 'a, b, and c'.
*
* @param $list
*  An array of words or items to join.
* @param $type
*  The text to use between the last two items. Defaults to 'and'.
* @param $oxford
*  Change this from default and you are a philistine.
*/
function oxford_comma_list($list, $type = 'and', $oxford = TRUE) {
  $final_join = " $type ";
  if ($oxford && count($list) > 2) {
    $final_join = ',' . $final_join;
  }
  $final = array_splice($list, -2, 2); 
  $final_string = implode($final_join, $final);
  array_push($list, $final_string);
  return implode(', ', $list);
}

Now the real question: how would you make this translatable?

Using Constants For Permission Names: WHY?

I keep seeing this sort of thing in so many modules:

define("MY_MODULE_PERM_ACCESS_WIDGETS", 'access widgets');
// Names changed to protect the guilty ;)

Am I missing something, or is this utterly pointless?

The only advantage I see is that you can change the permission string later on. But you actually can't change a permission string once you've made a release without an almighty amount of work in a hook_update_N()[*] to migrate users' existing permissions, so what is the point of using a constant apart from just creating shouty caps noise?

[*] Is there a helper function in core yet for migrating permission names during updates? There really should be.

Once & Only Once: The Conversion To Drupal 7's FieldAPI

I'm not a fan of repeating myself, or of doing the same work twice. So when I first got a look at FieldAPI back at DrupalCon Paris, my thought after 'Wow this is going to change everything,' was 'Every module that converts its custom data storage to this is going to be doing the same work, over and over'.

It seemed to me that we have a lot of modules that add things to stuff. The examples that spring to mind include Taxonomy image (add an image to a taxonomy term), Comment upload (add an uploaded file to a comment), User terms (you get the picture), and the daddy of them all, Image (which now we must call Image Oldskool, due to there being a shiny new Image module in Core).

What all these have in common is that on Drupal 7 their things-to-stuffness would be perfectly served by FieldAPI. Users get more flexibility, an expanding universe of formatters and widgets; module maintainers can write less code and in some cases even hang up their hats on a particular project as it can live entirely in configuration space. Everyone wins. But how to get there?

This matter has been bubbling at the back of my mind ever since Paris. On the one hand I could sort of see how this could be all done with a single framework: you tell it what sort of entity you are dealing with (node, comment, term), which bundle (article, page, which vocab), what your fields are, and how to load the old data. The framework creates the fields for you, then runs a batch to load each object, moves values around in clever ways that you don't need to worry about, and then saves the object. Banzai! Your data is now saved in a field.

On the other hand, this was clearly crack. Of the highest order.

And yet it works. It quite possibly still is crack, and some extra pairs of eyes to de-crackify it would be very welcome. But I can confirm that I've run data conversions on both User terms and oldskool Image nodes, and got term fields on my users and beautiful image fields on my nodes.

There remains some work to be done: I'd like to make conversion to file and image fields a bit more straightforward and create a nice way to specify how to populate the alt and title fields for images. And all this is largely academic and has only run on D7 databases with dummy D6 tables imported, since core's 6 to 7 upgrade process is not currently working. So I could use a hand. And if you have a things-to-stuff module, get in touch and give it a whirl.

Help needed: using jQuery to show passwords as you type

Today I found an article about password usability, which suggested that showing users what they type for their passwords is an improvement to usability.

You can try a working demo, which adds a 'show password' checkbox.

This had me wondering whether this is a feature we should consider having in Drupal.

I duly began writing a small module to do this, but I'm stuck on rewriting the Javascript from the article as well-formed Drupal-friendly jQuery. Now I can make jQuery do fancy things like expand things you click on and whizz things around, and my code so far replaces the password element with the new one, but making that new element itself clickable has me stumped: it's all a bit too meta.

I know when I'm beaten, so I'm blogging this to say: is there a jQuery whizz out there who would care to lend a hand? If so, please comment or email me, and let's make a new contrib module for this!

Getting Stuff Done

Basically, an initial patch went in, we opened followup issues for cleanup, the followup issues never got followed up, so what's in HEAD is a bit of a mess. — catch, in a recent issue comment

This happens far too often. I'm not linking to the specific issue in the Drupal 7 queue because I don't want to point fingers; this suffices to exemplify my point.

I wasn't anywhere near the sausage factory back when Drupal 6 was coming out, so I've no idea if this is a problem that's getting worse, but I feel this is a problem that happens a lot, and that it needs to happen a lot less.

I think that our move to distributed version control may help, as we can leave new developments on a branch until everything is cleaned up (and documented!), but as everyone knows, technological solutions are no substitute for good communication and organization.

Ultimately, we need people to take on the roles of roadmappers and project managers.

This is apparently a dirty word; there is a widespread notion that you can't organize volunteers. I can with confidence state that this is utter rubbish, because I've done it, and volunteer organizations around the world do it.

It's maybe harder because it's online rather than face to face (we do it fairly well at code sprints, after all), and maybe harder because we're mostly a bunch of fairly headstrong smart people who are used to managing our own activities (which is one of the things that makes us good programmers). But we clearly recognize the need to be organized on a smaller scale, and the need to work with rules. Managing Core is going to be a mammoth task, of which we are right to be apprehensive, but is it so different to what we already do?

Regardless, it is necessary, and I think we are also of necessity often pragmatists. This is one occasion we need to recognize the need for some medicine that in some cases might not be entirely to our palate.

Spoonful of sugar anyone?

Services, or How I Learned We're All Just Secretly hook_menu()

I am now batting nodes, complete with imagefield, between separate instances of Drupal with merriment and glee. For yes, I do have a working beta of Content distribution.

I'm about to quickly write a service to get CCK's content_fields() array from the remote, distributing site, so that the retrieving site can show a UI of possible values for a Views nodereference argument. Try it. It all makes sense once you do.

But I remember a couple of months ago, when Services was a total mystery to me. I installed it, knowing that this was what I needed to achieve this task, but I was baffled by it. What was a Service? And how come I needed a server as well? Surely a service is a kind of server? Argh! And so on.

The penny dropped a few weeks ago, when I suddenly realized that a service is really just another kind of hook_menu() item.

Consider: in hook_menu(), you define which function Drupal should call when it gets an HTTP request at a certain path, say, 'node/1'. And in hook_service(), you define which function Drupal should call when it gets a service method request, in this case via XMLRPC (which is still black magic to me, but with Services module, I don't need to know, because it Just Works). For that matter, in Drush's hook_drush_command(), you define... etc etc. They are all the same!

This has me wondering whether for Drupal 8 we should abstract this out to a general hook_callback(), where we define what function Drupal should call when it receives any kind of external input.

Why Is Writing Drupal Documentation Harder Than Writing Wikipedia?

I used to write on Wikipedia, years ago when it was a wild frontier, we had barely 30 thousand articles, and not even my geek friends had heard of it from a source other than my blathering on about it.

It was quick and simple, deserving the origin of its name [1]. And it was quick not just to edit the content of pages, but their place in hierarchies and their order within lists. A whole section of the site that wasn't to our liking could be rejigged in a handful of browser tabs. Pages didn't belong to anyone, but the edit history and the attached talk pages gave you a clue of who was involved, and once you'd made edits to a page you could spot it in the Recent Changes list or your personal watchlist of bookmarks.

Now over to Drupal handbooks, where I find the process slow and laborious and confusing. I ask myself (and you!) — why?

We have Recent Changes of a sort: the tracker; we even have a tracker just for documentation changes, but I forget it exists because it's tucked down at the bottom of the site blocks. We have page revisions and diffs of pages, just like Wikipedia.

The differences I can see are:

1. HTML is slower. I know purists dislike wiki markup, but it's undeniably quicker to type ''italic'' and [[clean URLs]] than to muck around with opening and closing tags and finding the right URL. Granted, there is no one standard (though I would say that used at UseMod comes close) and Wikipedia has now bloated it so much with complex syntax to create all manner of things. But the handful of basic wiki syntax elements are good.

2. Structure is divorced from content. It's very handy the way Book module builds menus and trees for you. But it makes moving a page to another part of a book feel like a big destructive (and irreversible?) act. Furthermore, only a small number of admins can change the order of child pages, something which frequently makes all the difference between a clear read and a mess. On wiki, structure ''is'' content (italics, remember?): you want to make a list of child pages? You write a bullet list of page names. You want to move a page to a different part of the hierarchy? You just find what links to it and edit. Granted, this can often lead to an almighty tangled mess; not for nothing it is the full term a 'wiki-wiki-web' — not a tree.

3. Every article has an associated talk page. One day you find a section has all been changed and you're confused as to why? Find the answer in the talk page. You're pondering a big reorganization yourself, and you want to make sure other writers who are interested in this page get wind of it and have a chance to discuss it? The talk page. It's a bit more anarchic to what we're used to on drupal.org, but it keeps the discussion and the document side by side. I'm only an occasional participant in the documentation team, I'm not on the mailing list[2] and so I often find ''everything'' has been turned upside down and I can't fathom why, and I can't tell if it's work in progress that I should help with or just an almighty mess. Talk pages aren't perfect, and it's hard to follow discussions spread across many places, or even to know if you might be missing something. But there is an immediacy about the 'talk' tab at the top of a page.

4. Lastly, on Wikipedia it felt like there was a collective ownership of pages and sections. On drupal.org, I feel I'm treading on toes if I make too many changes. I don't know why that is; maybe we don't have the critical mass of documentation editors to turn into a community.

In conclusion: I don't have answers. I know that editing and more to the point, organizing our documentation is a big job, and I think the tools are not quite up to the job. As a first step, we should make book hierarchy editable by the documentation team. After that, start upon the inevitable debate about wiki syntax, and think about ways to better tie isues to the documentation pages they are about.

Footnotes:

[1] 'wiki' meaning 'quick' in Hawaiian, coined by Ward Cunningham for the original wiki.
[2] I hate mailing lists. Don't get me started on them. The 1980s called: they want their communication technology back is all I'm going to say.

Files Aren't Visible From All Domains Of A Site

I had a fun afternoon a few months back when all the imagecache images broke on a site I'd just taken live. I've just figured it out, so I'm telling you about it.

This was the situation:

  • subsite.client.com was where I was developing the site, one of a family of multisites.
  • subsite.com was a parked domain that went to just this site. It was this I'd just pointed to the IP of the box and that wasn't showing any images.

On the development domain, all worked fine. On the subsite domain, nothing.

The problem is that if you go to admin/settings/file-system you get a different thing depending on the domain! Each time, you get 'sites/CURRENT_DOMAIN/files', and the reason is this function:

function file_directory_path() {
  return variable_get('file_directory_path', conf_path() .'/files');
}

So if the files directory has not been explicitly set and you're just relying on defaults, you're getting the wrong one.

I don't follow the internal workings of the file system or imagecache (or even symlinks which I consider dark magic on the commandline) to know if this breaking of imagecache is a bug or not.

But at least this is a ten-second fix on future sites. You'll find me here reading this post when I next have to set one up.

Multisite On localhost Without Virtual Hosts

I've been putting off setting up multisite on my localhost for ages, mostly because in the past I've found getting Apache virtual hosts to work can be a bit tricky: not impossible, but the sort of thing where I could easily lose an hour on a minor thing I've forgotten to do. And after all, with a shiny new iMac and a hard drive whose proportions I can't even remember, why not just 'drush dl' all over again?

But I'm actually working on a multisite project at the moment, and suddenly getting this to work becomes more interesting than having another SVN copy of my code kicking around.

Given multisite can respond to subfolders, I was wondering if this could work when Drupal itself is in a subfolder, like this:

- webroot
-- drupal-1
-- drupal-2
-- drupal-multisite

Turns out it can. Suppose you want a new subsite called 'drupal-subsite'. Here's what to do in your webroot:

ln -s drupal-multisite drupal-subsite
cd drupal-multisite
mkdir sites/localhost.drupal-subsite
cp sites/default/default.settings.php sites/localhost.drupal-subsite/settings.php

Your lolcathost(*) sees just another subfolder that's a site; the symbolic link sends you to the existing Drupal folder; and finally, the multisite system sees that you're browsing 'localhost/drupal-subsite' and selects the localhost.drupal-subsite folder as the one holding your site.

You can also have your subsite explicitly sit below the main site like this:

- webroot
-- drupal-1
-- drupal-2
-- drupal-multisite
--- drupal-subsite

In which case your sites folder is localhost.drupal-multisite.drupal-subsite and the symlink should be inside the multisite folder:

cd drupal-multisite
ln -s . drupal-subsite
mkdir sites/localhost.drupal-multisite.drupal-subsite
cp sites/default/default.settings.php sites/localhost.drupal-multisite.drupal-subsite/settings.php

So when I next get a moment I'll consolidate the various test sites I have, and when I have more than one patch on the go that I want to keep segregated, I can just add a multisite, get a fresh CVS copy of the code, and get going.

I've added details to the documentation too.

(*) yes, I keep typing it like that.

Pages

Subscribe to RSS - drupal planet