You are here

biblio.module in Bibliography Module 7.2

File

biblio.module
View source
<?php

/**
 *   biblio.module for Drupal
 *
 *   Copyright (C) 2006-2011  Ron Jerome
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License along
 *   with this program; if not, write to the Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */
define('BIBLIO_VERSION', '7.x-2.x-dev');
function _biblio_get_auth_types($auth_category, $biblio_type) {
  static $auth_types = array();
  if (empty($auth_types)) {
    $db_res = db_query("SELECT * FROM {biblio_contributor_type}");
    foreach ($db_res as $row) {
      $auth_types[$row->auth_category][$row->biblio_type][] = $row->auth_type;
    }
  }

  // fall back to defaults, if no author types are defined for this biblio_type
  // todo: this is throwing errors in the log. fix it. supressing errors for now...
  $result = isset($auth_types[$auth_category][$biblio_type]) ? $auth_types[$auth_category][$biblio_type] : @$auth_types[$auth_category][0];
  return $result;
}
function _biblio_get_auth_type($auth_category, $biblio_type) {
  $result = (array) _biblio_get_auth_types($auth_category, $biblio_type);

  // return first element of the array
  return empty($result) ? NULL : current($result);
}

/**
 * Translate field titles and hints through the interface translation system, if
 * the i18nstrings module is enabled.
 */
function _biblio_localize_fields(&$fields) {
  if (module_exists('i18nstrings')) {
    foreach ($fields as $key => $row) {
      $fields[$key]['title'] = tt("biblio:field:{$row['ftdid']}:title", $fields[$key]['title']);
      $fields[$key]['hint'] = tt("biblio:field:{$row['ftdid']}:hint", $fields[$key]['hint']);
    }
  }
}

/**
 * Translate a publication type through the interface translation system, if
 * the i18nstrings module is enabled.
 *
 * @param integer $tid
 *   The biblio publication type identifier.
 *
 * @param string $value
 *   The string to translate.
 *
 * @param string $field
 *   The publication type field to translate (either 'name' or 'description').
 *
 * @return
 *   Translated value.
 */
function _biblio_localize_type($publication_type, $field = 'name') {
  if (module_exists('i18nstrings')) {
    return tt("biblio:type:{$field}", $publication_type);
  }
  return $publication_type;
}

/**
 * Implementation of hook_locale().
 */
function biblio_locale($op = 'groups', $group = NULL) {
  switch ($op) {
    case 'groups':
      return array(
        'biblio' => t('Biblio'),
      );
    case 'refresh':
      if ($group == 'biblio') {
        biblio_locale_refresh_fields();
        biblio_locale_refresh_types();
      }
      break;
  }
}

/**
 * Refresh all translatable field strings.
 *
 * @param integer $tid
 *   Biblio publication type id whose field strings are to be refreshed. If not
 *   specified, strings for all fields will be refreshed.
 */
function biblio_locale_refresh_fields($tid = NULL) {
  if (module_exists('i18nstrings')) {
    if (isset($tid)) {
      $result = db_query('SELECT d.* FROM {biblio_field_type} b INNER JOIN {biblio_field_type_data} d ON b.ftdid = d.ftdid WHERE tid = :tid', array(
        ':tid' => $tid,
      ));
    }
    else {
      $result = db_query('SELECT * FROM {biblio_field_type_data}');
    }
    foreach ($result as $row) {
      tt("biblio:field:{$row->ftdid}:title", $row->title, NULL, TRUE);
      tt("biblio:field:{$row->ftdid}:hint", $row->hint, NULL, TRUE);
    }
  }
}

/**
 * Refresh all publication type strings.
 *
 * @param integer $tid
 *   Biblio publication type id whose field strings are to be refreshed. If not
 *   specified, strings for all fields will be refreshed.
 */
function biblio_locale_refresh_types($tid = NULL) {
  if (module_exists('i18nstrings')) {
    if (isset($tid)) {
      $result = db_query('SELECT * FROM {biblio_types} WHERE tid = :tid', array(
        ':tid',
        $tid,
      ));
    }
    else {
      $result = db_query('SELECT * FROM {biblio_types} WHERE tid > 0');
    }
    foreach ($result as $row) {
      tt("biblio:type:{$row->tid}:name", $row->name, NULL, TRUE);
      tt("biblio:type:{$row->tid}:description", $row->description, NULL, TRUE);
    }
  }
}

/**
 * Implements hook_init().
 *
 * @global object $user
 * @global array $conf
 */
function biblio_init() {
  global $user, $conf;
  drupal_add_css(drupal_get_path('module', 'biblio') . '/biblio.css');
  if ($user->uid === 0) {

    // Prevent caching of biblio pages for anonymous users so session variables work and thus filering works
    $base = variable_get('biblio_base', 'biblio');
    if (drupal_match_path($_GET['q'], "{$base}\n{$base}/*")) {
      $conf['cache'] = FALSE;
    }
  }
}

/**
 * Implements hook_cron().
 */
function biblio_cron() {
  require_once drupal_get_path('module', 'biblio') . '/includes/biblio.contributors.inc';
  require_once drupal_get_path('module', 'biblio') . '/includes/biblio.keywords.inc';
  $interval = variable_get('biblio_orphan_clean_interval', 24 * 60 * 60);

  //defaults to once per day
  if (time() >= variable_get('biblio_orphan_clean_next_execution', 0)) {
    biblio_delete_orphan_authors();
    biblio_delete_orphan_keywords();
    variable_set('biblio_orphan_clean_next_execution', time() + $interval);
  }
}

/**
 * Implements hook_theme().
 */
function biblio_theme($existing, $type, $theme, $path) {
  $path = drupal_get_path('module', 'biblio');
  return array(
    'biblio_alpha_line' => array(
      'file' => '/includes/biblio.theme.inc',
      'variables' => array(
        'type' => 'author',
        'current' => NULL,
        'path' => NULL,
      ),
    ),
    'biblio_admin_keyword_edit_form' => array(
      'file' => '/includes/biblio.admin.inc',
      'render element' => 'form',
    ),
    'biblio_admin_author_types_form' => array(
      'file' => '/includes/biblio.admin.inc',
      'render element' => 'form',
    ),
    'biblio_admin_type_mapper_form' => array(
      'file' => '/includes/biblio.admin.inc',
      'render element' => 'form',
    ),
    'biblio_admin_io_mapper_form' => array(
      'file' => '/includes/biblio.admin.inc',
      'render element' => 'form',
    ),
    'biblio_admin_io_mapper_add_form' => array(
      'file' => '/includes/biblio.admin.inc',
      'render element' => 'form',
    ),
    'biblio_admin_field_mapper_form' => array(
      'file' => '/includes/biblio.admin.inc',
      'render element' => 'form',
    ),
    'biblio_admin_types_edit_form' => array(
      'file' => '/includes/biblio.theme.inc',
      'render element' => 'form',
    ),
    'biblio_admin_types_form' => array(
      'file' => '/includes/biblio.theme.inc',
      'render element' => 'form',
    ),
    'biblio_field_tab' => array(
      'file' => '/includes/biblio.theme.inc',
      'render element' => 'form',
    ),
    'biblio_admin_author_edit_form' => array(
      'file' => '/includes/biblio.theme.inc',
      'render element' => 'form',
    ),
    'biblio_openurl' => array(
      'file' => '/includes/biblio.theme.inc',
      'variables' => array(
        'openURL',
      ),
    ),
    'biblio_style' => array(
      'file' => '/includes/biblio.theme.inc',
      'variables' => array(
        'biblio' => NULL,
        'style_name' => 'classic',
      ),
    ),
    'biblio_long' => array(
      'file' => '/includes/biblio.theme.inc',
      'variables' => array(
        'node' => NULL,
        'base' => 'biblio',
        'style_name' => 'classic',
      ),
    ),
    'biblio_tabular' => array(
      'file' => '/includes/biblio.theme.inc',
      'variables' => array(
        'node' => NULL,
        'base' => 'biblio',
        'teaser' => FALSE,
      ),
    ),
    'biblio_entry' => array(
      'file' => '/includes/biblio.theme.inc',
      'variables' => array(
        'node',
        'style_name' => 'classic',
      ),
    ),
    'biblio_format_authors' => array(
      'file' => '/includes/biblio.theme.inc',
      'variables' => array(
        'contributors' => NULL,
        'options' => array(),
      ),
    ),
    'biblio_page_number' => array(
      'file' => '/includes/biblio.theme.inc',
      'variables' => array(
        'orig_page_info' => NULL,
        'page_range_delim' => "-",
        'single_page_prefix' => '',
        'page_range_prefix' => '',
        'total_pages_prefix' => '',
        'single_page_suffix' => '',
        'page_range_suffix' => '',
        'total_pages_suffix' => '',
        'shorten_page_range_end' => FALSE,
      ),
    ),
    'biblio_author_link' => array(
      'file' => '/includes/biblio.theme.inc',
      'variables' => array(
        'author',
      ),
    ),
    'biblio_filters' => array(
      'file' => '/includes/biblio.theme.inc',
      'render element' => 'form',
    ),
    'form_filter' => array(
      'file' => '/includes/biblio.theme.inc',
      'render element' => 'form',
    ),
    'biblio_export_links' => array(
      'file' => '/includes/biblio.theme.inc',
      'variables' => array(
        'node' => NULL,
        'filter' => array(),
      ),
    ),
    'biblio_download_links' => array(
      'file' => '/includes/biblio.theme.inc',
      'variables' => array(
        'node',
      ),
    ),
    'google_scholar_link' => array(
      'file' => '/includes/biblio.theme.inc',
      'variables' => array(
        'node',
      ),
    ),
    'biblio_contributors' => array(
      'file' => '/includes/biblio.theme.inc',
      'render element' => 'form',
    ),
    'biblio_admin_overview' => array(
      'file' => '/includes/biblio.theme.inc',
      'variables' => array(
        'name' => NULL,
        'type' => NULL,
        'description' => NULL,
      ),
    ),
  );
}

/**
 * Page callback for biblio/autocomplete in hook_menu().
 *
 * @param string $field
 * @param string $string
 */
function biblio_autocomplete($field, $string = '') {
  $matches = array();
  switch ($field) {
    case 'contributor':
    case 'a':

      // @todo: get this working on first OR last name
      $result = db_query_range("SELECT biblio_contributor_name_value FROM {field_data_biblio_contributor_name} WHERE LOWER(biblio_contributor_name_value) LIKE LOWER(:name) OR LOWER(biblio_contributor_name_value) LIKE LOWER(:firstname) ORDER BY biblio_contributor_name_value ASC ", 0, 10, array(
        ':name' => $string . '%',
        ':firstname' => $string . '%',
      ));
      foreach ($result as $data) {
        $matches[$data->biblio_contributor_name_value] = check_plain($data->biblio_contributor_name_value);
      }
      break;
    case 'biblio_keywords':
    case 'k':
      $sep = check_plain(variable_get('biblio_keyword_sep', ','));
      $sep_pos = strrpos($string, $sep);

      //find the last separator
      $start = trim(drupal_substr($string, 0, $sep_pos));

      // first part of the string upto the last separator
      $end_sep = $sep_pos ? $sep_pos + 1 : $sep_pos;
      $end = trim(drupal_substr($string, $end_sep)) . '%';

      // part of the string after the last separator
      $result = db_query_range("SELECT * FROM {biblio_keyword_data} WHERE LOWER(word) LIKE LOWER(:end) ORDER BY word ASC ", 0, 10, array(
        ':end' => $end,
      ));
      foreach ($result as $data) {

        // now glue the word found onto the end of the original string...
        $keywords = $sep_pos ? $start . ', ' . check_plain($data->word) : check_plain($data->word);
        $matches[$keywords] = $keywords;
      }
      break;
    default:
      $field = drupal_strtolower($field);
      $string = '%' . drupal_strtolower($string) . '%';
      $query = db_select('biblio', 'b')
        ->fields('b', array(
        $field,
      ))
        ->condition($field, $string, 'LIKE')
        ->orderBy($field, 'ASC')
        ->range(0, 10);
      $result = $query
        ->execute();
      foreach ($result as $data) {
        $matches[$data->{$field}] = check_plain($data->{$field});
      }
  }
  print drupal_json_encode($matches);
  exit;
}

/**
 * Page callback for base/help in hook_menu().
 *
 * @return string The markup to display on the help page
 */
