You are here

taxonomy_server.module in Taxonomy import/export via XML 7

Extends taxonomy_xml to publish downloadable or queriable taxonomy vocabularies and terms. Extends the Drupal URLs to make vocabulary information available under /taxonomy/vocabulary and /taxonomy/vocabulary/{vid}.

This tool is more technical, and publishes the data in a machine-readable way, such as rdf from /taxonomy/vocabulary/{vid}/rdf

Publishing RDF Vocabularies] (Recipe 4)

When serving these resources, the server also tries to honor content- negotiation.

A normal web browser asking for /taxonomy/vocabulary/{vid} will be given an HTML tree view of the entire vocab.

An RDF-aware client asking for /taxonomy/vocabulary/{vid} (having "Accept: application/rdf+xml" in its $_REQUEST ) will be given a 303 Redirect to the rdf version of that resource.

As well as transparent content-negotiation, an html link 'alternate' is embedded in the HTML document, so that other user-agents can also detect that an rdf+xml version is available nearby.

@author dman dan@coders.co.nz

See also

vocabindex which presents this information also, in a sitemap way.

http://www.w3.org/TR/swbp-vocab-pub/ [Best Practice Recipes for

http://www.w3.org/TR/cooluris/

File

taxonomy_server/taxonomy_server.module
View source
<?php

/* double-commented to avoid conflict with svn
 */

/**
 * @file
 * Extends taxonomy_xml to publish downloadable or queriable taxonomy
 * vocabularies and terms.
 * Extends the Drupal URLs to make vocabulary information available under
 * /taxonomy/vocabulary and /taxonomy/vocabulary/{vid}.
 *
 * @see vocabindex which presents this information also, in a sitemap way.
 *
 * This tool is more technical, and publishes the data in a machine-readable
 * way, such as rdf from /taxonomy/vocabulary/{vid}/rdf
 *
 * @see http://www.w3.org/TR/swbp-vocab-pub/ [Best Practice Recipes for
 * Publishing RDF Vocabularies] (Recipe 4)
 *
 * When serving these resources, the server also tries to honor content-
 * negotiation.
 * @see http://www.w3.org/TR/cooluris/
 *
 * A normal web browser asking for /taxonomy/vocabulary/{vid} will be given an
 * HTML tree view of the entire vocab.
 *
 * An RDF-aware client asking for /taxonomy/vocabulary/{vid}
 * (having "Accept: application/rdf+xml" in its $_REQUEST )
 * will be given a 303 Redirect to the rdf version of that resource.
 *
 * As well as transparent content-negotiation, an html link 'alternate'
 * is embedded in the HTML document, so that other user-agents can also detect
 * that an rdf+xml version is available nearby.
 *
 * @author dman dan@coders.co.nz
 */
define("TAXONOMY_SERVER_CONTENT_NEGOTATION_DIR", drupal_get_path('module', 'taxonomy_server') . "/content_negotiation");

/**
 * Implementation of hook_menu: Define menu links.
 *
 */
function taxonomy_server_menu() {
  if (!module_exists('taxonomy_xml')) {
    return;
  }
  $items = array();
  $items['admin/structure/taxonomy/server'] = array(
    'title' => t('Taxonomy server'),
    'description' => t('Taxonomy server settings'),
    'type' => MENU_NORMAL_ITEM,
    'access arguments' => array(
      'administer taxonomy',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'taxonomy_server_settings',
    ),
  );
  $items['taxonomy/vocabulary'] = array(
    'title' => t('Vocabularies'),
    'access arguments' => array(
      'access vocabularies',
    ),
    'page callback' => 'taxonomy_server_vocabulary_page',
    'type' => MENU_SUGGESTED_ITEM,
  );
  $items['taxonomy/vocabulary/%taxonomy_vocabulary'] = array(
    'title' => t('Vocabulary'),
    'type' => MENU_NORMAL_ITEM,
    'access arguments' => array(
      'access content',
    ),
    'page callback' => 'taxonomy_server_vocabulary_page',
    'page arguments' => array(
      2,
    ),
  );
  $items['taxonomy/vocabulary/%taxonomy_vocabulary/view'] = array(
    'title' => 'View',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => 1,
  );

  // Depending on the settings, the data dump is either available
  // invisibly or as a visible tab
  $items['taxonomy/vocabulary/%taxonomy_vocabulary/rdf'] = array(
    'title' => 'RDF',
    'type' => variable_get('taxonomy_xml_show_data_tabs', FALSE) ? MENU_LOCAL_TASK : MENU_CALLBACK,
    'access arguments' => array(
      'access content',
    ),
    'page callback' => 'taxonomy_server_export_vocabulary',
    'page arguments' => array(
      2,
      'rdf',
    ),
    'weight' => 9,
  );

  // Hijack and override the normal taxonomy_term_page
  // to insert content-negotiation!
  // Returns the usual page in usual circumstances, but allows for clients to
  // get other formats too.
  // Copied from taxonomy.module
  $items['taxonomy/term/%taxonomy_term'] = array(
    'title' => 'Taxonomy term',
    'title callback' => 'taxonomy_term_title',
    'title arguments' => array(
      2,
    ),
    'page callback' => 'taxonomy_server_term_page',
    'page arguments' => array(
      2,
    ),
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
  );

  // Export individual taxonomy term information
  // This approach is closer to how rdf.module expects to do it
  $items['taxonomy/term/%taxonomy_term/rdf'] = array(
    'title' => 'RDF',
    'type' => variable_get('taxonomy_xml_show_data_tabs', FALSE) ? MENU_LOCAL_TASK : MENU_CALLBACK,
    'access arguments' => array(
      'access content',
    ),
    'page callback' => 'taxonomy_server_export_term',
    'page arguments' => array(
      2,
      'rdf',
    ),
    'weight' => 9,
  );
  return $items;
}

