Case Study: Porting Faq_Ask to D6
This is a case-study of porting a module (Faq_Ask in this case) from Drupal 5 to Drupal 6.
Info file
Two changes had to be made here. First, there was the addition of a core definition:
core = 6.xSecondly, there was a nasty 'gotcha'. The dependencies line needed some square brackets in it:
dependencies[] = faqInstall file
The only change that had to be made here was to use the database schema itself to create and delete the tables, rather than doing it with SQL. As the schema hook was already written, the install and uninstall hooks became very simple:
<?php
/**
* Implementation of hook_install().
*/
function faq_ask_install() {
$result = drupal_install_schema('faq_ask');
if (count($result) > 0) {
drupal_set_message(t('faq_ask module installed.'));
}
else {
drupal_set_message(t('faq_ask table creation failed. Please "uninstall" the module and retry.'));
}
}
/**
* Implementation of hook_uninstall().
*/
function faq_ask_uninstall() {
drupal_uninstall_schema('faq_ask');
// ... delete variables ...
drupal_set_message(t('faq_ask module uninstalled.'));
}
?>hook_help
The parameters have changed for this hook. Instead a single "$section," there are now two parameters ("$path" and "$arg"). Additionally, placeholders are now available for things that used to be done with the "arg" function.
The old code:
switch ($section='') {
case 'admin/help#faq_ask':
$output .= ...
return $output;
case 'faq_ask/'. arg(1):
case 'faq_ask':
...
}Is now:
switch ($path) {
case 'admin/help#faq_ask':
$output .= ...
return $output;
case 'faq_ask/%':
case 'faq_ask':
...
}hook_menu
This hook has changed quite a lot between D5 and D6, so each menu entry had to change from something along the lines of:
<?php
$items[] = array(
'path' => 'faq_ask/answer',
'title' => t('Answer a question'),
'callback' => 'faq_ask_answer',
'access' => user_access('answer question'),
'type' => MENU_CALLBACK,
);
?><?php
$items['faq_ask/answer/%node'] = array(
'title' => 'Answer a question',
'page callback' => 'faq_ask_answer',
'page arguments' => array(2),
'access arguments' => array('answer question'),
'type' => MENU_CALLBACK,
);
?><?php
/**
* Implementation of hook_menu()
*/
function faq_ask_menu() {
$items = array();
$items['admin/settings/faq/ask'] = array(
'title' => 'Experts',
'page callback' => 'drupal_get_form',
'page arguments' => array('faq_ask_settings_form'),
'access arguments' => array('administer faq'),
'description' => t('Allows the user to configure the Ask_FAQ module.'),
'type' => MENU_LOCAL_TASK,
'weight' => -2,
);
$items['faq_ask'] = array(
'title' => 'Ask a question',
'page callback' => 'faq_ask_page',
'access callback' => 'user_access',
'access arguments' => array('ask question'),
'weight' => 1,
);
$items['faq_ask/%'] = array(
'page arguments' => array(1),
'access arguments' => array('ask question'),
);
$items['faq_ask/answer/%node'] = array(
'title' => 'Answer a question',
'page callback' => 'faq_ask_answer',
'page arguments' => array(2),
'access arguments' => array('answer question'),
'type' => MENU_CALLBACK,
);
$items['faq_ask/edit/%node'] = array(
'title' => 'Edit a question',
'page callback' => 'drupal_get_form',
'page arguments' => array('faq_ask_form', null, 2),
'access arguments' => array('answer question'),
'type' => MENU_CALLBACK,
);
$items['faq_ask/more'] = array(
'title' => 'List more unanswered questions',
'page callback' => 'faq_ask_list_more',
'access callback' => 'faq_ask_user_access_or',
'access arguments' => array('answer question', 'ask question'),
'type' => MENU_CALLBACK,
);
return $items;
}
?><?php
/**
* Determines whether the current user has one of the given permissions.
*/
function faq_ask_user_access_or($string1, $string2) {
return user_access($string1) || user_access($string2);
}
?>On the note of wildcards, 'faq_ask/answer/%node' now uses an explicit node wildcard, whereas before it used an implicit nid:
<?php
function faq_ask_answer($nid) {
?><?php
function faq_ask_answer($node) {
// Change the status to published.
db_query("UPDATE {node} SET status=1 WHERE nid=%d", $node->nid);
// Need to invoke node/##/edit.
drupal_goto('node/'. $node->nid .'/edit');
}
?>Other pages also had changes to the new wildcard system, which resulted in similar function prototype changes.
Add CSS
In D5, it was common practice to use drupal_add_css (or _js) within the hook_menu code. In D6 this doesn't work.
However, hook_init was also changed and is now a good place to add the CSS. See http://api.drupal.org/api/function/hook_init.
<?php
function hook_init() {
drupal_add_css(drupal_get_path('module', 'faq_ask') .'/faq_ask.css');
}
?>Forms API
FAPI functions now generally take form values through a form state variable, which is now at the start of the parameter list, so the old D5 function prototype
<?php
function faq_ask_form($tid, $nid, $form_values = null) {
?><?php
function faq_ask_form($form_state, $tid, $node) {
?>$form_state['post'] is now used in place of $form_values. Likewise, the form submission handlers also had to change to use form state. The old D5 prototype was <?php
function faq_ask_form_submit($form_id, $form_values) {
?><?php
function faq_ask_form_submit($form, &$form_state) {
$form_values = $form_state['values'];
?>url() and l()
The url and l functions took a long argument list of options under D5. D6 changed this to an array containing the options, so a piece of code before porting:
<?php
$items[] = l('<strong>'. t('more...') .'</strong>', 'faq_ask/more', array(), null, null, false, true);
?><?php
$items[] = l('<strong>'. t('more...') .'</strong>', 'faq_ask/more', array('html' => TRUE));
?><?php
url('faq_ask/answer/'. $node->nid, null, null, true)
?><?php
url('faq_ask/answer/'. $node->nid, array('absolute' => TRUE))
?>Miscellaneous changes
The watchdog function also has some changes. Remembering to update this can be difficult, as in normal testing, watchdog will not be called unless you really try and break things, so you may not notice that your calls to it longer work. t() is now automatically called (or not) by the watchdog function, so you should not call t() on the messages you pass. So a simple call under D5:
<?php
watchdog('FAQ_Ask', t('Expert notification email sent.') .' '. $to, WATCHDOG_NOTICE);
?>This became:
<?php
watchdog('FAQ_Ask', 'Expert notification email sent. !to', array('!to' => $to), WATCHDOG_NOTICE);
?>array()) even if you don't use any placeholders in the message text.
A subtle, but significant change is in the code that creates the suggested taxonomy term programmatically and then provides it for the faq node. The term is originally built as an array; this was fine in 5.x, but changes to the taxonomy module in 6.x cause several problems. The "fix" was to force the term to be recast as an object before creating the node:
$term = (object)$term;
A number of fixes to coding style were made during the porting process. Updating to the new standards is a perfect time to get a refresher on all the standards which didn't change!
One more thing: Use the Coder module to double check things. There were some other changes that could now be done in D6 that were unavailable in D5, such as the "db_placeholders" function. Coder reminded me of these and some other things that should have been done. Don't let your code leave home without it!