function biblio_help_page() {
  $base = variable_get('biblio_base', 'biblio');
  $text = "<h3>" . t('General:') . "</h3>";
  $text .= "<p>" . t('By default, the !url page will list all of the entries in the database sorted by Year in descending order. If you wish to sort by "Title" or "Type",  you may do so by clicking on the appropriate links at the top of the page.  To reverse the sort order, simply click the link a second time.', array(
    '!url' => l('', $base),
  )) . "</p>";
  $text .= "<h3>" . t('Filtering Search Results:') . "</h3>";
  $text .= "<p>" . t('If you wish to filter the results, click on the "Filter" tab at the top of the page.  To add a filter, click the radio button to the left of the filter type you wish to apply, then select the filter criteria from the drop down list on the right, then click the filter button.') . "</p>";
  $text .= "<p>" . t('It is possible to create complex filters by returning to the <i>Filter</i> tab and adding additional filters.  Simply follow the steps outlined above and press the "Refine" button.') . "</p>";
  $text .= "<p>" . t('All filters can be removed by clicking the <i>Clear All Filters</i> link at the top of the result page, or on the <i>Filter</i> tab they can be removed one at a time using the <i>Undo</i> button, or you can remove them all using the <i>Clear All</i> button.') . "</p>";
  $text .= "<p>" . t('You may also construct URLs which filter.  For example, /biblio/year/2005 will show all of the entries for 2005.  /biblio/year/2005/author/smith will show all of entries from 2005 for smith.') . "</p>";
  $text .= "<h3>" . t('Exporting Search Results:') . "</h3>";
  $text .= "<p>" . t('Assuming this option has been enabled by the administrator, you can export search results directly into EndNote.  The link at the top of the result page will export all of the search results, and the links on individual entries will export the information related to that single entry.') . "</p>";
  $text .= "<p>" . t('The information is exported in EndNote "Tagged" format similar to this...') . "<pre>" . t('
                  %0  Book
                  %A  John Smith
                  %D  1959
                  %T  The Works of John Smith
                  ...') . '</pre></p>';
  $text .= "<p>" . t('Clicking on one of the export links should cause your browser to ask you whether you want to Open, or Save To Disk, the file endnote.enw.  If you choose to open it, Endnote should start and ask you which library you would like store the results in.  Alternatively, you can save the file to disk and manually import it into EndNote.') . "</p>";
  return $text;
}

/**
 * Implements hook_help().
 *
 * Throughout Drupal, hook_help() is used to display help text at the top of
 * pages. Some other parts of Drupal pages get explanatory text from these hooks
 * as well. We use it here to provide a description of the module on the
 * module administration page.
 */
function biblio_help($path, $arg) {
  switch ($path) {
    case 'admin/help#biblio':
      return biblio_help_page();
    case 'admin/structure/biblio':
      return t("Here, you can manage the field and display settings for each of Biblio's publication types, similar to admin/structure/types. If a <small>(<span class=\"admin-disabled\">Missing Field Instances</span>)</small> label appears next to a publication type, don't panic. It simply means that the default Biblio field instances have not been created yet for that publication type. You can create the default field instances by navigating to the " . l('Biblio Add page', 'admin/content/biblio/add') . " and selecting the publication type for which to create field instances. (note that it may take a while for field instance creation to execute.)");
    case 'admin/modules#description':

      // This description is shown in the listing at admin/modules.
      return t('Manages a list of scholarly papers on your site');
    case 'node/add#biblio':

      // This description shows up when users click "create content."
      return t('This allows you to add a bibliographic entry to the database');
    case 'admin/content/biblio/add':
      return t('Create a new Biblio from scratch, or input a record from a file if you have a Biblio sub-module enabled. <i>Please note</i> that the first time you select a particular publication type, it will take a few seconds to build the field instances. (It won\'t happen more than once per publication type)');
  }
}

/**
 * Implements hook_node_access.
 */
function biblio_node_access($node, $op, $account) {
  if (is_string($node)) {
    return NODE_ACCESS_IGNORE;
  }
  if ($node->type != 'biblio') {

    // we only care about biblio nodes
    return NODE_ACCESS_IGNORE;
  }
  switch ($op) {
    case 'view':
      if (variable_get('biblio_view_only_own', 0) && $account->uid != $node->uid || !user_access('access biblio content')) {
        return NODE_ACCESS_DENY;
      }
      break;
    case 'update':
    case 'delete':
      if (user_access('edit by all biblio authors') && isset($node->biblio_contributors) && is_array($node->biblio_contributors)) {
        foreach ($node->biblio_contributors as $key => $author) {
          if (isset($author['drupal_uid']) && $author['drupal_uid'] == $account->uid || isset($account->data['biblio_contributor_id']) && $author['cid'] == $account->data['biblio_contributor_id']) {
            return NODE_ACCESS_ALLOW;
          }
        }
      }
      break;
    default:
  }
  return NODE_ACCESS_IGNORE;
}

/**
 * Implements hook_access().
 */
function biblio_access($op, $node = '') {
  global $user;
  switch ($op) {
    case 'admin':
      return user_access('administer biblio');
    case 'import':
      return user_access('import from file');
    case 'export':
      return user_access('show export links');
    case 'edit_author':
      if (user_access('administer biblio') || user_access('edit biblio authors')) {
        return NODE_ACCESS_ALLOW;
      }
      break;
    case 'download':
      if (user_access('show download links') || user_access('show own download links') && $user->uid == $node->uid) {
        return NODE_ACCESS_ALLOW;
      }
      break;
    case 'rss':
      return variable_get('biblio_rss', 0);
    default:
  }
  return NODE_ACCESS_IGNORE;
}

/**
 * Implements hook_permission().
 *
 * Since we are limiting the ability to create new nodes to certain users,
 * we need to define what those permissions are here. We also define a permission
 * to allow users to edit the nodes they created.
 */
function biblio_permission() {
  return array(
    'administer biblio' => array(
      'title' => t('Administer Biblio'),
      'description' => t('Allows full control (create, update, delete) of all Biblio nodes'),
    ),
    'access biblio content' => array(
      'title' => t('Access Biblio content'),
      'description' => t('Allows the user to view Biblio nodes'),
    ),
    'create biblio' => array(
      'title' => t('Create Biblio'),
      'description' => t('Allows the user to create new Biblios'),
    ),
    'edit all biblio entries' => array(
      'title' => t('Edit all Biblio entries'),
      'description' => t('Allows the user to edit ALL biblio entries regardless of who "owns" them, otherwise they are restricted to on'),
    ),
    'delete biblios' => array(
      'title' => t('Delete Biblios'),
      'description' => t('Allows the user to delete any biblio entities'),
    ),
    'edit by all biblio authors' => array(
      'title' => t('Edit by all Biblio authors'),
      'description' => t('Allows any/all of the authors associated with a biblio entry to edit the biblio entry. This requires the Drupal UserID be mapped to a Biblio author ID'),
    ),
    'edit biblio authors' => array(
      'title' => t('Edit Biblio authors'),
      'description' => t('Allows the user to edit author information'),
    ),
    'import from file' => array(
      'title' => t('Import from file'),
      'description' => t('Allows the user to import bibliographic data from files such as BibTex, RIS, EndNote'),
    ),
    'show export links' => array(
      'title' => t('Show export links'),
      'description' => t('Allows users to see links which allow export of bibliographic data for an individual entry or the entire result set'),
    ),
    'show download links' => array(
      'title' => t('Show download links'),
      'description' => t('Allows users to see links to any attachements associated with the Biblio entry'),
    ),
    'show own download links' => array(
      'title' => t('Show own download links'),
      'description' => t('Allows user to only see download links on entries for which they are the owner.'),
    ),
    'show filter tab' => array(
      'title' => t('Show filter tab'),
      'description' => t('This determines if the "Filter" tab on the Biblio list page will be shown to the user'),
    ),
    'show sort links' => array(
      'title' => t('Show sort links'),
      'description' => t('This determines if the "Sort" links on the Biblio list page will be shown to the user'),
    ),
    'view full text' => array(
      'title' => t('Show full text'),
      'description' => t('This determines if the user will be able to access the "Full Text" of the article if it is available'),
    ),
    'manage biblio structure' => array(
      'title' => t('Manage Biblio structure'),
      'description' => t('This determines if the user will be able to modify field and display settings for biblio and contributor entities (admin/structure/biblio)'),
    ),
    'manage biblio content' => array(
      'title' => t('Manage Biblio content'),
      'description' => t('This determines if the user will be able to access the managment interface for biblios and contributors (admin/content/biblio)'),
    ),
  );
}

/**
 * Implements hook_user().
 */
function biblio_form_user_profile_form_alter(&$form, &$form_state, $form_id) {
  $account = $form['#user'];
  include_once drupal_get_path('module', 'biblio') . '/includes/biblio.admin.inc';
  $show_form = variable_get('biblio_show_user_profile_form', '1') || variable_get('biblio_show_crossref_profile_form', '1') || variable_get('biblio_show_openurl_profile_form', '1');
  $admin_show_form = $account->uid == 1 || user_access('administer users') && user_access('administer biblio') ? TRUE : FALSE;
  if ($admin_show_form || $show_form) {
    $form['biblio_fieldset'] = array(
      '#type' => 'fieldset',
      '#title' => t('Biblio settings'),
      '#weight' => 5,
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
    );
    if ($admin_show_form || variable_get('biblio_show_user_profile_form', '1')) {
      $form['biblio_fieldset'] += _biblio_get_user_profile_form($account);
    }
    if ($admin_show_form || variable_get('biblio_show_openurl_profile_form', '1')) {
      $form['biblio_fieldset'] += _biblio_get_user_openurl_form($account);
    }
    if ($admin_show_form || variable_get('biblio_show_crossref_profile_form', '1')) {
      $form['biblio_fieldset'] += _biblio_get_user_doi_form($account);
    }
  }
}

/**
 * Implements hook_forms().
 */
function biblio_forms() {
  $forms['biblio_admin_author_types_form_new'] = array(
    'callback' => 'biblio_admin_author_types_form',
  );
  $forms['biblio_admin_author_types_form_edit'] = array(
    'callback' => 'biblio_admin_author_types_form',
  );
  return $forms;
}

/**
 * Implements hook_menu().
 */
function biblio_menu() {
  global $user;
  $items = array();
  $base = variable_get('biblio_base', 'biblio');
  $base_title = check_plain(variable_get('biblio_base_title', 'Biblio'));

  // Main Biblio Content

  //----------------------------------------------------------------------------
  $items[$base] = array(
    'title' => $base_title,
    'page callback' => 'biblio_page',
    'access callback' => 'user_access',
    'access arguments' => array(
      'access biblio content',
    ),
    'file' => '/includes/biblio.pages.inc',
  );
  $items[$base . "/%biblio"] = array(
    'title callback' => 'biblio_page_title',
    'title arguments' => array(
      1,
    ),
    'page callback' => 'biblio_page_view',
    'page arguments' => array(
      1,
    ),
    'access arguments' => array(
      'access biblio content',
    ),
    'type' => MENU_CALLBACK,
  );
  $items[$base . "/%biblio/view"] = array(
    'title' => 'View',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
    'access arguments' => array(
      'access biblio content',
    ),
  );
  $items[$base . "/%biblio/edit"] = array(
    'title' => 'Edit',
    'page callback' => 'biblio_page_edit',
    'page arguments' => array(
      1,
    ),
    'access arguments' => array(
      'edit all biblio entries',
    ),
    'weight' => 0,
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'file' => 'includes/biblio.admin.inc',
  );
  $items[$base . "/%biblio/delete"] = array(
    'title' => 'Delete',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'biblio_delete_confirm',
      1,
    ),
    'access arguments' => array(
      'edit all biblio entries',
    ),
    'weight' => 1,
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'file' => 'includes/biblio.admin.inc',
  );
  $items[$base . "/contributor/%biblio_contributor"] = array(
    'title callback' => 'entity_label',
    'title arguments' => array(
      'biblio_contributor',
      2,
    ),
    //callback
    'page callback' => 'biblio_contributor_page_view',
    //callback
    'page arguments' => array(
      2,
    ),
    'access arguments' => array(
      'access biblio content',
    ),
  );
  $items[$base . "/contributor/%biblio_contributor/view"] = array(
    'title' => 'View',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
    'access arguments' => array(
      'access biblio content',
    ),
  );
  $items[$base . "/contributor/%biblio_contributor/edit"] = array(
    'title' => 'Edit',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'biblio_contributor_form',
      2,
    ),
    'access arguments' => array(
      'edit all biblio entries',
    ),
    'file' => 'includes/biblio.admin.inc',
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  );
  $items[$base . "/contributor/%biblio_contributor/delete"] = array(
    'title' => 'Delete Contributor',
    'title callback' => 'biblio_contributor_label',
    'title arguments' => array(
      2,
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'biblio_contributor_delete_confirm',
      2,
    ),
    'access arguments' => array(
      'edit all biblio entries',
    ),
  );

  // Importing & Exporting

  //----------------------------------------------------------------------------
  $items[$base . "/import"] = array(
    'title' => 'Import',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'biblio_import_form',
    ),
    'file' => '/includes/biblio.import.export.inc',
    'access callback' => 'user_access',
    'access arguments' => array(
      'import from file',
    ),
    'weight' => 10,
  );
  $items[$base . "/export"] = array(
    'title' => '',
    'page callback' => 'biblio_export',
    'access callback' => 'user_access',
    'access arguments' => array(
      'show export links',
    ),
    'file' => '/includes/biblio.import.export.inc',
    'type' => MENU_CALLBACK,
  );
  $items[$base . "/pot"] = array(
    'title' => '',
    'page callback' => 'biblio_dump_db_data_for_pot',
    'access callback' => 'user_access',
    'access arguments' => array(
      'export pot data',
    ),
    'type' => MENU_CALLBACK,
  );

  // Biblio Content Management

  //----------------------------------------------------------------------------
  $items['admin/content/biblio'] = array(
    'title' => 'Biblio',
    'description' => 'List and edit Biblio entries.
      Use Biblio for scholarly content, such as journal papers and books.',
    'page callback' => 'biblio_admin_view',
    'page arguments' => array(
      'biblio_admin',
      'page',
    ),
    'access arguments' => array(
      'manage biblio content',
    ),
    'weight' => 5,
    'type' => MENU_LOCAL_TASK,
  );
  $items["admin/content/biblio/biblio-list"] = array(
    'title' => 'Biblios',
    'description' => 'View, Edit, and Delete Biblios',
    'access arguments' => array(
      'manage biblio content',
    ),
    'weight' => 0,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/content/biblio/add'] = array(
    'title' => 'Add Biblio',
    'access arguments' => array(
      'create biblio',
    ),
    'page callback' => 'biblio_add',
    'type' => MENU_LOCAL_ACTION,
  );
  $items['admin/content/biblio/contributor'] = array(
    'title' => 'Contributors',
    'page callback' => 'biblio_admin_view',
    'page arguments' => array(
      'biblio_admin_contributors',
      'page',
    ),
    'access arguments' => array(
      'manage biblio content',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 1,
  );
  $items['admin/content/biblio/contributor/add'] = array(
    'title' => 'Add Contributor',
    'page callback' => 'biblio_contributor_add',
    'access arguments' => array(
      'create biblio',
    ),
    'file' => 'includes/biblio.admin.inc',
  );

  // Biblio Structure Management
  // ---------------------------------------------------------------------------
  $items["admin/structure/biblio"] = array(
    'title' => 'Biblio',
    'description' => 'Manage Fields and Dislplay for Biblios and Contributors',
    'access arguments' => array(
      'administer biblio',
    ),
    'page callback' => 'biblio_overview_types',
    'page arguments' => array(
      'biblio',
    ),
    'file' => 'includes/biblio.admin.inc',
    'access arguments' => array(
      'manage biblio structure',
    ),
  );
  $items["admin/structure/biblio/publication-types"] = array(
    'title' => 'Publication Types',
    'description' => 'Manage Biblio Publication Types',
    'access arguments' => array(
      'manage biblio structure',
    ),
    'weight' => 0,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  foreach (biblio_types('biblio') as $type) {
    $bundle = biblio_bundle_name($type->entity);
    $type_url_str = str_replace('_', '-', $type->{$bundle});
    $entity_url_str = str_replace('_', '-', $type->entity);
    $url = 'admin/structure/biblio/publication-types/' . $type_url_str;
    $items[$url] = array(
      'title' => 'Edit ' . $type->name,
      'title callback' => 'check_plain',
      'page callback' => 'biblio_field_settings',
      'file' => '/includes/biblio.admin.inc',
      'page arguments' => array(
        4,
      ),
      'access arguments' => array(
        'manage biblio structure',
      ),
    );
  }
  $items["admin/structure/biblio/contributors"] = array(
    'title' => 'Contributors',
    'description' => 'Manage Biblio Contributor Fields/Display',
    'page callback' => 'biblio_overview_types',
    'page arguments' => array(
      'biblio_contributor',
    ),
    'file' => '/includes/biblio.admin.inc',
    'access arguments' => array(
      'manage biblio structure',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 1,
  );
  foreach (biblio_types('biblio_contributor') as $type) {
    $bundle = biblio_bundle_name($type->entity);
    $type_url_str = str_replace('_', '-', $type->{$bundle});
    $entity_url_str = str_replace('_', '-', $type->entity);
    $url = 'admin/structure/biblio/contributors/' . $type_url_str;
    $items[$url] = array(
      'title' => 'Edit ' . $type->name,
      'title callback' => 'check_plain',
      'page callback' => 'biblio_field_settings',
      'file' => '/includes/biblio.admin.inc',
      'page arguments' => array(
        4,
      ),
      'access arguments' => array(
        'manage biblio structure',
      ),
    );
  }

  // Biblio Settings

  //----------------------------------------------------------------------------
  $items['admin/config/content/biblio'] = array(
    'title' => 'Biblio settings',
    'description' => 'Configure default behavior of the Biblio module.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'biblio_admin_settings',
    ),
    'access arguments' => array(
      'administer biblio',
    ),
    'file' => '/includes/biblio.admin.inc',
  );
  $items['admin/config/content/biblio/preferences'] = array(
    'title' => 'Preferences',
    'description' => 'Configure default behavior of the Biblio module.',
    'access arguments' => array(
      'administer biblio',
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/config/content/biblio/iomap'] = array(
    'title' => 'Import/Export Mapping',
    'page callback' => 'biblio_admin_io_mapper_page',
    'access arguments' => array(
      'administer biblio',
    ),
    'file' => '/includes/biblio.admin.inc',
    'type' => MENU_LOCAL_TASK,
    'weight' => -1,
  );
  $items['admin/config/content/biblio/iomap/formats'] = array(
    'title' => 'Import/Export Mapping',
    'page callback' => 'biblio_admin_io_mapper_page',
    'access arguments' => array(
      'administer biblio',
    ),
    'file' => '/includes/biblio.admin.inc',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -100,
  );
  $formats = module_invoke_all('biblio_mapper_options');
  foreach ($formats as $key => $format) {
    $items['admin/config/content/biblio/iomap/edit/' . $key] = array(
      'title' => $format['title'],
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'biblio_admin_io_mapper_form',
        6,
      ),
      'access arguments' => array(
        'administer biblio',
      ),
      'file' => '/includes/biblio.admin.inc',
      'tab_parent' => 'admin/config/content/biblio/iomap',
      'type' => MENU_LOCAL_TASK,
      'weight' => -1,
    );
  }
  $items['admin/config/content/biblio/iomap/%/%/add'] = array(
    'title' => '',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'biblio_admin_io_mapper_add_form',
      5,
      6,
    ),
    'access arguments' => array(
      'administer biblio',
    ),
    'tab_parent' => 'admin/config/content/biblio/iomap',
    'file' => '/includes/biblio.admin.inc',
    'type' => MENU_CALLBACK,
    'weight' => -1,
  );

  // Other Biblio Features

  //----------------------------------------------------------------------------
  $items['biblio/autocomplete'] = array(
    'title' => 'Autocomplete ',
    'page callback' => 'biblio_autocomplete',
    'access callback' => 'user_access',
    'access arguments' => array(
      'access biblio content',
    ),
    'type' => MENU_CALLBACK,
  );
  $items["user/%user/" . $base] = array(
    'title' => 'Publications',
    'page callback' => 'biblio_profile_page',
    'page arguments' => array(
      1,
      'profile',
      'no_filters',
    ),
    'access callback' => '_biblio_profile_access',
    'access arguments' => array(
      1,
      'profile',
    ),
    'file' => '/includes/biblio.pages.inc',
    'type' => MENU_LOCAL_TASK,
  );
  $items[$base . "/user/%"] = array(
    'title' => 'My publications',
    'page callback' => 'biblio_profile_page',
    'page arguments' => array(
      2,
    ),
    'access callback' => '_biblio_profile_access',
    'access arguments' => array(
      2,
      'menu',
    ),
    'parent' => '',
    'file' => '/includes/biblio.pages.inc',
  );
  $items[$base . "/help"] = array(
    'title' => 'Help',
    'page callback' => 'biblio_help_page',
    'access callback' => 'user_access',
    'access arguments' => array(
      'access biblio content',
    ),
    'type' => MENU_CALLBACK,
  );
  $items[$base . "/citekey"] = array(
    'title' => '',
    'page callback' => 'biblio_citekey_view',
    'access arguments' => array(
      'access biblio content',
    ),
    'file' => '/includes/biblio.pages.inc',
    'type' => MENU_CALLBACK,
  );
  $items[$base . "/recent/rss.xml"] = array(
    'title' => 'RSS feed',
    'page callback' => 'biblio_recent_feed',
    'access callback' => 'biblio_access',
    'access arguments' => array(
      'rss',
    ),
    'type' => MENU_CALLBACK,
  );
  biblio_menu_links();
  return $items;
}

/**
 * Modify and add links in various menus
 */