/**
 * Implementation of hook_permission().
 */
function taxonomy_server_permission() {
  return array(
    'access vocabularies' => array(
      'title' => t('Access vocabulary data exports'),
    ),
  );
}

/**
 * Implementation of hook_help().
 */
function taxonomy_server_help($path = '', $arg) {
  switch ($path) {
    case 'taxonomy/vocabulary':
      if (arg(2)) {
        return t("\n          Items with more than one parent may show up twice in a heirarchy.\n          This is normal.\n        ");
      }
      break;
    case 'admin/structure/taxonomy/server':
      return t("\n        Vocabularies are published at !vocabulary_link.\n        The vocabulary server publishes direct data downloads underneath the vocabulary and term pages.\n        This data is linked to from meta link hints inserted into the pages,\n        and is also available to semantic crawlers who request the page with content-negotiation.\n      ", array(
        '!vocabulary_link' => l('taxonomy/vocabulary', 'taxonomy/vocabulary'),
      ));
  }
}

/**
 * Form builder for server settings.
 *
 * @ingroup forms
 * @see taxonomy_server_settings_submit()
 */
function taxonomy_server_settings($form) {
  $form = array();
  $form['taxonomy_xml_show_data_tabs'] = array(
    '#title' => t('Display tabs for direct data download on the vocabulary and term pages'),
    '#type' => 'checkbox',
    '#default_value' => variable_get('taxonomy_xml_show_data_tabs', FALSE),
    '#description' => t('Access to this data is restricted through the user permissions. This option just controls whether the data is directly visible.'),
  );
  $form['#submit']['taxonomy_xml'] = 'taxonomy_server_settings_submit';
  return system_settings_form($form);
}

/**
 * Changing the settings may change the visibility of the tabs on vocab and term
 * pages.
 */
function taxonomy_server_settings_submit() {
  menu_rebuild();
  drupal_set_message(t('Updated tab visibility on vocabulary and term pages.'));
}

/**
 * An override of taxonomy_term_page. Checks if we can give the client
 * another version of this data.
 *
 * Checks content-negotiation, then passes back to the normal page renderer
 * unless an alternative was requested.
 *
 * @param $term object
 */
function taxonomy_server_term_page($term = NULL, $format_id = NULL) {

  // Only do content-negotiation if the format was not explicitly requested
  if ($format_id == NULL) {

    // Check if this request was from a content-negotiation compatible client.
    // Redirect them to the alternate version if so.
    $preferred = taxonomy_server_get_preferred_content($_SERVER["HTTP_ACCEPT"]);
    watchdog('taxonomy_server', "\n      Received a request for <b>{$_SERVER['REQUEST_URI']}</b> .\n      Seeing if I can give it better content via content-negotiation.\n      Client asked for ({$_SERVER['HTTP_ACCEPT']})\n      <br> It seems that the client would prefer '{$preferred}'\n    ");

    // TODO https & port support
    $rdf_uri = "http://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"] . "/rdf";
    if ($preferred == 'rdf') {
      watchdog('taxonomy_server', "Received a <b>content-negotiated</b> request for {$_SERVER['REQUEST_URI']} as {$preferred}. Boinging the request to that version of this page ({$rdf_uri}).");
      header("HTTP/1.1 303");
      header("Vary: Accept");
      header("Location: " . $rdf_uri);
      exit;
    }
  }

  // Maybe this page was a catcher because there was a format ID appended to the arguments
  if (!empty($format_id)) {

    // this vocab was requested in a specific export format.
    // Use the appropriate export function.
    return taxonomy_xml_export_term($term, $format_id);
  }

  // Else, return the normal HTML version!
  // @todo May need to start thinking about depth

  #dpm("Thought about, but decided against sending another format");

  #dpm($_SERVER['HTTP_ACCEPT']);

  // add a link tag so user-agents can see I have this semantic version avaialable
  drupal_add_html_head_link(array(
    'rel' => 'alternate',
    'type' => 'application/rdf+xml',
    'title' => $term->name,
    'href' => $rdf_uri,
  ));

  #    <link rel="alternate" type="application/rss+xml" title="semanticweb.org RSS Feed" href="http://semanticweb.org/index.php?title=Special:RecentChanges&amp;feed=rss" />

  // Need to load the taxonomy library inc
  module_load_include('inc', 'taxonomy', 'taxonomy.pages');
  return taxonomy_term_page($term);
}

