Using Taxonomy Menu and Entity Reference Prepopulate to create an easy & awesome content admin UX in Drupal

Backstory

Part of a site I'm currently building is set up so that approved users (aka content submitters) can submit resources. Another category of users, content administrators, will need to be able to define the different types of resources (ie. download, YouTube link, PDF file). Neither user group needs to know or care that we're using a single Resource content type to manage these items; the goal is for content submitters to "Post a YouTube link", instead of going to "Create Resource" and selecting the taxonomy term for a YouTube link.

An additional reason for the following custom configuration, beyond reducing the content submitter's cognitive load, is to allow the content administrators the ability to select the method by which a resource of a specific type should be included. Currently the choices are adding a link and attaching a file.

Resource content type fields. Both Link and File fields are defined in the content type; the field shown to the user depends on the Resource Method defined by the taxonomy term (next screenshot)

Taxonomy term creation screen. Content admins can specify here whether the term should provide a file upload field or a link field to content submitters. (You can also see the Icon field, which will be a subject for another post!)

Since content administrators can select either method, I've added both as fields on the Resource CT, and have defined both options for the Resource Method selection list on the Resources taxonomy term. Whether the Link or File field appears when adding a new Resource depends on the option selected by the associated taxonomy term, using Entity Reference Prepopulate (ERP) to ensure that a term is always selected.

Part 1: Set up entityreference_prepopulate

In order to use ERP, the Resource Type field on the Resource node must be an Entity Reference field (not a core taxonomy term field). Once the ERP field is set up, we can use the URL to preopulate the Resource Type field using the pattern node/add/resource?field_resource_type=[tid].

What the content submitter sees when creating a node using a valid ERP link

ERP field settings, described below in more detail

Explaination of ERP options:

  • Action disable field: tid must be supplied and cannot be changed
  • Fallback behavior: what happens if the tid is not supplied or is not valid (tid does not exist, is in wrong vocabulary, etc)
  • Redirect path: where the  user will go if this fallback behavior is chosen (more on my selection in a moment)
  • In addition to this patch that clarifies what the Redirect path is intended for, I've also added the ability to provide a configurable message when redirect takes place.

Part 2: Getting most of the way there with taxonomy_menu

Next I needed to find an easy way for links to be dynamically created, so that content submitters could just click a "Submit [Resource Type]" link, with one link created per taxonomy terms defined by the content administrator. Before looking into a custom solution, I decided to investigate how Taxonomy Menu (TM) worked (spoiler: It worked just fine and saved me a ton of custom work!).

I decided to try to add these links to the Navigation menu, under node/add/resource

Goal for Navigation menu. (Ok, not the goal, and actual working screenshot with Masquerade running, which is what the "Switch back" link is)

Options for Taxonomy Menu

Using TM, I was able to define the parent menu item for all taxonomy_menu links created in this vocabulary. Using the dev version of the module and the additional module taxonomy_menu_custom_paths, I could even customize what the item's path should be. Awesome, I'll just use node/add/resource?field_resource_type=[tid] ... what do you mean I can't?

In order for the custom path option to work, the path must exist in Drupal. No problem there, I initially thought, node/add/resource is a valid path and I just need to figure out how to get the query argument appended. Yeah, the menu system doesn't like that (even when I yanked out the validation of taxonomy_menu_custom_paths).

Part 3: Gluing the pieces together

Thanks to ERP, I had the ability to force the selection on the taxonomy term field to drive the content type's form options, and I was able to make automagical menu links appear when new taxonomy terms were created using TM.

/**
 * Implements hook_menu()
 */
function membercts_menu() {
  $items['node/add/resource/%'] = array(
    'title callback' => 'Redirecting',
    'page callback' => 'membercts_tm_erp_redirect',
    'page arguments' => array(3),
    'access callback' => TRUE
  );

  return $items;
}

/** Redirect from TM custom path to ERP page **/
function membercts_tm_erp_redirect($tid) {
  drupal_goto('node/add/resource', array('query' => array('field_resource_type' => $tid)));
}

I put this code in the .module file of the Feature I created that holds my ERP and TM configurations for this specific vocabulary and content type, but you could also put it in a regular stand-alone module.

The hook_menu call defines the path that TM will use: node/add/resource/[tid]. It also defines the callback for those pages via drupal_goto, which allows me to use the query string expected by ERP.

Part 4: But wait, there's more!

So, what happens if someone tries cheating the system by trying out different numbers for the tid, or if someone just tries node/add/resource without any arguments? Up to this point, the user would actually end up in a redirect loop, based on the ERP configuration above (redirect fallback to node/add/resource). To avoid this and make things extra user-friendly, I'm overriding the node/add/resource page and dispaying its menu children (the taxonomy term items) as a system menu, plus adding some custom help text.

/**
 * Implements hook_help()
 */
function membercts_help($path, $arg) {
  switch ($path) {
    case 'node/add/resource':
      $query = drupal_get_query_parameters();
      if (count($query) == 0) {
        $output = '';
        $output .= '<p>' . t('Select a resource type:') . '</p>';
      }
      break;
  }
  if (isset($output)) {return $output;}
}

/**
 * Implements hook_menu_alter()
 */
function membercts_menu_alter(&$items) {
  $items['node/add/resource']['page callback'] = 'membercts_node_add_resource';
}

/**
 * Overrides node_add() for resource nodes
 */
function membercts_node_add_resource($type) {
  $query = drupal_get_query_parameters();
  if (array_key_exists(RESOURCE_ERP_FIELD, $query)) {
    $show_taxonomy_menu = FALSE;
  } else {$show_taxonomy_menu = TRUE;}

  if ($show_taxonomy_menu) {
    module_load_include('inc', 'system', 'system.admin');
    $output = system_admin_menu_block_page();
    return $output;
  } else {
    global $user;
    $types = node_type_get_types();
    $node = (object) array('uid' => $user->uid, 'name' => (isset($user->name) ? $user->name : ''), 'type' => $type, 'language' => LANGUAGE_NONE);
    drupal_set_title(t('Create @name', array('@name' => $types[$type]->name)), PASS_THROUGH);
    $output = drupal_get_form($type . '_node_form', $node);
    return $output;
  }
}

The page override actually works because ERP doesn't check for the presence of a valid prepopulate argument (tid) until the form is partially loaded, and my menu override means the form does not load.

Pretty cool, huh?

Of course, you can see from the screenshots there's all sort of other goodies in play: the custom icon selector, the actual form_alter that figures out which Resource field to actually show, a rating system, and a snazzy workflow that allows content administrators to manage user submissions.