function biblio_menu_links() {

  // Get all biblio-generated links
  $result = db_select('menu_links', 'm')
    ->fields('m', array(
    'menu_name',
    'mlid',
    'plid',
    'link_path',
  ))
    ->condition('link_path', '%biblio%', 'like')
    ->execute();
  $add_link_exists = FALSE;

  // Menu link ID as specified in menu_links
  $biblio_root_mlid = 0;
  while ($row = $result
    ->fetchObject()) {
    switch ($row->link_path) {

      // if the menu path is for example.com/biblio, and it's in the navigation
      // menu, set the root (parent) link id
      case 'biblio':
        if ($row->menu_name == 'navigation') {
          $biblio_root_mlid = $row->mlid;
        }
        break;
      case 'admin/content/biblio/add':

        // Check if the "Add a new Biblio" link exists, to avoid duplicates
        if ($row->plid == $biblio_root_mlid && $row->menu_name == 'navigation') {
          $add_link_exists = TRUE;
        }
        break;
    }
  }
  if (!$add_link_exists) {

    // Create a link on the navigation menu to add a biblio
    $add_link = array(
      'link_path' => drupal_get_normal_path('admin/content/biblio/add'),
      'menu_name' => 'navigation',
      'plid' => $biblio_root_mlid,
      'link_title' => t('Add a new Biblio'),
    );
    menu_link_save($add_link);
  }
}

/**
 * Page callback for base/filter/clear in hook_menu().
 */
function biblio_filter_clear() {
  $options = array();
  $_SESSION['biblio_filter'] = array();
  $base = variable_get('biblio_base', 'biblio');
  if (isset($_GET['sort'])) {
    $options['sort'] = $_GET['sort'];
  }
  if (isset($_GET['order'])) {
    $options['order'] = $_GET['order'];
  }
  drupal_goto($base, $options);
}

/**
 * Removes brace from a string
 *
 * @param string $title_string
 * @return string
 */
function biblio_remove_brace($title_string) {

  //$title_string = utf8_encode($title_string);
  $matchpattern = '/\\{\\$(?:(?!\\$\\}).)*\\$\\}|(\\{[^}]*\\})/';
  $output = preg_replace_callback($matchpattern, 'biblio_remove_brace_callback', $title_string);
  return $output;
}

/**
 * Callback from preg_replace_callback
 *
 * @param array $match
 * @return array
 */
function biblio_remove_brace_callback($match) {
  if (isset($match[1])) {
    $braceless = str_replace('{', '', $match[1]);
    $braceless = str_replace('}', '', $braceless);
    return $braceless;
  }
  return $match[0];
}

/**
 * Implements hook_node_revision_delete().
 */
function biblio_node_revision_delete($node) {
  if ($node->type == 'biblio') {
    db_delete('biblio')
      ->condition('vid', $node->vid)
      ->execute();
    db_delete('biblio_contributor')
      ->condition(db_and()
      ->condition('nid', $node->nid)
      ->condition('vid', $node->vid))
      ->execute();
    db_delete('biblio_keyword')
      ->condition(db_and()
      ->condition('nid', $node->nid)
      ->condition('vid', $node->vid))
      ->execute();
  }
}

/**
 * Implements hook_node_insert().
 */
function biblio_node_insert($node) {
  if ($node->type == 'biblio') {
    if (variable_get('biblio_index', 0)) {
      _node_index_node($node);
      search_update_totals();
    }
  }
}

/**
 * Implements hook_node_update().
 */
function biblio_node_update($node) {
  if ($node->type == 'biblio') {
    if (variable_get('biblio_index', 0)) {

      // _node_index_node performs a node_load without resetting the node_load cache,
      // so it would index the old version. We reset the cache here.
      // Don't assign node_load to $node because node_load resets e.g. the menus mlid etc.
      $mynode = node_load($node->nid, NULL, TRUE);
      _node_index_node($mynode);
      search_update_totals();
    }
  }
}

/**
 * Implements hook_node_view
 */
function biblio_node_view($node, $view_mode) {
  if ($node->type == 'biblio') {
    switch ($view_mode) {
      case 'full':
        if (variable_get('biblio_hide_bibtex_braces', 0) && !empty($a4)) {
          drupal_set_title(filter_xss($node->title, biblio_get_allowed_tags()));
        }

      //fall through
      case 'teaser':
        $show_link = variable_get('biblio_lookup_links', array(
          'google' => TRUE,
        ));
        if (!empty($show_link['google'])) {
          $node->content['links']['biblio_google_scholar'] = array(
            '#links' => array(
              theme('google_scholar_link', array(
                'node' => $node,
              )),
            ),
            '#attributes' => array(
              'class' => array(
                'links',
                'inline',
              ),
            ),
          );
        }
    }
  }
}

/**
 * Doesn't seem to do anything
 * @todo remove if possible
 *
 * @global object $user
 * @param QueryAlterableInterface $query
 * @return type
 */
function biblio_query_node_access_alter(QueryAlterableInterface $query) {
  global $user;
  if (user_access('access biblio content', $user)) {
    return;
  }
  $tables = $query
    ->getTables();
  foreach ($tables as $alias => $table_info) {
    if (!$table_info instanceof SelectQueryInterface) {
      if ($table_info['table'] == 'node') {
        $query
          ->condition($alias . '.type', 'biblio', '<>');
        break;
      }
    }
  }
}

/**
 * Implements hook_user_presave
 */
function biblio_user_presave(&$edit, $account, $catagory) {
  $keys = array_keys($edit);
  foreach ($keys as $key) {
    if (strpos($key, 'biblio_') !== FALSE && isset($edit[$key])) {
      if (isset($account->data['biblio_id_change_count']) && $account->data['biblio_id_change_count'] > 2 && $key == 'biblio_contributor_id' && $edit[$key] != 0) {
        $edit[$key] = 0;
      }
      $edit['data'][$key] = $edit[$key];
      if ($key == 'biblio_contributor_id') {
        if ($edit[$key] != 0 && $edit[$key] != $account->data[$key]) {
          $edit['biblio_id_change_count']++;
        }
        db_update('biblio_contributor_data')
          ->condition('drupal_uid', $account->uid)
          ->fields(array(
          'drupal_uid' => 0,
        ))
          ->execute();
        db_update('biblio_contributor_data')
          ->condition('cid', $edit['biblio_contributor_id'])
          ->fields(array(
          'drupal_uid' => $account->uid,
        ))
          ->execute();
      }
    }
  }
}

/**
 * Displays the Add/Edit form for a biblio entity
 *
 * @global object $user
 * @param array $form
 * @param array $form_state
 * @param object $biblio
 * @return array
 */
function biblio_form($form, &$form_state, $biblio = NULL) {
  global $user;
  $fields = array();
  $form['#id'] = 'biblio-form';

  // Check to see if we've imported a biblio object using a sub-module
  if (isset($form_state['biblio_imported'])) {
    $biblio = $form_state['biblio_imported'];
  }
  $form['biblio_tabs'] = $tabs = array();
  $publication_type = !empty($form_state['biblio_type']) ? $form_state['biblio_type'] : (isset($biblio->publication_type) ? $biblio->publication_type : '');
  if (isset($_COOKIE['has_js']) && !$_COOKIE['has_js']) {
    unset($form['biblio_next']['#attributes']);
  }
  $step_two = FALSE;
  if (!empty($publication_type) && $publication_type != 'select_type' || isset($biblio)) {
    $step_two = TRUE;
  }

  /* publication type */
  $param['options'] = array(
    "enctype" => "multipart/form-data",
  );
  foreach (biblio_types() as $machine_name => $info) {
    $type_options[$machine_name] = $info->name;
  }
  asort($type_options);
  $options['select_type'] = t('Select Type...');
  $options += $type_options;
  if (!$step_two) {
    $form['biblio_type'] = array(
      '#type' => 'select',
      '#title' => t('Publication Type'),
      '#default_value' => $publication_type,
      '#options' => $options,
      '#description' => NULL,
      '#weight' => -15,
      '#attributes' => array(
        'onchange' => 'document.getElementById(\'edit-biblio-next\').click();
        document.getElementById(\'edit-biblio-next\').disabled="true"',
      ),
      '#multiple' => FALSE,
      '#required' => TRUE,
    );
    $form['biblio_next'] = array(
      '#type' => 'submit',
      '#value' => $step_two ? t('Change Publication Type') : t('Next'),
      '#limit_validation_errors' => array(),
      '#weight' => -10,
      '#submit' => array(),
    );
  }
  else {

    // Step two
    module_load_include('inc', 'biblio', 'includes/biblio.fields');

    // Add field instances only if they dont already exist.
    if (biblio_field_instances_missing($publication_type)) {
      biblio_add_field_instances('biblio', $publication_type);
    }

    // Create a new, empty biblio object
    if (!isset($biblio)) {
      $biblio = biblio_create($publication_type);
    }
    $wrapper = biblio_wrapper($biblio);

    // Set the title. PASS_TRHOUGH allows us to include HTML in the string.
    drupal_set_title(t('Create @name', array(
      '@name' => $options[$publication_type],
    )), PASS_THROUGH);
    $instance_info = field_info_instances('biblio', $biblio->publication_type);

    // $fielded_form becomes a placeholder for form values that are automatically
    // returned to us from the Field Attach API
    $fielded_form = $form;
    field_attach_form('biblio', $biblio, $fielded_form, $form_state);
    $tabs = array(
      '#type' => 'vertical_tabs',
      '#weight' => 3,
    );
    $tabs += biblio_form_vtabs();
    $tabs['contributors'] += biblio_contributor_widget($biblio, $form_state);

    // Add imported keywords as the default value for the biblio_keywords field
    if (isset($form_state['biblio_imported']) && isset($fielded_form['biblio_keywords'])) {
      $langcode = $fielded_form['biblio_keywords']['#language'];
      $fielded_form['biblio_keywords'][$langcode]['#default_value'] = implode(', ', $biblio->biblio_keywords);
    }

    // Add the values we created above to the real form
    foreach ($fielded_form as $field => $data) {

      // Set a vtab value, if it exists (not all array keys are valid fields)
      $vtab = isset($instance_info[$field]['settings']['vtab']) ? $instance_info[$field]['settings']['vtab'] : FALSE;

      // We don't want extra theme elements from $fielded_form.
      // Just field instances with a set vtab.
      if ($vtab !== FALSE) {
        if ($vtab == 'none' || $vtab == 'None') {

          // Fields specified to be outside of the vtabs
          $form[$field] = $data;
        }
        else {

          // Add all other fields to the vtabs
          $tabs[$vtab][$field] = $data;
        }
      }
      else {
        if (biblio_is_field_instance($field, 'biblio', $biblio->publication_type)) {
          $form[$field] = $data;
        }
      }
    }
    $form_state['biblio_fields'] = $fields;
    foreach (element_children($tabs) as $key) {
      $tab_children = element_children($tabs[$key]);
      if (empty($tab_children) && $key != 'biblio_full_text') {
        unset($tabs[$key]);
      }
    }

    // Add the buttons.
    // Save button is always available
    $form['buttons'] = array();
    $form['buttons']['#weight'] = 100;
    $form['buttons']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Save'),
      '#weight' => 5,
      '#submit' => array(
        'biblio_form_submit',
      ),
    );

    // Delete button is available if we're editing an existing biblio.
    if (!empty($biblio->bid)) {
      $form['buttons']['delete'] = array(
        '#access' => user_access('delete biblios'),
        '#type' => 'submit',
        '#value' => t('Delete'),
        '#weight' => 15,
        '#submit' => array(
          'biblio_form_delete_submit',
        ),
      );
    }
    $form_state['biblio'] = $biblio;
  }

  // $form['format'] = filter_form($node->format, 20);

  //$biblio_form['#tree']  = TRUE;
  $form['#validate'] = array(
    'biblio_form_validate',
  );
  $form['#cache'] = TRUE;
  $form['biblio_tabs'] += $tabs;
  biblio_hide_form_fields($form, $form_state);
  return $form;
}

/**
 * Creates the vertical tab form structure as used in the add/edit form
 *
 * @return array
 */
function biblio_form_vtabs() {
  $vtabs = biblio_form_vtab_info();
  foreach ($vtabs as $tab) {
    $form[$tab['tab_id']] = array(
      '#type' => 'fieldset',
      '#group' => 'biblio_tabs',
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
      '#title' => t($tab['title']),
      '#description' => '',
      '#weight' => $tab['weight'],
    );
  }
  return $form;
}

/**
 * Get info specific to each vertical tab
 *
 * @return array
 */
function biblio_form_vtab_info() {
  return array(
    array(
      'tab_id' => 'contributors',
      'weight' => 9,
      'title' => 'Contributors',
      'description' => 'Enter a single name per line using a format such as "Smith, John K" or "John K Smith" or "J.K. Smith"',
    ),
    array(
      'tab_id' => 'abstract',
      'weight' => 10,
      'title' => 'Abstract',
      'description' => '',
    ),
    array(
      'tab_id' => 'full_text',
      'weight' => 11,
      'title' => 'Full text',
      'description' => '',
    ),
    array(
      'tab_id' => 'publication',
      'weight' => 12,
      'title' => 'Publication',
      'description' => '',
    ),
    array(
      'tab_id' => 'publisher',
      'weight' => 13,
      'title' => 'Publisher',
      'description' => '',
    ),
    array(
      'tab_id' => 'identifiers',
      'weight' => 14,
      'title' => 'Identifiers',
      'description' => '',
    ),
    array(
      'tab_id' => 'locators',
      'weight' => 15,
      'title' => 'Locators',
      'description' => 'URL\'s etc',
    ),
    array(
      'tab_id' => 'keywords',
      'weight' => 16,
      'title' => 'Keywords',
      'description' => '',
    ),
    array(
      'tab_id' => 'notes',
      'weight' => 17,
      'title' => 'Notes',
      'description' => '',
    ),
    array(
      'tab_id' => 'alternate_titles',
      'weight' => 18,
      'title' => 'Alternate Titles',
      'description' => '',
    ),
    array(
      'tab_id' => 'other',
      'weight' => 19,
      'title' => 'Other',
      'description' => '',
    ),
  );
}

/**
 * Get form structure for the multivalued add contributor widget
 *
 * @param object $biblio
 * @param array $form_state
 * @return array
 */
function biblio_contributor_widget($biblio, &$form_state) {
  module_load_include('inc', 'biblio', 'includes/biblio.contributors');
  $categories = biblio_contributor_categories();
  $init_count = variable_get('biblio_init_auth_count', 4);
  $contributor_count = 0;

  // $biblio_wrapper = biblio_wrapper($biblio, 'biblio');
  $contributors = biblio_get_contributors($biblio);
  if (isset($form_state['biblio_imported_contributors'])) {

    // Reset the contributors array. If repopulating form, 4 blank
    // contributors show up from the previous form build.
    foreach ($form_state['biblio_imported_contributors'] as $author_data) {
      $contributors[] = $author_data;
    }
  }

  // Container for just the contributors.
  $wrapper = array();
  $wrapper['biblio_contributors'] = array(
    '#tree' => TRUE,
    '#weight' => 0,
    '#theme' => 'biblio_contributors',
    '#prefix' => '<div id="biblio-contributors-wrapper">',
    '#suffix' => '</div>',
  );
  foreach ($contributors as $cid => $info) {
    $wrapper['biblio_contributors'][] = array();
    end($wrapper['biblio_contributors']);
    $delta = key($wrapper['biblio_contributors']);
    $wrapper['biblio_contributors'][$delta] = biblio_contributor_biblio_form($info, $biblio, $form_state);
    $contributor_count++;
  }
  if (isset($form_state['biblio_contrib_count'])) {
    $form_count = max(max($init_count, $contributor_count), $form_state['biblio_contrib_count']);
  }
  else {
    $form_count = max($init_count, $contributor_count);
    $form_state['biblio_contrib_count'] = $form_count;
  }
  if ($form_count > $contributor_count) {
    $author = array();

    // array to hold the entity objects that will be generated for each contributor name field
    // (only those submitted with values will actually be saved)
    for ($i = 0; $i < $form_count - $contributor_count; $i++) {

      // Put the current contributor form in our main form
      $wrapper['biblio_contributors'][] = biblio_contributor_biblio_form(array(), $biblio, $form_state);
    }
  }
  $wrapper['add_more'] = array(
    '#type' => 'submit',
    '#value' => t('More contributors'),
    '#description' => t("If there aren't enough boxes above, click here to add more."),
    '#weight' => 0.1,
    '#submit' => array(
      'biblio_contributors_add_more',
    ),
    // If no javascript action.
    '#limit_validation_errors' => array(),
    '#ajax' => array(
      'callback' => 'biblio_contributors_add_more_callback',
      'wrapper' => 'biblio-contributors-wrapper',
    ),
  );
  return $wrapper;
}

/**
 * Get the options for category dropdown field in contributor add/edit form
 *
 * @return array
 */
function biblio_get_contributor_options() {
  $options = array(
    1 => t('Primary'),
    2 => t('Secondary'),
    3 => t('Tertiary'),
    4 => t('Subsidiary'),
    5 => t('Corporate/Institutional'),
  );
  return $options;
}