/**
 * Display a page showing one or all available vocabularies
 *
 * Inspects content-negotiation for alt formats or explicit content-type
 * requests
 *
 * @param $vocabulary object
 */
function taxonomy_server_vocabulary_page($vocabulary = NULL, $format_id = NULL) {
  if (!$vocabulary) {

    // list all vocabs
    $vocabs = taxonomy_get_vocabularies();
    $link_list = array();
    foreach ($vocabs as $vocabulary) {

      #$tree = taxonomy_get_tree($vocabulary->vid);

      #$vocabcount = count($tree);
      $vocabcount = db_query("SELECT count(*) FROM {taxonomy_term_data} WHERE vid = :vid", array(
        ':vid' => $vocabulary->vid,
      ))
        ->fetchField();
      $link_list[$vocabulary->vid] = array(
        'title' => $vocabulary->name . t(" (!count terms)", array(
          '!count' => $vocabcount,
        )),
        'href' => 'taxonomy/vocabulary/' . $vocabulary->vid,
      );
    }
    return theme('links', array(
      'links' => $link_list,
      'attributes' => array(
        'class' => 'vocabulary-list',
      ),
    ));
  }
  else {

    // list the given vocab
    if (is_numeric($vocabulary)) {
      $vocabulary = taxonomy_vocabulary_load($vocabulary);
    }
    if (is_string($vocabulary)) {
      return t('Invalid argument. %vid is not a known vocabulary. Must be an integer.');
    }

    // Check if this request was from a content-negotiation compatible client.
    // Redirect them to the RDF version if so.
    $preferred = taxonomy_server_get_preferred_content($_SERVER["HTTP_ACCEPT"]);
    watchdog('taxonomy_server', "\n      Received a request for <b>{$_SERVER['REQUEST_URI']}</b> .\n      Seeing if I can give it better content via content-negotiation.\n      Client asked for ({$_SERVER['HTTP_ACCEPT']})\n      <br> It seems that the client would prefer '{$preferred}'\n    ");

    # <pre>" . print_r($_SERVER, 1) . "</pre>

    // TODO https & port support
    $rdf_uri = taxonomy_xml_get_vocabulary_uri($vocabulary) . "/rdf";
    if ($preferred == 'rdf') {
      watchdog('taxonomy_server', "Received a <b>content-negotiated</b> request for {$_SERVER['REQUEST_URI']} as {$preferred}. Boinging the request to that version of this page.");
      header("HTTP/1.1 303");
      header("Vary: Accept");
      header("Location: " . $rdf_uri);
      exit;
    }
    if (!empty($format_id)) {

      // this vocab was requested in a specific export format.
      // Use the appropriate export function.
      return taxonomy_xml_export_vocabulary($vocabulary, $format_id);
    }

    // Else, return the normal HTML version!
    // @todo May need to start thinking about depth
    $item_tree = taxonomy_server_get_vocab_as_item_tree($vocabulary->vid);
    drupal_add_css(drupal_get_path('module', 'taxonomy_server') . '/taxonomy_server.css');

    // add a link tag so user-agents can see I have this semantic version avaialable
    drupal_add_html_head_link(array(
      'rel' => 'alternate',
      'type' => 'application/rdf+xml',
      'title' => $vocabulary->name,
      'href' => $rdf_uri,
    ));

    #    <link rel="alternate" type="application/rss+xml" title="semanticweb.org RSS Feed" href="http://semanticweb.org/index.php?title=Special:RecentChanges&amp;feed=rss" />
    return theme('item_list', array(
      'items' => $item_tree,
      'title' => $vocabulary->name,
      'type' => 'ul',
      'attributes' => array(
        'class' => 'term-tree',
      ),
    ));
  }
}

/**
 * Insert a reference to our pure-xml data whenver the user-HTML page is
 * diplayed for a term
 *
 * @see hook_entity_prepare_view()
 */
