Fix for A custom node type for vehicles using taxonomy to manage make / model details

<?php
// $Id: vehicle.module,v 1.26 2008/10/9 13:40:00 michaelphipps Exp $

/**
 * @node_vehicle
 * This module defines a custom node type with a custom make / model form element
 * that is managed via taxonomy.
 *
 * No Database required
 */


/**
 * Implementation of hook_node_info().
 */

function node_vehicle_node_info() {
  return array(
    'vehicle' => array(
      'name' => t('Vehicle'),
      'module' => 'node_vehicle',
      'description' => t("This is an Vehicle type with a few fields."),
      'has_title' => TRUE,
      'title_label' => t('Example Title'),
      'has_body' => TRUE,
      'body_label' => t('Example Body'),
    )
  );
}

/**
* Implementation of hook_menu().
*
*  Provide a simple menu hook to access the javascript function.
*/

function node_vehicle_menu() {
  $items['vehicle/js'] = array(
    'type' => 'MENU_CALLBACK',
    'page callback' => 'node_vehicle_makemodel_javascript',
    'access arguments' => array('access content'),
  );
  return $items;
}

/**
 * Implementation of hook_access().
 */

function node_vehicle_access($op, $node, $account) {
  if ($op == 'create') {
    return user_access('create vehicle content', $account);
  }

  if ($op == 'update') {
    if (user_access('edit any vehicle content', $account) || (user_access('edit own vehicle content', $account) && ($account->uid == $node->uid))) {
      return TRUE;
    }
  }

  if ($op == 'delete') {
    if (user_access('delete any vehicle content', $account) || (user_access('delete own vehicle content', $account) && ($account->uid == $node->uid))) {
      return TRUE;
    }
  }
}

/**
 * Implementation of hook_perm().
 */

function node_vehicle_perm() {
  return array(
    'create vehicle content',
    'delete own vehicle content',
    'delete any vehicle content',
    'edit own vehicle content',
    'edit any vehicle content',
  );
}


/**
 * Implementation of hook_elements().
 */

function node_vehicle_elements() {
  $type['makemodel'] = array(
    '#input' => TRUE,
    '#process' => array('node_vehicle_makemodel_expand'),
    '#element_validate' => array('node_vehicle_makemodel_validate'),
    '#default_value' => array('make' => '', 'model' => ''),
  );
  return $type;
}

/**
 * The process callback to expand the makemodel control.
 */

function node_vehicle_makemodel_expand($element) {
  $element['#tree'] = TRUE;

  if ( !isset($element['#value']) ) {
    $element['#value'] = array('make' => '', 'model' => '');
  }

  // Get the list of makes from the make/models vocabulary
  $vid = 1;// Should the user choose their Taxonomy category?
  $parent = 0;
  $max_depth = 1;
  $makes = taxonomy_get_tree($vid, $parent, -1, $max_depth);
  foreach ($makes as $make) {
    $make_options[$make->tid] = $make->name;
  }
 
  if ( isset($element['#value']['make']) ) {
    $models = taxonomy_get_tree($vid, $element['#value']['make'], -1, $max_depth);
    foreach ($models as $model) {
      $model_options[$model->tid] = $model->name;
    }
  }
  else {
    $model_options = array();
  }
 
  $element['make'] = array(
    '#type' => 'select',
    '#value' => $element['#value']['make'],
    '#options' => $make_options,
    '#attributes' =>  array('onchange' => 'changeModel();'),
  );
 
  $element['model'] = array(
    '#type' => 'select',
    '#value' => $element['#value']['model'],
    '#options' => $model_options,
  );
 
  return $element;

}

/**
 * Our element's validation function.
 *
 * Not currently being used, because I'm unsure what validation I need to do on taxonomy
 * data and if it is even needed at all.
 */

function node_vehicle_makemodel_validate($form, &$form_state) {
  if ( isset($form['#value']['make']) ) {
    if (0 == 1) {
      form_error($form['make'], t('Please select a make.'));
    }
  }
  if ( isset($form['#value']['model']) ) {
    if (0 == 1) {
      form_error($form['model'], t('Please select a model.'));
    }
  }
  return $form;
 
}



/**
 * Theme function to format the output.
 *
 * We use the container-inline class so that all three of the HTML elements
 * are placed next to each other, rather than on separate lines.
 */

function theme_makemodel($element) {
  return theme('form_element', $element, '<div class="container-inline">'. $element['#children'] .'</div>');
}