/**
 * Set up the form for ONE contributor in the biblio add/edit form.
 *
 * @param object $contributor
 * @param array $form_state
 * @param integer $delta The iterative value of the current contributor. 0,1,2...etc.
 * @return string
 */
function biblio_contributor_biblio_form($contributor, $biblio, &$form_state) {
  module_load_include('inc', 'biblio', 'includes/biblio.contributors');
  foreach (biblio_contributor_categories($biblio->publication_type) as $category => $info) {
    $category_options[$category] = $info['label'];
  }
  $form = array(
    '#tree' => TRUE,
  );
  $form['name'] = array(
    '#type' => 'textfield',
    '#autocomplete_path' => 'biblio/autocomplete/contributor',
    '#default_value' => isset($contributor['name']) ? $contributor['name'] : '',
  );
  $form['category'] = array(
    '#type' => 'select',
    '#default_value' => isset($contributor['category']) ? $contributor['category'] : '',
    '#options' => $category_options,
    '#multiple' => FALSE,
  );
  $form['cid'] = array(
    '#type' => 'hidden',
    '#default_value' => isset($contributor['cid']) ? $contributor['cid'] : '',
  );
  $form['rank'] = array(
    '#type' => 'textfield',
    '#size' => 6,
    '#default_value' => isset($contributor['rank']) ? $contributor['rank'] : '',
  );

  // Get the language of the name field
  // $lang = $form['biblio_contributor_name']['#language'];
  // Set up autocomplete for the Name field
  // $form['biblio_contributor_name'][$lang][0]['value']['#autocomplete_path']
  // = 'biblio/autocomplete/contributor';
  // Set the form's parents so FAPI can distinguish between the multiple Name fields
  // $form['#parents'] = array(
  //   'biblio_tabs',
  //   1,
  //   'biblio_contributors',
  //   $delta,
  //   'biblio_contributor_name'
  // );
  // // Remove the title above each field
  // $form['biblio_contributor_name'][$lang][0]['value']['#title'] = '';
  // // Reset the language to the language set for biblio_contributor_category field
  // $lang = $form['biblio_contributor_category']['#language'];
  // // Remove the "- None -" option from the select list
  // unset($form['biblio_contributor_category'][$lang]['#options']['_none']);
  // // Remove the title above each field
  // $form['biblio_contributor_category'][$lang]['#title'] = '';
  return $form;
}

/**
 * Callback from the Biblio Contributor Widget's "add more" button
 *
 * @param array $form
 * @param array $form_state
 * @return array
 */
function biblio_contributors_add_more_callback($form, &$form_state) {
  return $form['biblio_tabs']['contributors']['biblio_contributors'];
}

/**
 * Submit action for the "add more" button
 *
 * @param array $form
 * @param array $form_state
 */
function biblio_contributors_add_more($form, &$form_state) {
  $form_state['biblio_contrib_count'] += variable_get('biblio_contrib_fields_delta', 2);
  $form_state['rebuild'] = TRUE;
}
function biblio_hide_form_fields(&$form, &$form_state) {
  module_load_include('inc', 'biblio', 'includes/biblio.contributors');
  $elements_to_hide = array();
  $biblio = isset($form_state['biblio']) ? $form_state['biblio'] : FALSE;
  if ($biblio) {
    $instances = field_info_instances('biblio', $biblio->publication_type);

    // Hide all entity reference widgets, so we can make use of the default biblio
    // contributor widget
    foreach (biblio_contributor_categories() as $category => $info) {
      $vtab = $instances[$info['field']]['settings']['vtab'];
      $elements_to_hide[$vtab][] = $info['field'];
    }
  }

  // Set all containers of form fields we want to hide, to hidden
  foreach ($elements_to_hide as $vtab => $fields) {
    foreach ($fields as $field) {

      // Fields with a biblio vtab set
      if (isset($form['biblio_tabs'][$vtab][$field])) {
        $form['biblio_tabs'][$vtab][$field]['#type'] = 'hidden';
      }
      else {
        if (isset($form[$field])) {
          $form[$field]['#type'] = 'hidden';
        }
      }
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter
 */
function biblio_form_node_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'biblio_node_form') {
    $form['#pre_render'][] = 'biblio_node_form_pre_render';
    if (!isset($form['biblio_tabs']['biblio_full_text']) && isset($form['body'])) {
      unset($form['body']);
    }
    if (empty($form_state['biblio_type']) && empty($form['vid']['#value'])) {
      foreach (element_children($form) as $form_element) {
        if (strstr($form_element, 'biblio_')) {
          continue;
        }
        if (strstr($form_element, 'form_')) {
          continue;
        }
        if (isset($form[$form_element]['#type']) && $form[$form_element]['#type'] != 'value') {
          $form[$form_element]['#access'] = FALSE;
        }
      }
    }
  }
}

/**
 * Callback function for pre-render in the node form
 *
 * @param array $form
 * @return array
 */
function biblio_node_form_pre_render($form) {
  if (isset($form['biblio_tabs']['biblio_full_text']) && isset($form['body'])) {
    $form['biblio_tabs']['biblio_full_text']['body'] = $form['body'];
    unset($form['body']);
  }
  return $form;
}

/**
 * Implements hook_validate().
 *
 * Errors should be signaled with form_set_error().
 */
function biblio_node_form_validate($form, &$form_state) {
  if ($form_state['triggering_element']['#value'] == t('Next') || $form_state['triggering_element']['#value'] == t('Change Publication Type')) {
    $form_state['rebuild'] = TRUE;
    $form_state['biblio_type'] = $form_state['values']['biblio_type'];
    if ($form_state['values']['biblio_type'] == 0) {
      form_set_error('biblio_type', t('Please select a publication type.'));
    }
    return;
  }
  $format = new stdClass();
  foreach (_biblio_get_formatted_fields() as $field) {
    if (isset($form_state['values'][$field]['format'])) {
      $format->format = $form_state['values'][$field]['format'];
      if (!filter_access($format)) {
        form_set_error($field, t('You do not have access to the !format format', array(
          '!format' => $format->format,
        )));
      }
    }
  }
  if (isset($form_state['values']['biblio_keywords'])) {
    require_once drupal_get_path('module', 'biblio') . '/includes/biblio.keywords.inc';
    if (!is_array($form_state['values']['biblio_keywords'])) {
      $form_state['values']['biblio_keywords'] = biblio_explode_keywords($form_state['values']['biblio_keywords']);
    }
    foreach ($form_state['values']['biblio_keywords'] as $keyword) {
      if (strlen($keyword) > 255) {
        form_set_error('biblio_keywords', t('No single keyword can be greater than 255 characters in length, the word: @kw exceeds this length', array(
          '@kw' => $keyword,
        )));
      }
    }
  }
  if (isset($form_state['biblio_fields'])) {
    $vtabs = biblio_node_form_vtab_info();
    foreach ($vtabs as $tab) {
      $tabs[$tab['tab_id']] = $tab['title'];
    }
    foreach ($form_state['biblio_fields'] as $key => $fld) {
      if ($fld['required'] && isset($form_state['values'][$key]) && empty($form_state['values'][$key])) {
        $tab = $tabs[$fld['vtab']];
        form_set_error($key, t('The <b><u>@fld</u></b> field (on the <b><i>@tab</i></b> tab) is required', array(
          '@fld' => $fld['title'],
          '@tab' => $tab,
        )));
      }
    }
  }
}

/**
 * Gets the numeric year values associated with "In Press"/"Submitted
 *
 * @param mixed $year
 * @return int
 */
function _biblio_numeric_year($year) {
  if (!is_numeric($year)) {
    if (drupal_strtoupper($year) == drupal_strtoupper(t("In Press"))) {
      return 9998;
    }
    if (drupal_strtoupper($year) == drupal_strtoupper(t("Submitted"))) {
      return 9999;
    }
    return 0;
  }
  else {
    return $year;
  }
}

/**
 * Gets the string values (in press/submitted) of the years 9999 and 9998
 *
 * @param int $year
 * @return string The associated year string
 */
function _biblio_text_year($year) {
  if ($year == 9998) {
    return check_plain(variable_get('biblio_inpress_year_text', t('In Press')));
  }
  if ($year == 9999) {
    return check_plain(variable_get('biblio_no_year_text', t('Submitted')));
  }
  return $year;
}

/**
 * Gets a list of machine-readable field names from the biblio_fields db table
 *
 * @todo check if needed in 7.x-2.x
 * @return array
 */
function _biblio_get_formatted_fields() {
  $fields =& drupal_static(__FUNCTION__);
  if (!isset($fields)) {
    $query = db_select('biblio_fields', 'bf');
    $result = $query
      ->fields('bf', array(
      'name',
    ))
      ->condition('type', 'text_format')
      ->execute();
    foreach ($result as $field) {
      $fields[] = $field->name;
    }
  }
  return (array) $fields;
}

/**
 * Prepare a biblio for submit to database.
 * Contains code common to insert and update.
 *
 * @param object $biblio
 */
function _biblio_prepare_submit(&$biblio) {
  module_load_include('inc', 'biblio', 'includes/biblio.util');
  $wrapper = biblio_wrapper($biblio);
  if (!isset($biblio->publication_type)) {
    $biblio->publication_type = 'miscellaneous';
  }
  $title = $wrapper->biblio_title
    ->value();
  $biblio->biblio_sort_title = biblio_normalize_title($title);
  if (!isset($biblio->biblio_year)) {
    $wrapper->biblio_year
      ->set(9999);
  }
  $wrapper->biblio_year
    ->set(_biblio_numeric_year($wrapper->biblio_year
    ->value()));
  if (variable_get('biblio_auto_citekey', 1) && empty($biblio->biblio_citekey)) {
    $biblio->biblio_citekey = biblio_citekey_generate($biblio);
  }
  foreach (_biblio_get_formatted_fields() as $field) {
    if (isset($biblio->{$field}) && is_array($biblio->{$field})) {
      $biblio->biblio_formats[$field] = $biblio->{$field}['format'];
      $biblio->{$field} = $biblio->{$field}['value'];
    }
  }
  $wrapper->biblio_coins
    ->set(biblio_coins($biblio));
}

/**
 * Implementation of hook_update().
 *
 * As an existing node is being updated in the database, we need to do our own
 * database updates.
 *
 * @param object $node
 */
function biblio_update($node) {
  module_load_include('inc', 'biblio', 'includes/biblio.util');
  module_load_include('inc', 'biblio', 'includes/biblio.contributors');
  module_load_include('inc', 'biblio', 'includes/biblio.keywords');
  _biblio_prepare_submit($node);
  biblio_update_contributors($node);
  biblio_update_keywords($node);
  $node->biblio_coins = biblio_coins($node);

  // Update the node in the database:
  if (!empty($node->revision)) {
    drupal_write_record('biblio', $node);
  }
  else {
    drupal_write_record('biblio', $node, 'vid');
  }
}

/**
 * Implementation of hook_load().
 *
 * This hook is called
 * every time a node is loaded, and allows us to do some loading of our own.
 *
 * @todo move necessary code from this function to the new biblio_load() function. See http://drupal.org/node/1537326
 *
 */
function biblio_load_old($nodes) {
  module_load_include('inc', 'biblio', 'includes/biblio.util');
  module_load_include('inc', 'biblio', 'includes/biblio.contributors');
  module_load_include('inc', 'biblio', 'includes/biblio.keywords');
  $vids = array();
  foreach ($nodes as $nid => $node) {
    $vids[] = $node->vid;
  }
  $result = db_query('SELECT b.*, bt.name as biblio_type_name
                      FROM {biblio} b
                      LEFT JOIN {biblio_types} bt on b.biblio_type = bt.tid
                      WHERE b.vid IN (:vids)', array(
    ':vids' => $vids,
  ), array(
    'fetch' => PDO::FETCH_ASSOC,
  ));
  $contributors = biblio_load_contributors_multiple($vids);
  $keywords = biblio_load_keywords_multiple($vids);
  foreach ($result as $record) {
    if ((isset($record['biblio_url']) || isset($record['biblio_accession_number'])) && variable_get('biblio_fix_isi_links', 0)) {
      biblio_fix_isi_links($record);
    }
    foreach ($record as $key => $value) {
      $nodes[$record['nid']]->{$key} = $value;
    }
    $nodes[$record['nid']]->biblio_year = _biblio_text_year($record['biblio_year']);
    $nodes[$record['nid']]->biblio_contributors = isset($contributors[$record['vid']]) ? $contributors[$record['vid']] : array();
    $nodes[$record['nid']]->biblio_keywords = isset($keywords[$record['vid']]) ? $keywords[$record['vid']] : array();
    if (empty($record['biblio_coins'])) {
      $nodes[$record['nid']]->biblio_coins = biblio_coins($nodes[$record['nid']]);
    }
    if ($record['biblio_formats'] != NULL) {
      $nodes[$record['nid']]->biblio_formats = unserialize($record['biblio_formats']);
    }
  }
}

/**
 * Generates automated citation keys
 *
 * @param object $biblio
 * @return string The citation key data
 */
function biblio_citekey_generate($biblio) {
  $php = check_plain(variable_get('biblio_citekey_phpcode', ''));
  if (empty($php)) {
    $prefix = variable_get('biblio_citekey_prefix', '');
    $primary_field = variable_get('biblio_citekey_field1', 'nid');
    $secondary_field = variable_get('biblio_citekey_field2', 'nid');
    $citekey = !empty($biblio->{$primary_field}) ? $biblio->{$primary_field} : (!empty($biblio->{$secondary_field}) ? $biblio->{$secondary_field} : $biblio->bid);
    return check_plain($prefix . $citekey);
  }
  else {
    ob_start();
    $return = eval($php);
    ob_end_clean();
    return check_plain(strip_tags((string) $return));
  }
}

/**
 * Implementation of hook_block().
 *
 * Generates a block containing the latest poll.
 */
function biblio_block_info() {
  $blocks['recent'] = array(
    'info' => t('Recent publications'),
  );
  return $blocks;
}

/**
 * Implements hook_block_configure().
 */
function biblio_block_configure($delta = '') {
  $form = array();
  $form['block'] = array(
    '#type' => 'fieldset',
    '#collapsible' => FALSE,
    '#collapsed' => FALSE,
    '#title' => t('Options'),
    '#description' => '',
  );
  $form['block']['biblio_rowsperblock'] = array(
    '#type' => 'textfield',
    '#title' => t('Number of results in the "Recent Publications" block'),
    '#default_value' => variable_get('biblio_rowsperblock', 4),
    '#size' => 2,
    '#maxlength' => 2,
    '#description' => t('This sets the number of results that will be displayed in the "New Publications" block.'),
  );
  $form['block']['biblio_block_order'] = array(
    '#type' => 'radios',
    '#title' => t('Order by'),
    '#default_value' => variable_get('biblio_block_order', 'n.created'),
    '#options' => array(
      'n.created' => t('Date Created'),
      'b.biblio_year' => t('Year Published'),
    ),
  );
  return $form;
}

/**
 * Implements hook_block_save().
 */
function biblio_block_save($delta = '', $edit = array()) {
  if ($delta == 'recent') {
    variable_set('biblio_rowsperblock', $edit['biblio_rowsperblock']);
    variable_set('biblio_block_order', $edit['biblio_block_order']);
  }
}

/**
 * Implements hook_block_view().
 */
function biblio_block_view($delta = '') {
  switch ($delta) {
    case 'recent':
      $num_in_block = variable_get('biblio_rowsperblock', 4);
      $block_order = variable_get('biblio_block_order', 'n.created');
      $query = db_select('node', 'n')
        ->fields('n', array(
        'nid',
        'title',
      ))
        ->condition(db_and()
        ->condition('n.type', 'biblio')
        ->condition('n.status', 1))
        ->orderBy($block_order, 'DESC')
        ->range(0, $num_in_block);
      if ($block_order == 'b.biblio_year') {
        $query
          ->leftJoin('biblio', 'b', 'n.vid=b.vid');
      }
      $result = $query
        ->execute();
      $base = variable_get('biblio_base', 'biblio');
      $block['subject'] = t('Recent Publications');
      $block['content'] = '<div class="item-list"><ul>';
      $options['html'] = TRUE;
      foreach ($result as $pub) {
        $block['content'] .= '<li >' . l(filter_xss($pub->title, biblio_get_allowed_tags()), "node/{$pub->nid}", $options) . '</li>';
      }
      $block['content'] .= '</ul>';
      if (variable_get('biblio_rss', 0)) {
        $block['content'] .= theme('feed_icon', array(
          'url' => url("{$base}/recent/rss.xml", array(
            'absolute' => TRUE,
          )),
          'title' => t('Recent Publications'),
        ));
      }
      $block['content'] .= l(t('More...'), $base);
      $block['content'] .= '</div>';
      return $block;
      break;
  }
}

/**
 * hook_menu callback function for $base/recent/rss.xml
 */