function taxonomy_server_entity_prepare_view($entities, $entity_type) {
  switch ($entity_type) {
    case "taxonomy_term":
      foreach ($entities as $term) {
        $rdf_uri = url('taxonomy/term/' . $term->tid . '/rdf', array(
          'absolute' => TRUE,
        ));
        drupal_add_html_head_link(array(
          'rel' => 'alternate',
          'type' => 'application/rdf+xml',
          'title' => $term->name,
          'href' => $rdf_uri,
        ));
      }
  }
}
function taxonomy_server_get_vocab_as_item_tree($vid, $tid = 0) {
  $vocab_tree_list = array();
  $tree = taxonomy_get_tree($vid, $tid, 2, NULL);

  #dpm($tree);
  foreach ($tree as $child_term) {
    $vocab_tree_list[$child_term->name] = taxonomy_server_get_term_as_item_tree($vid, $child_term);
  }
  return $vocab_tree_list;
}
function taxonomy_server_get_term_as_item_tree($vid, $term) {
  $vocab_tree_list = array();
  $vocab_tree_list['data'] = l($term->name, 'taxonomy/term/' . $term->tid);
  $tree = taxonomy_get_tree($vid, $term->tid, 2, NULL);
  foreach ($tree as $child_term) {
    $vocab_tree_list['children'][$child_term->name] = taxonomy_server_get_term_as_item_tree($vid, $child_term);
  }
  return $vocab_tree_list;
}

/**
 * Returns an RDF representation of the vocab
 *
 * Channels a taxonomy_xml vocab export.
 */
function taxonomy_server_export_vocabulary($vocabulary, $format = 'rdf') {
  module_load_include('inc', 'taxonomy_xml', 'taxonomy_xml.export');
  return taxonomy_xml_export_vocabulary($vocabulary, $format);

  // that process actually prints out the result and exists, return is redundant.
}

/**
 * Returns an RDF representation of the term
 *
 * Channels a taxonomy_xml vocab export.
 */
function taxonomy_server_export_term($term, $format = 'rdf') {
  return taxonomy_xml_export_term($term, $format);

  // that process actually prints out the result and exists, return is redundant.
}

/**
 * Get the best uri requested, based on content negotiation and guesswork.
 *
 * stolen  from neologism.module
 */
function taxonomy_server_get_preferred_uri($uri, $accept) {
  $uri = "http://" . $_SERVER["HTTP_HOST"] . $uri;
  $preferred = taxonomy_server_get_preferred_content($accept);
  if (substr($uri, -1) == "/") {
    return $uri . $preferred;
  }
  else {
    return $uri . "/" . $preferred;
  }
}

/**
 * Get the best content type requested, depending on what the server asked for.
 *
 * stolen from neologism.module
 */
function taxonomy_server_get_preferred_content($accept) {
  if (isset($accept)) {
    module_load_include('inc.php', 'taxonomy_server', 'content_negotiation/content_negotiation');
    if (!class_exists('content_negotiation')) {
      watchdog('taxonomy_server', "Failed to load the content negotiation library expected to be at %path. This means I won't be serving alternative formats transparently", array(
        '%path' => TAXONOMY_SERVER_CONTENT_NEGOTATION_DIR . "/content_negotiation.inc.php",
      ));
      return 'html';
    }
    $best = content_negotiation::mime_best_negotiation();
    if ($best == "application/rdf+xml") {
      return 'rdf';
    }
    else {
      if ($best == "text/rdf+n3") {
        return 'n3';
      }
      else {
        if ($best == "application/xml") {
          return 'xml';
        }
        else {
          return 'html';
        }
      }
    }
  }
  else {
    return 'rdf';
  }
}

Functions

Namesort descending Description
taxonomy_server_entity_prepare_view Insert a reference to our pure-xml data whenver the user-HTML page is diplayed for a term
taxonomy_server_export_term Returns an RDF representation of the term
taxonomy_server_export_vocabulary Returns an RDF representation of the vocab
taxonomy_server_get_preferred_content Get the best content type requested, depending on what the server asked for.
taxonomy_server_get_preferred_uri Get the best uri requested, based on content negotiation and guesswork.
taxonomy_server_get_term_as_item_tree
taxonomy_server_get_vocab_as_item_tree
taxonomy_server_help Implementation of hook_help().
taxonomy_server_menu Implementation of hook_menu: Define menu links.
taxonomy_server_permission Implementation of hook_permission().
taxonomy_server_settings Form builder for server settings.
taxonomy_server_settings_submit Changing the settings may change the visibility of the tabs on vocab and term pages.
taxonomy_server_term_page An override of taxonomy_term_page. Checks if we can give the client another version of this data.
taxonomy_server_vocabulary_page Display a page showing one or all available vocabularies

Constants

Namesort descending Description
TAXONOMY_SERVER_CONTENT_NEGOTATION_DIR @file Extends taxonomy_xml to publish downloadable or queriable taxonomy vocabularies and terms. Extends the Drupal URLs to make vocabulary information available under /taxonomy/vocabulary and /taxonomy/vocabulary/{vid}.