function node_vehicle_makemodel_javascript() {

  // Getting the list of makes from the make/models vocabulary
  $vid = 1 ;  // Should the user choose their Taxonomy category?
  $parent = 0;
  $max_depth = 1;
  $makes = taxonomy_get_tree($vid, $parent, -1, $max_depth);
  foreach ($makes as $make) {
    $make_options[$make->tid ] = $make->name;
  }

  unset($temp);
  $content .=  "model = new Array();\n";
   
  $result = db_query("SELECT term_data.tid AS id, term_hierarchy.parent AS parentid, term_data.name AS model FROM {term_data} join {term_hierarchy} ON (term_data.tid = term_hierarchy.tid) WHERE term_data.vid=1 AND term_hierarchy.parent >0 ORDER BY parentid, model");
  while ($models = db_fetch_object($result)) {
    if ( $temp == $models->parentid ) {
      $content .=  ",";
    }
    else {
      if ( isset($temp) ) $content .=  ");\n";
      $temp=$models->parentid;
      $content .=  "model[$temp] = new Array(\n";
    }
    $content .=  "'". $models->model ."',". $models->id ."\n";
  }

  $content .=  ");\n";

  $content .= '//change model
function changeModel(){
var objMake = document.getElementById("edit-makemodel-make");
var objModel = document.getElementById("edit-makemodel-model");

var make_id = objMake[objMake.selectedIndex].value;

//clear model select
oldLength = objModel.length;
for (i = oldLength; i >= 0; i--) objModel.options[i] = null;

//build model select
if (make_id=="")
  objModel.options[0] = new Option("Any model","");
else
for (i = 0; i <= model[make_id].length-1; i=i+2)
objModel.options[i/2] = new Option(model[make_id][i],model[make_id][i+1]);

}
'
;

  header("Content-type: text/javascript");
  print $content;
  exit();

}


/**
 * Implementation of hook_form().
 *
 * Describe the form for collecting vehicle information.
 */

function node_vehicle_form(&$node) {
  // The site admin can decide if this node type has a title and body, and how
  // the fields should be labeled. We need to load these settings so we can
  // build the node form correctly.
  $type = node_get_types('type', $node);

  if ($type->has_title) {
    $form['title'] = array(
      '#type' => 'textfield',
      '#title' => check_plain($type->title_label),
      '#required' => TRUE,
      '#default_value' => $node->title,
      '#weight' => -5
    );
  }

  if ($type->has_body) {
    $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count);
  }
 
  // Add the javascript to make the make/model selection work properly
  drupal_add_js('vehicle/js');

  // Now we define the form elements specific to our node type.  
  $form['makemodel']= array(
    '#type' => 'makemodel',
    '#title' => t("Make/Model"),
    '#default_value' => array('make' => $node->make, 'model' => $node->model),
    '#description' => t('Select a vehicle make and model.'),
  );

  return $form;
}

/**
 * Implementation of hook_validate().
 *
 * Again, unsure if I need this since I am collecting taxonomy tids from select boxes.
 */

function node_vehicle_validate(&$node) {
  // include validation logic (if required)
}

/**
 * Implementation of hook_insert().
 *
 * Saves the taxonomy details from the makemodel field.
 */

function node_vehicle_insert($node) {
  taxonomy_node_save($node, $node->makemodel);
}

/**
 * Implementation of hook_update().
 *
 * Saves the taxonomy details from the makemodel field
 */

function node_vehicle_update($node) {
  // if this is a new node or we're adding a new revision,
  if ($node->revision) {
    node_vehicle_insert($node);
  }
  taxonomy_node_save($node, $node->makemodel);
}

/**
 * Implementation of hook_nodeapi().
 *
 * When a node revision is deleted, we need to remove the corresponding record
 * from our table. The only way to handle revision deletion is by implementing
 * hook_nodeapi().
 */

function node_vehicle_nodeapi(&$node, $op, $teaser, $page) {
  switch ($op) {
    case 'delete revision':
      // Do taxonomy terms get removed automatically?  If so, nothing is required here
     
      break;
  }
}

/**
 * Implementation of hook_delete().
 *
 * When a node is deleted, we need to remove all related records from out table.
 */

function node_vehicle_delete($node) {
  // No logic is required here at this moment
}

/**
 * Implementation of hook_load().
 *
 * Need to separate the vehicle terms into separate make / model elements
 * so they can be used to populate existing nodes
 */

function node_vehicle_load($node) {
  $vid=1;
  $make_model_array = taxonomy_node_get_terms_by_vocabulary($node, $vid);

  foreach ($make_model_array as $key => $value) {
    if ( taxonomy_get_parents($value->tid) ) {
      $model=$key;
    }
    else {
      $make=$key;
    }
  }

  $additions = new stdClass();
  $additions->make = $make;
  $additions->model = $model;

  return $additions;
}

/**
 * Implementation of hook_view().
 *
 * This is a typical implementation that simply runs the node text through
 * the output filters.
 *
 * Do I need this?
 */

function node_vehicle_view($node, $teaser = FALSE, $page = FALSE) {
  $node = node_prepare($node, $teaser);
  return $node;
}


/**
 * Implementation of hook_theme().
 *
 * Tell Drupal about theme functions and their arguments.
 */

function node_vehicle_theme() {
  return array(
     'makemodel' => array(
      'arguments' => array('element'),
    ),
  );
}

Submit Fix

Any tags you'd like to associate with your code, delimitered by commas (example: Views, CCK, Module, etc).
Select the syntax highlighting mode to use.