function biblio_recent_feed() {
  $numberInFeed = variable_get('biblio_rss_number_of_entries', 10);
  $query = db_select('node', 'n')
    ->fields('n', array(
    'nid',
    'title',
  ))
    ->condition(db_and()
    ->condition('n.type', 'biblio')
    ->condition('n.status', 1))
    ->orderBy('n.created', 'DESC')
    ->range(0, $numberInFeed);
  $result = $query
    ->execute();
  $siteName = variable_get('site_name', 'Drupal');
  $base = variable_get('biblio_base', 'biblio');
  $channel['title'] = $siteName . ' - ' . t("Recently Added Publications");
  $channel['link'] = url($base, array(
    'absolute' => TRUE,
  ));
  $channel['description'] = t("This feed lists the %num most recently added publications on %site", array(
    '%num' => $numberInFeed,
    '%site' => $siteName,
  ));
  $nids = array();
  foreach ($result as $row) {
    $nids[] = $row->nid;
  }
  node_feed($nids, $channel);
}

/**
 * @todo create relevant documentation for this function
 *
 * @param array $rss_info
 * @param array $nids
 */
function biblio_filter_feed($rss_info, $nids) {
  $base = variable_get('biblio_base', 'biblio');
  $channel['title'] = $rss_info['title'];
  $channel['link'] = url($base . $rss_info['link'], array(
    'absolute' => TRUE,
  ));
  $channel['description'] = $rss_info['description'];
  node_feed($nids, $channel);
}

/**
 * Gets a list of machine-readable field names
 *
 * @return array Iterative array of field names
 *
 * @todo: possibly get rid of this
 */
function biblio_get_db_fields() {
  $fields = array();
  $fields[] = 'nid';
  $fields[] = 'vid';
  $fields[] = 'biblio_type';
  $result = db_query('SELECT name FROM {biblio_fields} ', array(), array(
    'fetch' => PDO::FETCH_ASSOC,
  ));
  foreach ($result as $field) {
    $fields[] = $field['name'];
  }
  return $fields;
}

/*******************************************
 * Filter
 * Largely inspired from the footnote module
 *
 *******************************************/

/**
 * @todo create relevant documentation for this function
 *
 * @param type $citekey
 * @return array
 */
function _biblio_citekey_print($citekey) {
  $nid = db_query("SELECT nid FROM {biblio} WHERE biblio_citekey = :key", array(
    ':key' => $citekey,
  ))
    ->fetchObject();
  if ($nid->nid > 0) {
    $style = biblio_get_style();
    $base = variable_get('biblio_base', 'biblio');
    $node = node_load($nid->nid);
    return theme('biblio_style', array(
      'node' => $node,
      'base' => $base,
      'style' => $style,
    ));
  }
  else {
    return t("Citekey @cite not found", array(
      '@cite' => $citekey,
    ));
  }
}

/**
 * Implements hook_filter_info().
 */
function biblio_filter_info() {
  $filters['biblio_filter_reference'] = array(
    'title' => t('Biblio module references &lt;bib&gt; <i>or</i> [bib]'),
    'description' => t('Use &lt;bib&gt;citekey&lt;/bib&gt; or [bib]citebkey[/bib]to insert automatically numbered references.'),
    'prepare callback' => '_biblio_filter_reference_prepare',
    'process callback' => '_biblio_filter_reference_process',
    'tips callback' => '_biblio_filter_reference_tips',
  );
  $filters['biblio_filter_inline_reference'] = array(
    'title' => t('Biblio module inline references &lt;ibib&gt; <i>or</i> [ibib]'),
    'description' => t('Use &lt;bib&gt;citekey&lt;/bib&gt; or [bib]citebkey[/bib]to insert automatically numbered references.'),
    'prepare callback' => '_biblio_filter_inline_reference_prepare',
    'process callback' => '_biblio_filter_inline_reference_process',
    'tips callback' => '_biblio_filter_inline_reference_tips',
  );
  return $filters;
}

/**
 * Tips callback for hook_filter_info().
 */
function _biblio_filter_reference_tips($filter, $format, $long = FALSE) {
  if (!$long) {

    // This string will be shown in the content add/edit form
    return t('Use &lt;bib&gt;<i>citekey</i>&lt;/bib&gt; <b><i>or</i></b> [bib]<i>citekey</i>[/bib] to insert automatically numbered references.');
  }
  else {
    return t('You can cite references directly into texts with <code>&lt;bib&gt;<i>citekey</i>&lt;/bib&gt; <b><i>or</i></b> [bib]<i>citekey</i>[/bib]</code>. This will be replaced with a running number (the publication reference) and the publication referenced by the citekey within the &lt;bib&gt; tags will be printed at the bottom of the page (the reference).');
  }
}

/**
 * Tips callback function for hook_filter_info()
 */
function _biblio_filter_inline_reference_tips($filter, $format, $long = FALSE) {
  return t('This creates an in line reference to another publication.');
}

/**
 * Prepare callback function for hook_filter_info()
 */
function _biblio_filter_reference_prepare($text, $filter, $format, $langcode, $cache, $cache_id) {
  return $text;
}

/**
 * Prepare callback function for hook_filter_info()
 */
function _biblio_filter_inline_reference_prepare($text, $filter, $format, $langcode, $cache, $cache_id) {
  return $text;
}

/**
 * Process callback function for hook_filter_info()
 */
function _biblio_filter_reference_process($text, $filter, $format, $langcode, $cache, $cache_id) {
  $pattern = array(
    '|\\[bib](.*?)\\[/bib]|s',
    '|<bib>(.*?)</bib>|s',
  );
  if (variable_get('biblio_footnotes_integration', 0) && module_exists('footnotes')) {

    // this is used with footnote module integration to replace the <bib> tags with <fn> tags
    $text = preg_replace_callback($pattern, '_biblio_filter_footnote_callback', $text);
    return $text;
  }
  else {
    $text = preg_replace_callback($pattern, '_biblio_filter_replace_callback', $text);

    //Replace tag <footnotes> with the list of footnotes.

    //If tag is not present, by default add the footnotes at the end.

    //Thanks to acp on drupal.org for this idea. see http://drupal.org/node/87226
    $footer = '';
    $footer = _biblio_filter_replace_callback(NULL, 'output footer');
    if (preg_match('/<bibliography(\\/( )?)?>/', $text) > 0) {
      $text = preg_replace('/<bibliography(\\/( )?)?>/', $footer, $text, 1);
      return $text;
    }
    else {
      return $text . "\n\n" . $footer;
    }
  }
}

/**
 * Process callback function for hook_filter_info()
 */
function _biblio_filter_inline_reference_process($text, $filter, $format, $langcode, $cache, $cache_id) {
  $pattern = array(
    '|\\[ibib](.*?)\\[/ibib]|s',
    '|<ibib>(.*?)</ibib>|s',
  );
  $text = preg_replace_callback($pattern, '_biblio_inline_filter_replace_callback', $text);
  return $text;
}

/**
 * preg_replace_callback() callback function
 */
function _biblio_inline_filter_replace_callback($matches) {
  $text = _biblio_citekey_print($matches[1]);
  return $text;
}

/**
 * preg_replace_callback() callback function
 */
function _biblio_filter_footnote_callback($matches, $square_brackets = FALSE) {
  if ($square_brackets) {
    $text = '[fn]' . _biblio_citekey_print($matches[1]) . "</fn>";
  }
  else {
    $text = '<fn>' . _biblio_citekey_print($matches[1]) . "</fn>";
  }
  return $text;
}

/**
 * preg_replace_callback() callback function
 *
 * Uses static vars to temporarily store footnotes found.
 * In my understanding, this is not threadsafe?!
 */
function _biblio_filter_replace_callback($matches, $op = '') {
  static $n = 0;
  static $store_matches = array();
  $str = '';
  if ($op == 'output footer') {
    if ($n > 0) {
      $str = '<hr /><h3>' . t('References') . '</h3>';
      $str .= '<div class="references"><ol>';
      for ($m = 1; $m <= $n; $m++) {
        $str .= '<li id="reference' . $m . '"><a name="ref' . $m . '">' . _biblio_citekey_print($store_matches[$m - 1]) . " </a></li>\n\n";
      }
      $str .= '</ol></div>';
    }
    $n = 0;
    $store_matches = array();
    return $str;
  }

  //default op: act as called by preg_replace_callback()
  $ref = array_search($matches[1], $store_matches);
  if ($ref === FALSE) {
    $n++;
    array_push($store_matches, $matches[1]);

    //$stores_matches[$matches[1]] = $n;
    $ref = $n;
  }
  else {
    $ref++;
  }
  $allowed_tags = array();
  $title = filter_xss($matches[1], biblio_get_allowed_tags());

  //html attribute cannot contain quotes
  $title = str_replace('"', "&quot;", $title);

  //remove newlines. Browsers don't support them anyway and they'll confuse line break converter in filter.module
  $title = str_replace("\n", " ", $title);
  $title = str_replace("\r", "", $title);

  //return '<sup class="see_reference" title="'. $title .'"><a href="#reference'. $n .'">'. $n .'</a></sup>';

  //$text = '<span><a href="#reference'. $n .'">['. $n .']</a> </span>';

  //$text = '<span>['. $n .']</span>';

  //$text .= '<span class="hovertip">'._biblio_citekey_print($title) .'</span>';
  $text = '<span hovertip="reference' . $ref . '"><a href="#ref' . $ref . '">[' . $ref . ']</a></span>';
  if (module_exists('hovertip')) {
    $text = '<span hovertip="reference' . $ref . '"><a href="#ref' . $ref . '">[' . $ref . ']</a></span>';
    $text .= '<span id="reference' . $ref . '" class="hovertip">' . _biblio_citekey_print($title) . '</span>';
  }
  else {
    $text = '<a href="#ref' . $ref . '" title="Reference ' . $ref . '">[' . $ref . ']</a>';
  }
  return $text;
}

/**
 * Implements hook_taxonomy_vocabulary_delete.
 */
function hook_taxonomy_vocabulary_delete($vocabulary) {
  if ($vocabulary->vid == variable_get('biblio_keyword_vocabulary', FALSE)) {
    variable_del('biblio_keyword_freetagging');
    variable_del('biblio_keyword_vocabulary');
  }
}

/**
 * Implements hook_term_path().
 *
 * Not supported in Drupal 7
 */
function biblio_term_path($term) {
  $base = variable_get('biblio_base', 'biblio');
  if ($term->vid == variable_get('biblio_collection_vocabulary', 0)) {
    return "{$base}/collection/{$term->name}";
  }
  elseif ($term->vid == variable_get('biblio_keyword_vocabulary', 0)) {
    return "{$base}/term_id/{$term->tid}";
  }
  else {
    return;
  }
}

/**
 * Gets potential duplicate of a node
 *
 * @staticvar array $sums
 * @param object $node
 * @return int the nid of the potential duplicate
 */
function biblio_hash($node) {
  static $sums = array();
  $duplicate = NULL;
  if (empty($sums)) {
    $res = db_query("SELECT nid, biblio_md5 FROM {biblio} ");
    foreach ($res as $md5) {
      $sums[$md5->biblio_md5] = $md5->nid;
    }
  }
  $hash_string = str_replace(' ', '', drupal_strtolower($node->title));
  if (isset($node->biblio_contributors[0]['lastname'])) {
    $hash_string .= str_replace(' ', '', drupal_strtolower($node->biblio_contributors[0]['lastname']));
  }
  $hash_string .= $node->biblio_year;
  $sum = md5($hash_string);
  if (isset($sums[$sum])) {
    $duplicate = $sums[$sum];
  }
  else {
    $sums[$sum] = $node->nid;
  }
  $node->biblio_md5 = $sum;
  return $duplicate;

  //return the nid of the potential duplicate
}

/**
 * Implements hook_token_info().
 */
function biblio_token_info() {
  $node['biblio_year'] = array(
    'name' => t("Biblio: Publication year"),
    'description' => '',
  );
  $node['biblio_authors'] = array(
    'name' => t("Biblio: Authors"),
    'description' => '',
  );
  $node['biblio_type_id'] = array(
    'name' => t("Biblio: Type ID (e.g.: 100)"),
    'description' => '',
  );
  $node['biblio_type'] = array(
    'name' => t("Biblio: Type Name (e.g.: book)"),
    'description' => '',
  );
  $node['biblio_citekey'] = array(
    'name' => t("Biblio: Cite Key often used in BibTex files"),
    'description' => '',
  );
  return array(
    'tokens' => array(
      'node' => $node,
    ),
  );
}

/**
 * Implements hook_tokens().
 */
function biblio_tokens($type, $tokens, $data = array(), $options = array()) {
  $replacements = array();
  if ($type == 'node' && !empty($data['node']) && $data['node']->type == 'biblio') {
    $node = $data['node'];
    foreach ($tokens as $name => $original) {
      switch ($name) {

        // Simple key values on the node.
        case 'biblio_year':
          $replacements[$original] = check_plain($node->biblio_year);
          break;
        case 'biblio_authors':
          $replacements[$original] = check_plain($node->biblio_contributors[0]['lastname']);
          break;
        case 'biblio_type_id':
          $replacements[$original] = check_plain($node->biblio_type);
          break;
        case 'biblio_citekey':
          $replacements[$original] = check_plain($node->biblio_citekey);
          break;
        case 'biblio_type':
          $type = db_query('SELECT name FROM {biblio_types} as t WHERE t.tid = :tid', array(
            ':tid' => $node->biblio_type,
          ))
            ->fetchField();
          $replacements[$original] = check_plain($type);
          break;
      }
    }
  }
  return $replacements;
}

/**
 * Callback for $base/user/[user] in hook_menu().
 */
function _biblio_profile_access($user, $type = 'profile') {
  if ($type == 'profile') {
    $key = 'biblio_show_profile';
  }
  elseif ($type == 'menu' && !empty($user) && $user->uid > 0) {
    $key = 'biblio_my_pubs_menu';
  }
  else {
    return FALSE;
  }

  // if user cannot override site settings or user hasn't yet made its selection, we use site default
  if (!variable_get('biblio_show_user_profile_form', '1') || !isset($user->data[$key])) {
    return variable_get($key, '0');

    // return site default
  }
  else {
    return $user->data[$key];

    // return user setting
  }
}

/**
 * Helper function to get either the user or system style
 *
 * @global object $user
 * @return type
 */
function biblio_get_style() {
  global $user;
  if (isset($user->data['biblio_user_style']) && $user->data['biblio_user_style'] != "system") {
    return $user->data['biblio_user_style'];
  }
  return module_exists('biblio_citeproc') ? variable_get('biblio_citeproc_style', 'cse.csl') : variable_get('biblio_style', 'cse');
}

/**
 * Gets a list of available bibliography format styles
 *
 * @return array
 */
function biblio_get_styles() {
  $styles = array();
  if (module_exists('biblio_citeproc')) {
    $result = db_select('biblio_citeproc_styles', 'csl')
      ->fields('csl', array(
      'filename',
      'title',
    ))
      ->orderBy('title', 'ASC')
      ->execute();
    foreach ($result as $style) {
      $styles[$style->filename] = $style->title;
    }
  }
  else {
    $dir = drupal_get_path('module', 'biblio') . '/styles';
    $files = file_scan_directory($dir, '/biblio_style_..*.inc$/');
    foreach ($files as $file) {
      include_once $file->uri;
      $function = $file->name . '_info';
      if (function_exists($function)) {
        $styles = array_merge($styles, call_user_func($function));

        //build and array of the short and long names
      }
    }
    ksort($styles);
  }
  return $styles;
}

/**
 * Implements hook_views_api().
 */
function biblio_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'biblio') . '/views',
  );
}

/**
 * Implements hook_views_handlers().
 */
function biblio_views_handlers() {
  return array(
    'handlers' => array(
      'views_handler_field',
      'views_handler_field_field',
      'entity_views_handler_field_field',
    ),
  );
}

/**
 * @todo add relevant documentation
 *
 * @param object $node
 */
function biblio_fix_isi_links(&$node) {
  $isi = check_plain(variable_get('biblio_isi_url', 'http://apps.isiknowledge.com/InboundService.do?Func=Frame&product=WOS&action=retrieve&SrcApp=EndNote&Init=Yes&SrcAuth=ResearchSoft&mode=FullRecord&UT='));
  if (isset($node['biblio_url']) && preg_match('/Go\\s*to\\s*ISI/', $node['biblio_url'])) {
    $node['biblio_url'] = str_replace('<Go to ISI>://', $isi, $node['biblio_url']);
  }
  if (isset($node['biblio_accession_number']) && preg_match('/^ISI:/', $node['biblio_accession_number'])) {
    $node['biblio_accession_number'] = str_replace("ISI:", $isi, $node['biblio_accession_number']);
  }
}

/**
 * Gets a list of allowed HTML tags
 *
 * @return array iterative array of allowed tags
 */
function biblio_get_allowed_tags() {
  return array(
    'a',
    'b',
    'i',
    'u',
    'sub',
    'sup',
    'span',
  );
}

/**
 * @todo add relevant documentation
 *
 * @param object $biblio
 * @return array
 */
function biblio_get_title_url_info($biblio) {
  return array(
    'link' => variable_get('biblio_link_title_url', 0) && !empty($biblio->biblio_url) ? $biblio->biblio_url : "biblio/{$biblio->bid}",
    'options' => array(
      'attributes' => variable_get('biblio_links_target_new_window', FALSE) ? array(
        'target' => '_blank',
      ) : array(),
      'html' => TRUE,
    ),
  );
}

/**
 * Get publication type mapping for a biblio import type
 *
 * @param string $type (can be one of "type_names", "type_map" or "field_map")
 * @param string $format (tagged, ris, endnote_xml8 etc...)
 * @return array $map
 */
function biblio_get_map($type, $format) {
  $result = db_select('biblio_type_maps', 'btm')
    ->fields('btm', array(
    $type,
  ))
    ->condition('format', $format)
    ->execute()
    ->fetchField();
  $map = unserialize($result);
  if ($type == 'export_map' && empty($map)) {
    $schema = drupal_get_schema('biblio');
    $fieldnames = array_keys($schema['fields']);
    asort($fieldnames);
    $map = array_fill_keys($fieldnames, 1);
  }
  return $map;
}

/**
 * Inserts the publication type mapping to the database
 *
 * @param string $maps serialized type mapping data
 */
function biblio_save_map($maps) {
  db_insert('biblio_type_maps')
    ->fields($maps)
    ->execute();
}

/**
 * Saves a new publication type mapping to the database
 *
 * @param string $type (can be one of "type_names", "type_map" or "field_map")
 * @param string $format (tagged, ris, endnote_xml8 etc...)
 * @param array  $map
 */
function biblio_set_map($type, $format, $map) {
  $map[$type] = serialize($map);
  $map['format'] = $format;
  drupal_write_record('biblio_type_maps', $map, 'format');
}

/**
 * Resets the publication type mapping of an import type
 * @param type $type
 * @param type $format
 */
function biblio_reset_map($type, $format) {
  module_invoke_all($format . '_map_reset', $type);
}

/**
 * Implements hook_field_extra_fields().
 */
function biblio_field_extra_fields() {
  module_load_include('inc', 'biblio', 'includes/biblio.fields');
  return _biblio_field_extra_fields();
}

/**
 * Removes the cid field from the contributors of a biblio node
 * Fixes node export importing issue #826506
 *
 * @param object $node
 * @param type $original_node
 */
function biblio_node_export_node_alter($node, $original_node) {
  if ($node->type == 'biblio' && isset($node->biblio_contributors) && is_array($node->biblio_contributors)) {
    foreach ($node->biblio_contributors as $n => $value) {
      unset($node->biblio_contributors[$n]['cid']);
    }
  }
}

/**
 * Implements hook_feeds_processor_targets_alter
 *
 * Alters mapping targets for nodes.
 * Use this hook to add additional target options to the mapping form of Node processors.
 */
function biblio_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
  module_load_include('inc', 'biblio', 'includes/biblio.feeds');
  return _biblio_feeds_processor_targets_alter($targets, $entity_type, $bundle_name);
}

/**
 * @todo create relevant documentation for this function
 *
 * @return type
 */
function biblio_feeds_importer_default() {
  module_load_include('inc', 'biblio', 'includes/biblio.feeds');
  $defaults = array();
  if (module_exists('feeds_oai_pmh')) {
    $defaults += _biblio_feeds_oai_importer_default();
  }
  return $defaults;
}

/**
 * @todo create relevant documentation for this function
 *
 * @return array
 */
function biblio_ctools_plugin_api() {
  list($module, $api) = func_get_args();
  if ($module == "feeds" && $api == "feeds_importer_default") {
    return array(
      "version" => 1,
    );
  }
}

/*********************************
 * Entity Additions
 *********************************/

/**
 * Implements hook_entity_info().
 *
 * Inform the Drupal and the Field API about entity types.
 * Uses the contrib Entity API module to create entities
 */
function biblio_entity_info() {
  $return['biblio'] = array(
    'label' => t('Biblio'),
    'entity class' => 'Biblio',
    'controller class' => 'BiblioController',
    'views controller class' => 'EntityDefaultViewsController',
    'base table' => 'biblio',
    'fieldable' => TRUE,
    'entity keys' => array(
      'id' => 'bid',
      'bundle' => biblio_bundle_name('biblio'),
    ),
    'bundle keys' => array(
      'bundle' => biblio_bundle_name('biblio'),
    ),
    'static cache' => TRUE,
    // Will be retrieved using biblio_types()
    'bundles' => array(),
    'load hook' => 'biblio_load',
    // @todo: implement biblio node view modes. See http://drupal.org/node/1537320
    'view modes' => array(
      'full' => array(
        'label' => t('Full Content'),
        // Indicates whether or not the Field UI should allow field formatters to
        // be configured separately for that view mode by default.
        'custom settings' => FALSE,
      ),
      'teaser' => array(
        'label' => t('Teaser'),
        'custom settings' => FALSE,
      ),
    ),
    // Entity API label callback that takes a look at our entity class method defaultLabel()
    'label callback' => 'entity_class_label',
    // This is also a standard Entity API callback for uri.
    // It executes our entity defaultUri() method
    'uri callback' => 'entity_class_uri',
    'module' => 'biblio',
    'access callback' => 'biblio_entity_access',
  );

  // Since we have multiple bundles, we will need to tell the Field API what
  // bundles we have and what paths to put the extra field management interfaces
  // for our entity. To do that, we define, for each of our bundles, a label
  // that is shown to the user and the menu information that the Field API will
  // need to add itself to the menu.
  $publication_types = biblio_types('biblio');
  if (!empty($publication_types)) {
    foreach ($publication_types as $type => $info) {
      $return['biblio']['bundles'][$type] = array(
        'label' => $info->name,
        'admin' => array(
          // defines the path that should be used in hook_menu() for the Fields UI pages
          'path' => 'admin/structure/biblio/publication-types/%biblio_bundle',
          // The exact path that should be used when generating links within the admin interface
          'real path' => 'admin/structure/biblio/publication-types/' . str_replace('_', '-', $type),
          // menu args are 0 based, so  is the seventh part of the path, which
          // here is %biblio_bundle.
          'bundle argument' => 4,
          'access arguments' => array(
            'manage biblio structure',
          ),
        ),
      );
    }
  }
  $return['biblio_contributor'] = array(
    'label' => t('Contributor'),
    'entity class' => 'BiblioContributor',
    'controller class' => 'BiblioContributorController',
    'base table' => 'biblio_contributor',
    'fieldable' => TRUE,
    'entity keys' => array(
      'id' => 'cid',
      'bundle' => biblio_bundle_name('biblio_contributor'),
      'label' => 'title',
    ),
    'bundle keys' => array(
      'bundle' => biblio_bundle_name('biblio_contributor'),
    ),
    // @todo: possibly figure out a better bundle name and/or figure out what
    // bundles we can have for contributors
    'bundles' => array(),
    'load hook' => 'biblio_contributor_load',
    'view modes' => array(
      'full' => array(
        'label' => t('Full'),
        'custom settings' => FALSE,
      ),
    ),
    'label callback' => 'entity_class_label',
    'uri callback' => 'entity_class_uri',
    'module' => 'biblio',
    'access callback' => 'biblio_entity_access',
  );
  $contributor_categories = biblio_types('biblio_contributor');
  if (!empty($contributor_categories)) {
    foreach ($contributor_categories as $type => $info) {
      $return['biblio_contributor']['bundles'][$type] = array(
        'label' => $info->name,
        'admin' => array(
          'path' => 'admin/structure/biblio/contributors/%contributor_bundle',
          'real path' => 'admin/structure/biblio/contributors/' . str_replace('_', '-', $type),
          'bundle argument' => 4,
          'access arguments' => array(
            'manage biblio structure',
          ),
        ),
      );
    }
  }
  return $return;
}

/**
 * Gets all publication types that are set up by default.
 * @return type
 */
function biblio_types($entity_type = FALSE) {
  $query = db_select('biblio_types', 'bt')
    ->fields('bt', array(
    'name',
    'description',
    'entity',
  ));
  if ($entity_type) {
    $query
      ->condition('entity', $entity_type);
  }
  $result = $query
    ->execute();
  $entity_type_passed_in = $entity_type;
  while ($row = $result
    ->fetchAssoc()) {
    if ($entity_type_passed_in === FALSE) {
      $entity_type = $row['entity'];
    }
    $bundle_name = biblio_bundle_name($entity_type);

    // computer-friendly version of the human-readable content type stored in the database
    $machine_name = str_replace(' ', '_', strtolower($row['name']));
    $types[$machine_name] = (object) array(
      // type is the machine name of the bundle
      $bundle_name => $machine_name,
      'entity' => $row['entity'],
      // name is the human name of the bundle
      'name' => t($row['name']),
      'description' => t($row['description']),
    );
  }
  return $types;
}

/**
 * Gets the title of a bundle for a given entity type
 *
 * @param string $entity_type
 * @return string
 */
function biblio_bundle_name($entity_type) {
  switch ($entity_type) {
    case 'biblio':
      return 'publication_type';
      break;
    default:
      return 'type';
      break;
  }
}

/**
 * Access callback for the entity API.
 */
function biblio_entity_access($op, $type = NULL, $account = NULL) {
  global $user;
  if (!isset($account)) {
    $account = $user;
  }
  switch ($op) {
    case 'create':
      return user_access('administer biblio', $account) || user_access('create biblio', $account);
    case 'view':
      return user_access('administer biblio', $account) || user_access('access biblio content', $account);
    case 'delete':
    case 'edit':
      return user_access('administer biblio') || user_access('edit all biblio entries');
  }
}
function biblio_bundle_load($type) {
  return biblio_combined_bundle_load($type);
}
function contributor_bundle_load($type) {
  return biblio_combined_bundle_load($type);
}

/**
 * Get an individual publication type definition object.
 * Necessary for the %biblio_bundle menu placeholder to work in hook_menu().
 *
 * @param type $type
 *  The key of the publication type we want
 * @return object
 *  The specified publication type.
 */
function biblio_combined_bundle_load($type) {
  foreach (biblio_entities() as $entity) {
    foreach (biblio_types($entity) as $type_name => $info) {
      $types[$type_name] = $info;
    }
  }

  // Replace dashes in a type name with underscores.
  // All Drupal paths use dashes in place of underscores in the URI, but
  // bundle names need to use underscores, not dashes.
  $type = str_replace('-', '_', $type);
  return isset($types[$type]) ? $types[$type] : FALSE;
}

/**
 * Very simply returns an array of all the entity types that biblio creates.
 *
 * @return array Iterative array containing the biblio entity types
 */
function biblio_entities() {
  return array(
    'biblio',
    'biblio_contributor',
  );
}

/**
 * Helper function to get a wrapper object, built by the Entity API, to help
 * ease the pain of getting/setting field data. Fields for entities can be get
 * and set using the following syntax:
 * @example
 * // set biblio field data (in this case, the title);
 * $wrapper = biblio_wrapper($biblio, 'biblio')
 * $wrapper->biblio_title->set('My Special Publication');
 * // get biblio field data
 * $title = $wrapper->biblio_title->value();
 *
 * @param object $entity standard entity object - $biblio, $contributor, etc.
 * @param string $entity_type biblio, biblio_contributor
 *  Defaults to 'biblio'
 * @return object A fully structured entity wrapper built by the Entity API
 */
function biblio_wrapper($entity, $entity_type = 'biblio') {
  $entity_types = biblio_entities();
  if (!in_array($entity_type, $entity_types)) {

    // We're not dealing with one of biblio's entity types
    $info = entity_get_info($entity_type);
    if (empty($info)) {

      // We're not even dealing with a real entity type.
      throw new Exception('Invalid entity type: ' . $entity_type);
    }
  }
  return entity_metadata_wrapper($entity_type, $entity);
}

/**
 * Create a biblio entity object
 * @param string $publication_type
 *  The publication type of the biblio to be created (bundle)
 * @param array $values
 *  An associative array of any additional values to be set when creating this
 *  entity. These values will be carried throughout the biblio object's life.
 *  Example: $values['language'] => 'en';
 * @return object The biblio object, with default values.
 */
function biblio_create($publication_type, $values = array()) {
  module_load_include('inc', 'biblio', 'includes/biblio.fields');
  biblio_check_instances($publication_type);
  $values['publication_type'] = $publication_type;
  $field_values = array();
  foreach ($values as $property => $value) {
    if (biblio_is_field_instance($property, 'biblio', $publication_type)) {

      // We'll let the metadata wrapper handle field data
      $field_values[$property] = $value;
    }
    else {

      // Set all aother properties during entity creation
      $initial_values[$property] = $value;
    }
  }
  $biblio = entity_create('biblio', $initial_values);
  $wrapper = biblio_wrapper($biblio);
  $property_info = $wrapper
    ->getPropertyInfo();
  foreach ($field_values as $property => $value) {

    // 'text_formatted' field types have an extra layer of properties
    // see http://drupal.org/node/1396046
    $value_prop = isset($property_info[$property]['property info']['value']);
    if ($value_prop) {
      $wrapper->{$property} = array(
        'value' => $value,
      );
    }
    else {

      // Most normal fields
      $wrapper->{$property}
        ->set($value);
    }
  }
  return $biblio;
}

/**
 * Load a biblio object from the database.
 *
 * @param $bid
 *   The biblio ID.
 * @param $reset
 *   Whether to reset the biblio_load_multiple cache.
 *
 * @return
 *   A fully-populated node object.
 */
function biblio_load($bid, $reset = FALSE) {
  $biblios = biblio_load_multiple(array(
    $bid,
  ), array(), $reset);
  $biblio = reset($biblios);
  return $biblio;
}

/**
 * Load biblio entities from the database.
 *
 * This function should be used whenever you need to load more than one biblio
 * from the database. biblios are loaded into memory and will not require
 * database access if loaded again during the same page request.
 *
 * @see entity_load()
 *
 * @param $bids
 *   An array of biblio IDs.
 * @param $conditions
 *   An array of conditions on the {biblio} table in the form 'field' => $value.
 * @param $reset
 *   Whether to reset the internal entity_load cache.
 *
 * @return
 *   An array of biblio objects indexed by bid.
 */
function biblio_load_multiple($bids = array(), $conditions = array(), $reset = FALSE) {

  // entity_load() will create a new instance of biblioController as needed
  // and call the load() method on it.
  $biblios = entity_load('biblio', $bids, $conditions, $reset);
  return $biblios;
}

/**
 * Save a biblio object to the database
 */
function biblio_save($biblio) {
  module_load_include('inc', 'biblio', 'includes/biblio.keywords');
  _biblio_prepare_submit($biblio);
  $biblio->changed = REQUEST_TIME;

  // @todo: get md5 working. See http://drupal.org/node/1537364
  //  $duplicate = biblio_hash($biblio);
  //  if (isset($duplicate) && $duplicate != $biblio->bid) { // if this is a potential duplcate, write the bids of the pre-existing and new biblios
  //    $dup_map = array('vid' => $duplicate, 'did' => $biblio->bid);
  //    drupal_write_record('biblio_duplicates', $dup_map);
  //  }
  entity_save('biblio', $biblio);
}

/**
 * Delete single biblio entry.
 */
function biblio_delete($biblio) {
  entity_delete_multiple('biblio', array(
    $biblio->bid,
  ));
}

/**
 * Deletes multiple biblio records from the database
 *
 * @param array $contributor_ids An array of biblio ids of the entities to delete
 */
function biblio_delete_multiple($bids) {
  if (!empty($bids)) {
    $biblios = biblio_load_multiple($bids);
  }
  foreach ($biblios as $bid => $biblio) {
    module_invoke_all('entity_delete', $biblio, 'biblio');

    // @todo delete keywords, contributors, and field data associated with a
    // biblio. See http://drupal.org/node/1537326
  }
  entity_delete_multiple('biblio', $bids);
}

/**
 * Let the Form API validate our form for us.
 *
 * @param array $form
 * @param array $form_state
 */
function biblio_form_validate($form, &$form_state) {
  if ($form_state['triggering_element']['#value'] == t('Next') || $form_state['triggering_element']['#value'] == t('Change Publication Type')) {
    $form_state['rebuild'] = TRUE;
    $form_state['biblio_type'] = $form_state['values']['biblio_type'];
    if ($form_state['values']['biblio_type'] == '') {
      form_set_error('biblio_type', t('Please select a publication type.'));
    }
    return;
  }
  if (isset($form_state['biblio']) && !isset($biblio)) {
    $biblio = $form_state['biblio'];
  }
  if (isset($biblio)) {

    // Field validation.
    $i = 0;
    field_attach_form_validate('biblio', $biblio, $form, $form_state);
  }
}

/**
 * Process data that was submitted from the biblio add form. This handles the
 * additions of Biblio and Contributor entities.
 *
 * @global type $user
 * @param type $form
 * @param array $form_state
 */
function biblio_form_submit($form, &$form_state) {
  global $user;
  module_load_include('inc', 'biblio', 'includes/biblio.contributors');

  // Pull the old biblio object out of the $form_state variable.
  $biblio =& $form_state['biblio'];

  // Set the biblio's uid if it's being created at this time.
  if (empty($biblio->uid)) {
    $biblio->uid = $user->uid;
  }
  biblio_create_contributor_refs($form_state);

  // Notify field widgets.
  field_attach_submit('biblio', $biblio, $form, $form_state);

  // Save the biblio.
  biblio_save($biblio);

  // Notify the user.
  drupal_set_message(t('Biblio saved.'));
  $form_state['redirect'] = 'biblio/' . $biblio->bid;
}

/**
 * Creates contributor reference fields based on user-entered contributor names
 * and categories. Also creates Contributor entities for those contributors
 * that don't already exist.
 *
 * @param  array &$form_state
 */
function biblio_create_contributor_refs(&$form_state) {
  module_load_include('inc', 'biblio', 'includes/biblio.contributors');
  $categories = biblio_contributor_categories();

  // Reset the existing values, so we can unlink a ref by deleting a name in
  // the form.
  // @todo use the field language, not 'und'
  foreach ($categories as $info) {
    $form_state['values'][$info['field']]['und'] = array();
  }
  $delta = 0;
  foreach ($form_state['values']['biblio_contributors'] as $contrib) {
    if ($contrib['rank'] == '') {
      $contrib['rank'] = $delta;
    }
    $delta++;

    // Don't process fields left blank
    if (empty($contrib['name']) || empty($contrib['category'])) {
      continue;
    }
    $contributor = biblio_get_contributor_by_name($contrib['name']);
    if (!$contributor) {

      // Name not found in db. Contributor entity needs to be created
      $contributor = biblio_contributor_create($contrib['name']);
      $wrapper = biblio_wrapper($contributor, 'biblio_contributor');
      $wrapper->biblio_contributor_name
        ->set($contrib['name']);

      // Save the contributor entity so we can get a CID
      biblio_contributor_save($contributor);
    }
    $contrib['cid'] = $contributor->cid;

    // Get the field associated with the contributor's category
    $field = $categories[$contrib['category']]['field'];

    // Whether or not the CID already exists for this biblio in the same
    // contributor category (handles duplicate contributors in the same biblio)
    $id_exists = FALSE;
    foreach ($form_state['values'][$field]['und'] as $info) {
      if ($info['target_id'] == $contributor->cid) {
        $id_exists = TRUE;
      }
    }

    // Add the target CID and rank to the form values array, as if a user
    // had submitted these values
    if (!$id_exists) {
      $form_state['values'][$field]['und'][$contrib['rank']] = array(
        'target_id' => (string) $contrib['cid'],
        '_weight' => $contrib['rank'],
      );
    }
  }
}

// function biblio_contributor_exists($name) {
function biblio_get_contributor_by_name($name) {
  $contributors =& drupal_static(__FUNCTION__);
  if (isset($contributors[$name])) {
    return $contributors[$name];
  }

  // This is a temporary contributor entity only used to get the field values
  // and MD5
  $contributor = biblio_contributor_create($name);
  $result = db_select('biblio_contributor', 'c')
    ->fields('c', array(
    'cid',
    'md5',
  ))
    ->condition('md5', $contributor->md5)
    ->execute()
    ->fetchObject();
  if (isset($result->cid)) {

    // only need first result
    $contributors[$name] = biblio_contributor_load($result->cid);
    return $contributors[$name];
  }
  else {
    return FALSE;
  }
}

/**
 * Get an array of contributors that were submitted from the biblio add form
 *
 * @param array $form The full form array
 * @return array an iterative array of contributors, sorted by rank
 */
function biblio_form_get_contributors($form) {
  $i = 0;
  foreach ($form['biblio_tabs']['contributors']['biblio_contributors'] as $key => $value) {

    // Contributors are keyed by a standard numeric iteration, but are mixed
    // in with other form array values.
    if (is_numeric($key)) {

      // Get the name that the user entered in the name field
      $name_value = $value['name']['#value'];

      // If user didn't set rankings for the contributors, the value defaults
      // to an empty string
      $rank_value = $value['rank']['#value'] == '' ? $i : $value['rank']['#value'];

      // Name fields left blank are empty strings
      if (isset($name_value) && $name_value != '') {
        $contributors[$rank_value] = $name_value;
        $i++;
      }
    }
  }

  // Sort contributors in order of rank
  ksort($contributors);
  return $contributors;
}

/**
 * Displays a biblio; Hands data off to the Field API
 *
 * @param type $biblio
 * @param type $view_mode View mode defined in biblio_entity_info().
 * @return type
 */
function biblio_page_view($biblio, $view_mode = 'full', $langcode = NULL) {
  if (!isset($langcode)) {
    $langcode = $GLOBALS['language_content']->language;
  }
  global $user;
  $wrapper = biblio_wrapper($biblio);
  $title = $wrapper->biblio_title
    ->value();
  $links = array();
  $style = biblio_get_style();
  $base = variable_get('biblio_base', 'biblio');
  $base_title = check_plain(variable_get('biblio_base_title', 'Biblio'));

  // Remove previously built content, if exists.
  $biblio->content = array();
  switch ($view_mode) {
    case 'full':
      if (variable_get('biblio_hide_bibtex_braces', 0) && !isset($biblio->view)) {
        $title = biblio_remove_brace($title);
      }
      $title = filter_xss($title, biblio_get_allowed_tags());

    // fall through...
    case 'search_index':
      switch (variable_get('biblio_layout', 'standard')) {
        case 'standard':
          field_attach_prepare_view('biblio', array(
            $biblio->bid => $biblio,
          ), $view_mode);
          entity_prepare_view('biblio', array(
            $biblio->bid => $biblio,
          ));
          $biblio->content = field_attach_view('biblio', $biblio, $view_mode);
          if (isset($biblio->content['biblio_keywords'])) {
            $keys = element_children($biblio->content['biblio_keywords']);
            foreach ($keys as $key) {
              $biblio->content['biblio_keywords'][$key]['#href'] = 'biblio/keywords/' . $biblio->content['biblio_keywords'][$key]['#options']['entity']->tid;
            }
          }
          $coins = filter_xss($wrapper->biblio_coins
            ->value(), array(
            'span',
          ));
          $biblio->content['biblio_coins'] = array(
            '#markup' => $coins,
          );
          break;
        case 'orig':
        case 'ft':
          $biblio->content['body']['#markup'] = theme('biblio_long', array(
            'biblio' => $biblio,
            'base' => $base,
            'style' => $style,
          ));
          break;
        case 'tabular':
        default:
          $biblio->content['body']['#markup'] = theme('biblio_tabular', array(
            'biblio' => $biblio,
            'base' => $base,
          ));
          break;
      }
      break;
    case 'teaser':
      $biblio->content['teaser']['#markup'] = theme('biblio_style', array(
        'biblio' => $biblio,
        'base' => $base,
        'style_name' => $style,
      ));
      break;
  }
  drupal_set_title($title);
  module_invoke_all('entity_view', $biblio, 'biblio', $view_mode, $langcode);
  return $biblio->content;
}

/**
 * hook_menu() callback
 */
function biblio_page_title($biblio) {
  $wrapper = biblio_wrapper($biblio);
  return $wrapper->biblio_title
    ->value();
}

/**
 * Get the language code for a field instance
 * @param string $field_name The machine name of the field/instance
 * @param object $biblio
 * @return string The language code ('en', 'und', etc...)
 */
function biblio_field_language($field_name, $biblio) {
  $field_languages = field_language('biblio', $biblio);
  return $field_languages[$field_name];
}

/**
 * Rather than build a complete form, we will simply pass data on to a utility
 * function of the Form API called confirm_form().
 *
 * @param type $form
 * @param type $form_state
 * @param type $biblio
 * @return type
 */
function biblio_delete_confirm($form, &$form_state, $biblio) {
  $wrapper = biblio_wrapper($biblio);
  $form['#biblio'] = $biblio;

  // Always provide entity id in the same form key as in the entity edit form
  $form['bid'] = array(
    '#type' => 'value',
    '#value' => $biblio->bid,
  );
  return confirm_form($form, t('Are you sure you want to delete %title?', array(
    '%title' => $wrapper->biblio_title
      ->value(),
  )), 'biblio/' . $biblio->bid, t('This action cannot be undone.'), t('Delete'), t('Cancel'));
}

/**
 * Delete form confirmation page
 *
 * @param array $form
 * @param array $form_state
 */
function biblio_delete_confirm_submit($form, &$form_state) {
  if ($form_state['values']['confirm']) {
    $wrapper = biblio_wrapper($form['#biblio']);
    biblio_delete($form['#biblio']);
    watchdog('biblio', '@type: deleted %title.', array(
      '@type' => $wrapper->publication_type,
      '%title' => $wrapper->biblio_title
        ->value(),
    ));
    $types = biblio_types('biblio');
    drupal_set_message(t('@type %title has been deleted.', array(
      '@type' => $types[$wrapper->publication_type
        ->value()]->name,
      '%title' => $wrapper->biblio_title
        ->value(),
    )));
  }
  $form_state['redirect'] = '<front>';
}

/**
 * Redirects the user to biblio/$bid/delete
 *
 * @param type $form
 * @param type $form_state
 */
function biblio_form_delete_submit($form, &$form_state) {
  $destination = array();
  if (isset($_GET['destination'])) {
    $destination = drupal_get_destination();
    unset($_GET['destination']);
  }
  $biblio = $form_state['biblio'];
  $form_state['redirect'] = array(
    'biblio/' . $biblio->bid . '/delete',
    array(
      'query' => $destination,
    ),
  );
}

/**
 * Create a contributor entity object
 * @param  string $name   (optional) The name of the contributor. If given, this
 *                        function will parse out the author name and
 *                        automatically fill in any associated fields (first
 *                        name, last name, initials, etc.) and the type
 * @param  string $type   (optional) The contributor type (bundle) of the
 *                        contributor entity to create. ex: 'person' or
 *                        'organization'. Leave null to let this function parse
 *                        the name (if passed in) and auto-generate the type.
 * @param  array  $values (optional)
 * @return object         The contributor entity object
 */
function biblio_contributor_create($name = NULL, $type = NULL, $values = array()) {
  module_load_include('inc', 'biblio', 'includes/biblio.contributors');
  if (variable_get('biblio_contributor_parser', 1) && isset($name)) {
    $parsed_contributor = biblio_contributor_initial_parse($name);
    if (isset($parsed_contributor->organization)) {
      $values['type'] = 'organization';
    }
    else {
      $values['type'] = 'person';
    }
    $contributor = entity_create('biblio_contributor', $values);
    $contributor->orig_name = $name;
    biblio_contributor_set_parsed_values($contributor, $parsed_contributor);
  }
  else {
    $values['type'] = $type;
    $contributor = entity_create('biblio_contributor', $values);
  }
  $contributor->md5 = _md5sum($contributor);
  return $contributor;
}

/**
 * Load a contributor.
 */
function biblio_contributor_load($cid, $reset = FALSE) {
  $contributors = biblio_contributor_load_multiple(array(
    $cid,
  ), array(), $reset);
  return reset($contributors);
}

/**
 * Load multiple contributors based on certain conditions.
 */
function biblio_contributor_load_multiple($cids = array(), $conditions = array(), $reset = FALSE) {
  return entity_load('biblio_contributor', $cids, $conditions, $reset);
}

/**
 * Save contributor.
 */
function biblio_contributor_save($contributor) {
  module_load_include('inc', 'biblio', 'includes/biblio.contributors');
  biblio_contributor_pre_save($contributor);
  entity_save('biblio_contributor', $contributor);
}

/**
 * Delete single contributor.
 */
function biblio_contributor_delete($contributor) {
  biblio_contributor_delete_multiple(array(
    $contributor->cid,
  ));
}

/**
 * Delete multiple contributors.
 */
function biblio_contributor_delete_multiple($contributor_ids) {
  $deleted = entity_delete_multiple('biblio_contributor', $contributor_ids);
}

/**
 * Displays a biblio contributor; Hands data off to the Field API
 *
 * @param type $contributor
 * @param type $view_mode View mode defined in biblio_entity_info().
 * @return type
 */
function biblio_contributor_page_view($contributor, $view_mode = 'full') {

  // Remove previously built content, if exists.
  $contributor->content = array();
  $wrapper = biblio_wrapper($contributor, 'biblio_contributor');

  // Build fields content. Standard stuff.
  field_attach_prepare_view('biblio_contributor', array(
    $contributor->cid => $contributor,
  ), $view_mode);
  entity_prepare_view('biblio_contributor', array(
    $contributor->cid => $contributor,
  ));
  $contributor->content += field_attach_view('biblio_contributor', $contributor, $view_mode);
  $extra_fields = field_extra_fields_get_display('biblio_contributor', 'contributor', $view_mode);

  // $contributor->content = array_merge($extra_fields, $contributor->content);
  biblio_append_extra_fields($contributor->content, $extra_fields);
  if (isset($contributor->content['view_all_by_contributor'])) {
    $contributor->content['view_all_by_contributor']['#markup'] = l(t('View all publications by ' . $wrapper->biblio_contributor_name
      ->value()), 'biblio/contributor/' . $contributor->cid . '/publications', array(
      'attributes' => array(
        'class' => array(
          'biblio-view-all-by-contrib',
        ),
      ),
    ));
  }
  return $contributor->content;
}
function biblio_append_extra_fields(&$content, $extra_fields) {
  foreach ($extra_fields as $field => $info) {
    if (isset($info['visible']) && $info['visible']) {
      $content[$field] = array();
      foreach ($info as $attribute => $value) {
        $content[$field]['#' . $attribute] = $value;
      }
    }
  }
}

/**
 * hook_menu callback function for admin/structure/biblio/add
 *
 * @return string
 */
function biblio_add() {
  return drupal_get_form('biblio_form');
}

/**
 * Presents the biblio editing form, or redirects to delete confirmation.
 *
 * @param type $biblio
 * @return type
 */
function biblio_page_edit($biblio) {
  $wrapper = biblio_wrapper($biblio);
  $lang = biblio_field_language('biblio_title', $biblio);
  $types = biblio_types('biblio');
  drupal_set_title(t('<em>Edit @type</em> @title', array(
    '@type' => $types[$biblio->publication_type]->name,
    '@title' => $wrapper->biblio_title
      ->value(),
  )), PASS_THROUGH);
  return drupal_get_form('biblio_form', $biblio);
}

/**
 * Get the child elements of a menu item.
 *
 * @param string $menu_item
 * @return array
 */
function menu_get_children($menu_item) {
  $leading_path = db_like($menu_item['path']);
  $result = db_query('SELECT * FROM menu_router WHERE path LIKE :leading_path', array(
    ':leading_path' => db_like($leading_path) . '/%',
  ));
  $children = array();
  foreach ($result as $row) {
    $children[$row->title] = $row->path;
  }
  return $children;
}

/**
 * hook_menu callback function for biblio/add/[publication-type]
 *
 * @global type $user
 * @param type $type
 * @return array
 */
function biblio_contributor_add() {

  // Create a new, empty biblio contributor object
  $contributor = entity_create('biblio_contributor', array());

  // Set page title
  // PASS_TRHOUGH as the second param tells the function to allow HTML in the
  // string we're giving because we've already checked to make sure it's safe.
  drupal_set_title(t('Create Contributor'), PASS_THROUGH);

  // Display the form
  return drupal_get_form('biblio_contributor_add_form', $contributor);
}

/**
 * Build the contributor add form
 * Called by the Form API
 *
 * @param array $form
 * @param array $form_state
 * @param object $contributor
 * @return type
 */
function biblio_contributor_add_form($form, &$form_state, $contributor) {

  // Save the biblio for later, in case we need it.
  $form['#contributor'] = $contributor;
  $form_state['contributor'] = $contributor;

  // Get all fields from the Field API
  field_attach_form('biblio_contributor', $contributor, $form, $form_state);

  // Add autocomplete to name field
  foreach ($form['field_author_name']['und'] as $key => $field_data) {
    if (is_numeric($key)) {
      $form['field_author_name']['und'][$key]['value']['#autocomplete_path'] = 'biblio/autocomplete/contributor';
    }
  }

  // Add the buttons.
  // Save button is always available
  $form['buttons'] = array();
  $form['buttons']['#weight'] = 100;
  $form['buttons']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
    '#weight' => 5,
    '#submit' => array(
      'biblio_contributor_form_submit',
    ),
  );

  // Delete button is available if we're editing an existing biblio.
  if (!empty($contributor->cid)) {
    $form['buttons']['delete'] = array(
      '#access' => user_access('delete biblios'),
      '#type' => 'submit',
      '#value' => t('Delete'),
      '#weight' => 15,
      '#submit' => array(
        'biblio_contributor_form_delete_submit',
      ),
    );
  }
  return $form;
}

/**
 * Glue code between the form and the biblio_contributor_save() function.
 *
 * @global type $user
 * @param type $form
 * @param array $form_state
 */
function biblio_contributor_form_submit($form, &$form_state) {
  global $user;

  // Pull the old biblio object out of the $form_state variable.
  $contributor =& $form_state['contributor'];

  // Set the biblio's uid if it's being created at this time.
  if (empty($contributor->uid)) {
    $contributor->uid = $user->uid;
  }

  // Notify field widgets.
  field_attach_submit('biblio_contributor', $contributor, $form, $form_state);

  // Save the contributor.
  biblio_contributor_save($contributor);

  // Notify the user.
  drupal_set_message(t('Contributor saved.'));
  $form_state['redirect'] = 'biblio/contributor/' . $contributor->cid;
}
function biblio_contributor_form($form, $form_state, $contributor = NULL) {
  field_attach_form('biblio_contributor', $contributor, $form, $form_state);
  $form_state['contributor'] = $contributor;
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
    '#weight' => 50,
    '#submit' => array(
      'biblio_contributor_form_submit',
    ),
  );

  // Delete button is available if we're editing an existing biblio.
  if (!empty($contributor->cid)) {
    $form['delete'] = array(
      '#access' => user_access('delete biblios'),
      '#type' => 'submit',
      '#value' => t('Delete'),
      '#weight' => 51,
      '#submit' => array(
        'biblio_contributor_form_delete_submit',
      ),
    );
  }
  return $form;
}
function biblio_contributor_form_delete_submit($form, &$form_state) {
  $destination = array();
  if (isset($_GET['destination'])) {
    $destination = drupal_get_destination();
    unset($_GET['destination']);
  }
  $contributor = $form_state['contributor'];
  $form_state['redirect'] = array(
    'biblio/contributor/' . $contributor->cid . '/delete',
    array(
      'query' => $destination,
    ),
  );
}

/**
 * Rather than build a complete form, we will simply pass data on to a utility
 * function of the Form API called confirm_form().
 *
 * @param type $form
 * @param type $form_state
 * @param type $biblio
 * @return type
 */
function biblio_contributor_delete_confirm($form, &$form_state, $contributor) {
  $form['#contributor'] = $contributor;

  // Always provide entity id in the same form key as in the entity edit form
  $form['cid'] = array(
    '#type' => 'value',
    '#value' => $contributor->cid,
  );
  return confirm_form($form, t('Are you sure you want to delete %title?', array(
    '%title' => $contributor->title,
  )), 'biblio/contributor/' . $contributor->cid, t('This action cannot be undone.'), t('Delete'), t('Cancel'));
}

/**
 * Delete form confirmation page
 *
 * @param array $form
 * @param array $form_state
 */
function biblio_contributor_delete_confirm_submit($form, &$form_state) {
  if ($form_state['values']['confirm']) {
    $contributor = $form['#contributor'];
    biblio_contributor_delete($contributor);
    watchdog('biblio', 'Deleted biblio contributor %title.', array(
      '%title' => $contributor->title,
    ));
    drupal_set_message(t('Contributor %title has been deleted.', array(
      '%title' => $contributor->title,
    )));
  }
  $form_state['redirect'] = '<front>';
}

/**
 * Implements hook_admin_paths().
 */
function biblio_admin_paths() {
  $base = variable_get('biblio_base', 'biblio');
  $paths = array(
    "{$base}/*/edit" => TRUE,
    "{$base}/*/delete" => TRUE,
  );
  return $paths;
}

/**
 * hook_menu callback for biblio administration pages that display a View.
 *
 * @param string $view_name The machine name of the view to show
 * @param string $view_display The display ID of the view to show
 *
 * @return type
 */
function biblio_admin_view($view_name, $view_display) {
  if (!module_exists('views')) {
    drupal_set_message('This page requires the Views module be enabled.', 'error');
    return;
  }
  $view_obj = views_get_view($view_name);

  // If the view exists
  if ($view_obj) {
    return views_page($view_name, $view_display);
  }
  else {
    $message = "Could not locate the <i>" . $view_name . "</i> View using the display <i>" . $view_display . "</i>. Please ensure that the Views module is enabled, and that the <i>" . $view_name . "</i> View is enabled.";
    drupal_set_message($message, 'error');
  }
  return '';
}

/**
 * Implements hook_form_alter().
 */
function biblio_form_alter(&$form, &$form_state, $form_id) {
  if (isset($form['#field']['bundles']['biblio'])) {
    switch ($form_id) {

      // Modify the Field settings form so we can add an option to select vtab
      // for a field in the biblio add/edit form
      case 'field_ui_field_settings_form':
      case 'field_ui_field_edit_form':
        $field_name = $form['#instance']['field_name'];
        $entity_type = $form['#instance']['entity_type'];
        $bundle = $form['#instance']['bundle'];
        $field_info = field_info_instance($entity_type, $field_name, $bundle);
        if (isset($field_info['settings']['vtab'])) {
          $vtab_default_value = $field_info['settings']['vtab'];
        }
        else {
          $vtab_default_value = 'none';
        }
        $vtabs = biblio_form_vtab_info();
        foreach ($vtabs as $vtab_info) {
          $options[$vtab_info['tab_id']] = t($vtab_info['title']);
        }
        $options['none'] = t('None');
        $form['instance']['settings']['vtab'] = array(
          '#type' => 'select',
          '#title' => t('Biblio Form Vertical Tab'),
          '#options' => $options,
          '#default_value' => $vtab_default_value,
          '#required' => TRUE,
        );
        break;
    }
  }
}

/**
 * Implements hook_entity_property_info_alter().
 */
function biblio_entity_property_info_alter(&$info) {
  $properties =& $info['biblio']['properties'];
  $properties['created'] = array(
    'label' => t("Date created"),
    'type' => 'date',
    'description' => t("The date the biblio was added."),
    'setter callback' => 'entity_property_verbatim_set',
    'setter permission' => 'edit all biblio entries',
    'schema field' => 'created',
  );
  $properties['changed'] = array(
    'label' => t("Date changed"),
    'type' => 'date',
    'description' => t("The date the biblio was most recently changed/edited."),
    'setter callback' => 'entity_property_verbatim_set',
    'setter permission' => 'edit all biblio entries',
    'schema field' => 'changed',
  );
  $properties['uid'] = array(
    'label' => t("Author"),
    'type' => 'user',
    'description' => t("The creator of the biblio entity."),
    'setter callback' => 'entity_property_verbatim_set',
    'setter permission' => 'edit all biblio entries',
    'required' => TRUE,
    'schema field' => 'uid',
  );
  $properties['biblio_sort_title'] = array(
    'label' => t("Sort Title"),
    'setter callback' => 'entity_property_verbatim_set',
    'setter permission' => 'edit all biblio entries',
    'schema field' => 'biblio_sort_title',
  );
  $properties =& $info['biblio_contributor']['properties'];
  $properties['created'] = array(
    'label' => t("Date created"),
    'type' => 'date',
    'description' => t("The date the contribtuor was added."),
    'setter callback' => 'entity_property_verbatim_set',
    'setter permission' => 'edit all biblio entries',
    'schema field' => 'created',
  );
  $properties['changed'] = array(
    'label' => t("Date changed"),
    'type' => 'date',
    'description' => t("The date the contributor was most recently changed/edited."),
    'setter callback' => 'entity_property_verbatim_set',
    'setter permission' => 'edit all biblio entries',
    'schema field' => 'changed',
  );
  $properties['md5'] = array(
    'label' => t("MD5"),
    'description' => t("MD5 of the contributor. Used for resolving duplicates"),
    'setter callback' => 'entity_property_verbatim_set',
    'setter permission' => 'edit all biblio entries',
    'schema field' => 'md5',
  );
}
function biblio_get_fielded_pubtypes() {
  $pubtypes = array();
  foreach (field_info_instances('biblio') as $bundle => $instances) {
    if (!empty($instances)) {
      $pubtypes[] = $bundle;
    }
  }
  return $pubtypes;
}

/**
 * Implements hook_entity_presave().
 */
function biblio_entity_presave($entity, $type) {
  _biblio_truncate_long_fields($entity, $type);
}

/**
 * Truncates field values that are longer than the specified field value length.
 * This is particularly useful when importing biblio entities.
 *
 * @param  object $entity
 * @param  string $type
 */
function _biblio_truncate_long_fields($entity, $type) {
  $valid_entity_types = array(
    'biblio',
    'biblio_contributor',
  );
  if (!in_array($type, $valid_entity_types)) {
    return;
  }
  $wrapper = biblio_wrapper($entity, $type);
  $entity_info = entity_get_info($type);
  $fields_info = field_info_fields();
  foreach ($entity as $field => $field_structure) {
    if (isset($fields_info[$field]) && ($value = $wrapper->{$field}
      ->value())) {
      $field_settings = $fields_info[$field]['settings'];
      if (is_string($value) && isset($field_settings['max_length'])) {
        $max_length = $field_settings['max_length'];
        $length = strlen($value);
        if ($length > $max_length) {
          $wrapper->{$field} = substr($value, 0, $max_length);
        }
      }
    }
  }
}

Functions

Namesort descending Description
biblio_access Implements hook_access().
biblio_add hook_menu callback function for admin/structure/biblio/add
biblio_admin_paths Implements hook_admin_paths().
biblio_admin_view hook_menu callback for biblio administration pages that display a View.
biblio_append_extra_fields
biblio_autocomplete Page callback for biblio/autocomplete in hook_menu().
biblio_block_configure Implements hook_block_configure().
biblio_block_info Implementation of hook_block().
biblio_block_save Implements hook_block_save().
biblio_block_view Implements hook_block_view().
biblio_bundle_load
biblio_bundle_name Gets the title of a bundle for a given entity type
biblio_citekey_generate Generates automated citation keys
biblio_combined_bundle_load Get an individual publication type definition object. Necessary for the %biblio_bundle menu placeholder to work in hook_menu().
biblio_contributors_add_more Submit action for the "add more" button
biblio_contributors_add_more_callback Callback from the Biblio Contributor Widget's "add more" button
biblio_contributor_add hook_menu callback function for biblio/add/[publication-type]
biblio_contributor_add_form Build the contributor add form Called by the Form API
biblio_contributor_biblio_form Set up the form for ONE contributor in the biblio add/edit form.
biblio_contributor_create Create a contributor entity object
biblio_contributor_delete Delete single contributor.
biblio_contributor_delete_confirm Rather than build a complete form, we will simply pass data on to a utility function of the Form API called confirm_form().
biblio_contributor_delete_confirm_submit Delete form confirmation page
biblio_contributor_delete_multiple Delete multiple contributors.
biblio_contributor_form
biblio_contributor_form_delete_submit
biblio_contributor_form_submit Glue code between the form and the biblio_contributor_save() function.
biblio_contributor_load Load a contributor.
biblio_contributor_load_multiple Load multiple contributors based on certain conditions.
biblio_contributor_page_view Displays a biblio contributor; Hands data off to the Field API
biblio_contributor_save Save contributor.
biblio_contributor_widget Get form structure for the multivalued add contributor widget
biblio_create Create a biblio entity object
biblio_create_contributor_refs Creates contributor reference fields based on user-entered contributor names and categories. Also creates Contributor entities for those contributors that don't already exist.
biblio_cron Implements hook_cron().
biblio_ctools_plugin_api @todo create relevant documentation for this function
biblio_delete Delete single biblio entry.
biblio_delete_confirm Rather than build a complete form, we will simply pass data on to a utility function of the Form API called confirm_form().
biblio_delete_confirm_submit Delete form confirmation page
biblio_delete_multiple Deletes multiple biblio records from the database
biblio_entities Very simply returns an array of all the entity types that biblio creates.
biblio_entity_access Access callback for the entity API.
biblio_entity_info Implements hook_entity_info().
biblio_entity_presave Implements hook_entity_presave().
biblio_entity_property_info_alter Implements hook_entity_property_info_alter().
biblio_feeds_importer_default @todo create relevant documentation for this function
biblio_feeds_processor_targets_alter Implements hook_feeds_processor_targets_alter
biblio_field_extra_fields Implements hook_field_extra_fields().
biblio_field_language Get the language code for a field instance
biblio_filter_clear Page callback for base/filter/clear in hook_menu().
biblio_filter_feed @todo create relevant documentation for this function
biblio_filter_info Implements hook_filter_info().
biblio_fix_isi_links @todo add relevant documentation
biblio_form Displays the Add/Edit form for a biblio entity
biblio_forms Implements hook_forms().
biblio_form_alter Implements hook_form_alter().
biblio_form_delete_submit Redirects the user to biblio/$bid/delete
biblio_form_get_contributors Get an array of contributors that were submitted from the biblio add form
biblio_form_node_form_alter Implements hook_form_FORM_ID_alter
biblio_form_submit Process data that was submitted from the biblio add form. This handles the additions of Biblio and Contributor entities.
biblio_form_user_profile_form_alter Implements hook_user().
biblio_form_validate Let the Form API validate our form for us.
biblio_form_vtabs Creates the vertical tab form structure as used in the add/edit form
biblio_form_vtab_info Get info specific to each vertical tab
biblio_get_allowed_tags Gets a list of allowed HTML tags
biblio_get_contributor_by_name
biblio_get_contributor_options Get the options for category dropdown field in contributor add/edit form
biblio_get_db_fields Gets a list of machine-readable field names
biblio_get_fielded_pubtypes
biblio_get_map Get publication type mapping for a biblio import type
biblio_get_style Helper function to get either the user or system style
biblio_get_styles Gets a list of available bibliography format styles
biblio_get_title_url_info @todo add relevant documentation
biblio_hash Gets potential duplicate of a node
biblio_help Implements hook_help().
biblio_help_page Page callback for base/help in hook_menu().
biblio_hide_form_fields
biblio_init Implements hook_init().
biblio_load Load a biblio object from the database.
biblio_load_multiple Load biblio entities from the database.
biblio_load_old Implementation of hook_load().
biblio_locale Implementation of hook_locale().
biblio_locale_refresh_fields Refresh all translatable field strings.
biblio_locale_refresh_types Refresh all publication type strings.
biblio_menu Implements hook_menu().
biblio_menu_links Modify and add links in various menus
biblio_node_access Implements hook_node_access.
biblio_node_export_node_alter Removes the cid field from the contributors of a biblio node Fixes node export importing issue #826506
biblio_node_form_pre_render Callback function for pre-render in the node form
biblio_node_form_validate Implements hook_validate().
biblio_node_insert Implements hook_node_insert().
biblio_node_revision_delete Implements hook_node_revision_delete().
biblio_node_update Implements hook_node_update().
biblio_node_view Implements hook_node_view
biblio_page_edit Presents the biblio editing form, or redirects to delete confirmation.
biblio_page_title hook_menu() callback
biblio_page_view Displays a biblio; Hands data off to the Field API
biblio_permission Implements hook_permission().
biblio_query_node_access_alter Doesn't seem to do anything @todo remove if possible
biblio_recent_feed hook_menu callback function for $base/recent/rss.xml
biblio_remove_brace Removes brace from a string
biblio_remove_brace_callback Callback from preg_replace_callback
biblio_reset_map Resets the publication type mapping of an import type
biblio_save Save a biblio object to the database
biblio_save_map Inserts the publication type mapping to the database
biblio_set_map Saves a new publication type mapping to the database
biblio_term_path Implements hook_term_path().
biblio_theme Implements hook_theme().
biblio_tokens Implements hook_tokens().
biblio_token_info Implements hook_token_info().
biblio_types Gets all publication types that are set up by default.
biblio_update Implementation of hook_update().
biblio_user_presave Implements hook_user_presave
biblio_views_api Implements hook_views_api().
biblio_views_handlers Implements hook_views_handlers().
biblio_wrapper Helper function to get a wrapper object, built by the Entity API, to help ease the pain of getting/setting field data. Fields for entities can be get and set using the following syntax: @example // set biblio field data (in this case, the…
contributor_bundle_load
hook_taxonomy_vocabulary_delete Implements hook_taxonomy_vocabulary_delete.
menu_get_children Get the child elements of a menu item.
_biblio_citekey_print @todo create relevant documentation for this function
_biblio_filter_footnote_callback preg_replace_callback() callback function
_biblio_filter_inline_reference_prepare Prepare callback function for hook_filter_info()
_biblio_filter_inline_reference_process Process callback function for hook_filter_info()
_biblio_filter_inline_reference_tips Tips callback function for hook_filter_info()
_biblio_filter_reference_prepare Prepare callback function for hook_filter_info()
_biblio_filter_reference_process Process callback function for hook_filter_info()
_biblio_filter_reference_tips Tips callback for hook_filter_info().
_biblio_filter_replace_callback preg_replace_callback() callback function
_biblio_get_auth_type
_biblio_get_auth_types
_biblio_get_formatted_fields Gets a list of machine-readable field names from the biblio_fields db table
_biblio_inline_filter_replace_callback preg_replace_callback() callback function
_biblio_localize_fields Translate field titles and hints through the interface translation system, if the i18nstrings module is enabled.
_biblio_localize_type Translate a publication type through the interface translation system, if the i18nstrings module is enabled.
_biblio_numeric_year Gets the numeric year values associated with "In Press"/"Submitted
_biblio_prepare_submit Prepare a biblio for submit to database. Contains code common to insert and update.
_biblio_profile_access Callback for $base/user/[user] in hook_menu().
_biblio_text_year Gets the string values (in press/submitted) of the years 9999 and 9998
_biblio_truncate_long_fields Truncates field values that are longer than the specified field value length. This is particularly useful when importing biblio entities.

Constants

Namesort descending Description
BIBLIO_VERSION biblio.module for Drupal