<?php
/* $Id: nodewords.module,v 1.32.2.19 2007/01/22 12:15:34 robrechtj Exp $ */

/**
 * @file
 * Assign META tags to nodes, vocabularies, terms and pages.
 */

/**
 * Implementation of hook_block().
 */
function nodewords_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
    case 'list':
      $blocks[0]['info'] = t('Meta tags');
      return $blocks;

    case 'view':
      switch ($delta) {
        case 0:
          $tags = nodewords_get();
          if (isset($tags['keywords'])) {
            $tags['keywords'] = str_replace(',', ', ', $tags['keywords']);
          }

          $block['subject'] = t('Meta tags');
          $block['content'] = theme('nodewords_content', $tags);
          break;
      }
      return $block;
  }
}

/**
 *  Implemenation of hook_help().
 */
function nodewords_help($section) {
  switch ($section) {
    case 'admin/modules#name':
      return t('meta tags');

    case 'admin/modules#description':
      return t('Allows users to add meta tags, eg keywords or description.');

    case 'admin/settings/nodewords':
      $cvs_id = '<em>$Id: nodewords.module,v 1.32.2.19 2007/01/22 12:15:34 robrechtj Exp $</em>';
      return $cvs_id;

    case 'admin/settings/nodewords/frontpage':
      return t('On this page you can enter the meta tags for the front page of your site.');
  }
}

/**
 * Implementation of hook_menu().
 */
function nodewords_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/nodewords',
      'callback' => 'nodewords_settings_page',
      'title' => t('meta tags'),
      'access' => user_access('administer meta tags'),
      'type' => MENU_NORMAL_ITEM,
    );
    $items[] = array(
      'path' => 'admin/settings/nodewords/global',
      'title' => t('settings'),
      'access' => user_access('administer meta tags'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -10,
    );
    $items[] = array(
      'path' => 'admin/settings/nodewords/frontpage',
      'callback' => 'nodewords_frontpage_settings_page',
      'title' => t('front page'),
      'access' => user_access('administer meta tags'),
      'type' => MENU_LOCAL_TASK,
    );
  }
  else {
    $tags = nodewords_get();
    foreach ($tags as $name => $content) {
      if (!empty($content)) {
        drupal_set_html_head('<meta name="'. $name .'" content="'. $content .'" />');
      }
    }
  }
  return $items;
}

/**
 * Implementation of hook_nodeapi().
 */
function nodewords_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
  switch ($op) {
    case 'delete':
      if (user_access('administer meta tags')) {
        _nodewords_delete('node', $node->nid);
      }
      break;

    case 'insert':
    case 'update':
      if (isset($node->nodewords) && user_access('administer meta tags')) {
        _nodewords_set('node', $node->nid, $node->nodewords);
      }
      break;

    case 'update index':
      $output = '<h2>' . $node->nodewords['keywords'] . '</h2>';
      $output .= '<h3>' . $node->nodewords['description'] . '</h3>';
      return $output;

    case 'load':
      $node->nodewords = _nodewords_load('node', $node->nid);
      break;
  }
}

/**
 * Implementation of hook_form_alter().
 */
function nodewords_form_alter($form_id, &$form) {
  if (!user_access('administer meta tags')) {
    return;
  }

  if (isset($form['type']) && $form_id == $form['type']['#value'] . '_node_form') {
    $type = 'node';
    $id = $form['nid']['#value'];
  } else if ($form_id == 'taxonomy_form_vocabulary') {
    $type = 'vocabulary';
    $id = $form['vid']['#value'];
    $form['submit']['#weight'] = 5;
    $form['delete']['#weight'] = 5;
  } else if ($form_id == 'taxonomy_form_term') {
    $type = 'term';
    $id = $form['tid']['#value'];
    $form['submit']['#weight'] = 5;
    $form['delete']['#weight'] = 5;
  } else if ($form_id == 'panels_edit_form') {
    $type = 'panels';
    $id = $form['did']['#value'];
    $form['submit']['#weight'] = 5;
    $form['#submit']['nodewords_panels_handler'] = array();
  } else if ($form_id == 'panels_delete_confirm') {
    $form['#submit']['nodewords_panels_handler'] = array();
  }

  if (isset($type)) {
    if (isset($id) && is_numeric($id)) {
      $tags = _nodewords_load($type, $id);
    } else {
      $tags = array();
    }
    $form['nodewords'] = _nodewords_form($type, $tags);
  }
}

/**
 * Implementation of hook_perm().
 */
function nodewords_perm() {
  return array('administer meta tags');
}

/**
 * Menu callback: global module settings.
 */
function nodewords_settings_page() {
  $settings = _nodewords_get_settings();

  $form['nodewords'] = array(
    '#tree' => TRUE,
  );

  $form['nodewords']['global'] = array(
    '#tree' => TRUE,
  );

  $form['nodewords']['global']['copyright'] = array(
    '#type' => 'textfield',
    '#title' => t('Global copyright'),
    '#default_value' => $settings['global']['copyright'],
    '#size' => 60,
    '#maxlength' => $settings['max_size'],
    '#description' => t('Enter a short copyright statement (one line) that will be used on all pages unless specifically set.'),
  );

  $form['nodewords']['global']['geourl'] = array(
    '#type' => 'textfield',
    '#title' => t('Global GeoURL'),
    '#default_value' => $settings['global']['geourl'],
    '#size' => 60,
    '#maxlength' => $settings['max_size'],
    '#description' => t('Enter a GeoURL (latitude, longitude) that will be used on all pages unless specifically set.'),
  );

  $form['nodewords']['global']['keywords'] = array(
    '#type' => 'textfield',
    '#title' => t('Global keywords'),
    '#default_value' => $settings['global']['keywords'],
    '#size' => 60,
    '#maxlength' => $settings['max_size'],
    '#description' => t('Enter a comma-seperated list of global keywords. These global keywords will be added after the page-specific keywords on all pages.'),
  );

  if (function_exists('taxonomy_get_vocabularies')) {
    $select = array();
    foreach (taxonomy_get_vocabularies() as $vocabulary) {
      $select[$vocabulary->vid] = check_plain($vocabulary->name);
    }
    if (count($select) > 0) {
      $form['nodewords']['keywords_vids'] = array(
        '#type' => 'select',
        '#title' => t('Auto-keywords vocabularies'),
        '#default_value' => $settings['keywords_vids'],
        '#options' => $select,
        '#description' => t('Select the vocabularies which contain terms you want to add to the keywords meta tag for nodes. The terms of these vocabularies are added before the global keywords but after the page-specific keywords.'),
        '#multiple' => TRUE,
      );
    }
  }

  $form['nodewords']['use_teaser'] = array(
    '#type' => 'checkbox',
    '#title' => t('Use the teaser of the page if the meta description is not set.'),
    '#default_value' => $settings['use_teaser'],
  );

  $form['nodewords']['global']['robots'] = array(
    '#type' => 'select',
    '#title' => t('Default robots meta tag'),
    '#options' => array(
        'index,follow' => 'ALL=INDEX,FOLLOW',
        'noindex,follow' => 'NOINDEX,FOLLOW',
        'index,nofollow' => 'INDEX,NOFOLLOW',
        'noindex,nofollow' => 'NONE=NOINDEX,NOFOLLOW',
    ),
    '#multiple' => FALSE,
    '#default_value' => $settings['global']['robots'],
    '#description' => t('The ROBOTS meta tag offers a simple mechanisme to indicate to web robots and crawlers wether the page should be indexed (INDEX or NOINDEX) and whether links on the page should be followed (FOLLOW or NOFOLLOW). Here you can enter the default robots meta tag to use for all pages. If unsure, select "ALL=INDEX,FOLLOW".'),
  );

  $form['nodewords']['max_size'] = array(
    '#type' => 'textfield',
    '#title' => t('Text length'),
    '#default_value' => $settings['max_size'],
    '#size' => 6,
    '#maxlength' => 6,
    '#description' => t('The maximum number of characters the content of a meta tag can contain.'),
  );

  $form['nodewords']['edit'] = array(
    '#type' => 'fieldset',
    '#title' => t('Tags to show on edit form'),
    '#tree' => TRUE,
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#description' => t('Select the meta tags you want to be able to edit on the edit page of nodes, terms and vocabularies.'),
  );
  $form['nodewords']['head'] = array(
    '#type' => 'fieldset',
    '#title' => t('Tags to output in html head'),
    '#tree' => TRUE,
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#description' => t('Select the meta tags you want to appear in the HEAD section of the HTML pages.'),
  );

  foreach (_nodewords_get_possible_tags() as $name) {
    foreach (array('edit', 'head') as $where) {
      $form['nodewords'][$where][$name] = array(
        '#type' => 'checkbox',
        '#title' => ucfirst($name), //TODO: is this UTF8 safe?
        '#default_value' => $settings[$where][$name],
      );
    }
  }

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
  $form['reset'] = array(
    '#type' => 'submit',
    '#value' => t('Reset to defaults'),
  );

  return drupal_get_form('nodewords_settings', $form);
}

/**
 * Submit of nodewords_settings form.
 */
function nodewords_settings_submit($form_id, $form_values) {
  switch ($_POST['op']) {
    case t('Reset to defaults'):
      variable_del('nodewords');
      break;
    case t('Save configuration'):
      variable_set('nodewords', $form_values['nodewords']);
      break;
  }
}

/**
 * Handling of edit/delete form for panels.
 */
function nodewords_panels_handler($form_id, $form_values) {
  switch ($_POST['op']) {
    case t('Save'):
      if (isset($form_values['nodewords']) && user_access('administer meta tags')) {
        _nodewords_set('panels', $form_values['did'], $form_values['nodewords']);
      }
      break;

    case t('Delete'):
      if ($form_values['confirm']) {
        _nodewords_delete('panels', $form_values['did']);
      }
      break;
  }
}

/**
 * Menu callback: front page settings.
 */
function nodewords_frontpage_settings_page() {
  $tags = _nodewords_load('page', '');

  $form = array();
  $form['nodewords'] = _nodewords_form('page', $tags);

  unset($form['nodewords']['#title']);
  unset($form['nodewords']['#type']);

  if (empty($form['nodewords'])) {
    return t('Currently no meta tags can be assigned to the front page because you have disabled all tags to show on the edit forms. <a href="%nodewords-settings-url" title="meta tags settings">Enable some meta tags to show on edit forms</a> first.', array('%nodewords-settings-url' => url('admin/settings/nodewords')));
  }

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );

  return drupal_get_form('nodewords_frontpage_settings', $form);
}

/**
 * Submit of nodewords_frontpage_settings form.
 */
function nodewords_frontpage_settings_submit($form_id, $form_values) {
  if ($_POST['op'] == t('Submit')) {
    _nodewords_set('page', '', $form_values['nodewords']);
    drupal_set_message(t('The meta tags for the front page has been saved.'));
  }
}

/**
 * Implementation of hook_taxonomy().
 */
function nodewords_taxonomy($op, $type, $object = NULL) {
  if ($type == 'term') {
    $id = $object['tid'];
  }
  elseif ($type == 'vocabulary') {
    $id = $object['vid'];
  }
  else {
    return;
  }

  switch ($op) {
    case 'delete':
      if (user_access('administer meta tags')) {
        _nodewords_delete($type, $id);
      }
      break;

    case 'insert':
    case 'update':
      if (isset($object['nodewords']) && user_access('administer meta tags')) {
        _nodewords_set($type, $id, $object['nodewords']);
      }
      break;
  }
}

/************************************************************
 * Theming functions.
 ************************************************************/

/**
 * Displays the defined meta tags $tags as content, eg in a
 * block or body.
 *
 * @param $tags
 *   Associative array of defined tags.
 *   No need to 'check_plain' on content.
 * @return
 *   Formatted HTML.
 */
function theme_nodewords_content($tags) {
  $output = "";
  foreach ($tags as $name => $content) {
    if (!empty($content)) {
      $class = "nodewords-$name";
      $output .= '<dt class="' . $class . '">' . $name . '</dt>';
      $output .= '<dd class="' . $class . '">' . $content . '</dd>';
    }
  }
  if (!empty($output)) {
    $output = '<dl class="nodewords">' . $output . '</dl>';
  }
  return $output;
}

/************************************************************
 * Nodewords API functions.
 ************************************************************/

/**
 * Get the defined meta tags for $type / $id.
 *
 * @param $type
 *   Realm of the object the meta tags are associated with.
 *   This is one of the following: 'node', 'page', 'term',
 *   'vocabulary'.
 * @param $ids
 *   Id (or path) of the object to get the meta tags from.
 *   This is one of the following:
 *   - 'node' => array of 'nid' of the node
 *   - 'page' => array of 'path' of the displayed page
 *   - 'term' => array of 'tid' of the term
 *   - 'vocabulary' => array of 'vid' of the vocabulary
 *   If $type or $ids is not set, an attempt will be made to
 *   get it from $_GET['q'].
 * @param $filtered
 *   If TRUE, only the meta tags that the user configured for
 *   output will be returned.
 *   If FALSE, all meta tags will be returned.
 * @return
 *   An associative array of the defined meta tags.
 */
function nodewords_get($type = NULL, $ids = NULL, $filtered = TRUE) {
  /* Autodetect if $type and/or $ids is not set */
  if ($type == NULL || $ids == NULL) {
    $result = _nodewords_detect_type_and_ids();
    $type = $result['type'];
    $ids = $result['ids'];
  }

  if (!is_array($ids)) {
    $ids = array($ids);
  }

  /* Load the values from the database */
  if (count($ids) == 1 && ($type != 'node' || node_access('view', node_load($ids[0])))) {
    $tags = _nodewords_load($type, $ids[0]);
  }
  else {
    $tags = array();
  }

  /* Pages with more than one node/term/vocabulary/... */
  if ($type == 'term') {
    if (isset($tags['keywords'])) {
      $terms = array($tags['keywords']);
    }
    else {
      $terms = array();
    }
    foreach ($ids as $id) {
      $term = taxonomy_get_term($id);
      if ($term) {
        $terms[] = $term->name;
      }
    }
    if (count($terms)) {
      $tags['keywords'] = implode(',', $terms);
    }
  }

  /* Prepare tags for output */
  $tags = _nodewords_prepare($type, $ids, $tags, $filtered);

  return $tags;
}

/************************************************************
 * Database access functions.
 ************************************************************/

/* Update or insert tags in the table */
function _nodewords_set($type, $id, $tags) {
  foreach ($tags as $name => $content) {
    if (empty($content)) {
      _nodewords_delete_tag($type, $id, $name);
    }
    else {
      _nodewords_update_tag($type, $id, $name, $content);
    }
  }
}

/* Delete tags from table */
function _nodewords_delete($type, $id) {
  return db_query("DELETE FROM {nodewords} WHERE type = '%s' AND id = '%s'", $type, $id);
}

/* Load tags from table */
function _nodewords_load($type, $id) {
  $tags = array();
  $result = db_query("SELECT * FROM {nodewords} WHERE type = '%s' AND id = '%s'", $type, $id);
  while ($row = db_fetch_object($result)) {
    $tags[$row->name] = $row->content;
  }
  return $tags;
}

/* Set one tag */
function _nodewords_update_tag($type, $id, $name, $content) {
  $result = db_query("SELECT * FROM {nodewords} WHERE type = '%s' AND id = '%s' AND name = '%s'", $type, $id, $name);
  if (db_num_rows($result) == 0) {
    db_query("INSERT INTO {nodewords} (type, id, name, content) VALUES ('%s', '%s', '%s', '%s')", $type, $id, $name, $content);
  }
  else {
    db_query("UPDATE {nodewords} SET content = '%s' WHERE type = '%s' AND id = '%s' AND name = '%s'", $content, $type, $id, $name);
  }
}

/* Delete one tag */
function _nodewords_delete_tag($type, $id, $name) {
  db_query("DELETE FROM {nodewords} WHERE type = '%s' and id = '%s' AND name = '%s'", $type, $id, $name);
}

/************************************************************
 * Helper functions - forms.
 ************************************************************/

/*
 * Create a form - returns a $form variable
 */
function _nodewords_form($type, $tags) {
  $settings = _nodewords_get_settings();
  $form = array();

  if ($settings['edit']['abstract']) {
    $form['abstract'] = array(
      '#type' => 'textfield',
      '#title' => t('Abstract'),
      '#default_value' => $tags['abstract'],
      '#size' => 60,
      '#maxlength' => $settings['max_size'],
      '#description' => t('Enter a short abstract for this page. Typically it is one sentence.'),
    );
  }

  if ($settings['edit']['copyright']) {
    $form['copyright'] = array(
      '#type' => 'textfield',
      '#title' => t('Copyright'),
      '#default_value' => $tags['copyright'],
      '#size' => 60,
      '#maxlength' => $settings['max_size'],
      '#description' => t('Enter a short copyright statement for this page.'),
    );
  }

  if ($settings['edit']['description']) {
    $form['description'] = array(
      '#type' => 'textarea',
      '#title' => t('Description'),
      '#default_value' => $tags['description'],
      '#cols' => 60,
      '#rows' => 6,
      '#description' => t('Enter a description for this page. Limit your description to about 20 words, with a maximum of %count characters. It should not contain any HTML tags or other formatting.', array('%count' => $settings['max_size'])) . ($settings['use_teaser'] ? ' ' . t('When you leave this field empty, the teaser will be used as description.') : ''),
    );
  }

  if ($settings['edit']['geourl']) {
    $form['geourl'] = array(
      '#type' => 'textfield',
      '#title' => t('GeoURL'),
      '#default_value' => $tags['geourl'],
      '#size' => 60,
      '#maxlength' => $settings['max_size'],
      '#description' => t('Enter a GeoURL (latitude, longitude) for this page.'),
    );
  }

  if ($settings['edit']['keywords']) {
    $form['keywords'] = array(
      '#type' => 'textfield',
      '#title' => t('Keywords'),
      '#default_value' => $tags['keywords'],
      '#size' => 60,
      '#maxlength' => $settings['max_size'],
      '#description' => t('Enter a comma seperated list of keywords for this page. Avoid duplication of words as this will lower your search engine ranking.'),
    );
  }

  if ($settings['edit']['robots']) {
    $options = array(
      '' => t('Use default setting'),
      'index,follow' => 'ALL=INDEX,FOLLOW',
      'noindex,follow' => 'NOINDEX,FOLLOW',
      'index,nofollow' => 'INDEX,NOFOLLOW',
      'noindex,nofollow' => 'NONE=NOINDEX,NOFOLLOW',
    );
    $form['robots'] = array(
      '#type' => 'select',
      '#title' => t('Robots meta tag'),
      '#options' => $options,
      '#multiple' => FALSE,
      '#default_value' => $tags['robots'],
      '#description' => t('The ROBOTS meta tag offers a simple mechanisme to indicate to web robots and crawlers wether the page should be indexed (INDEX or NOINDEX) and whether links on the page should be followed (FOLLOW or NOFOLLOW). The default setting is %default.', array('%default' => theme('placeholder', $options[$settings['global']['robots']]))),
    );
  }

  if (!empty($form)) {
    $form['#type'] = 'fieldset';
    $form['#title'] = t('Meta tags');
    $form['#tree'] = TRUE;
    $form['#collapsible'] = TRUE;
    $form['#collapsed'] = empty($tags);
  }

  return $form;
}

/************************************************************
 * Helper functions - settings.
 ************************************************************/

/*
 * Load default and user-defined settings. If $defaults => default settings are returned.
 */
function _nodewords_get_settings($defaults = FALSE) {
  static $settings = NULL;
  static $default_settings = array('use_teaser' => 1,
                                   'max_size' => 255,
                                   'keywords_vids' => array(),
                                   'global' => array('copyright' => '',
                                                     'geourl' => '',
                                                     'keywords' => '',
                                                     'robots' => 'index,follow'),
                                   'head' => array('abstract' => 1,
                                                   'copyright' => 1,
                                                   'description' => 1,
                                                   'geourl' => 1,
                                                   'keywords' => 1,
                                                   'robots' => 1),
                                   'edit' => array('abstract' => 0,
                                                   'copyright' => 0,
                                                   'description' => 1,
                                                   'geourl' => 0,
                                                   'keywords' => 1,
                                                   'robots' => 0));

  if ($defaults) {
    return $default_settings;
  }

  if ($settings == NULL) {
    $settings = variable_get('nodewords', array());

    foreach ($default_settings as $key => $value) {
      if (is_array($value)) {
        $settings[$key] = isset($settings[$key]) ? array_merge($value, $settings[$key]) : $value;
      }
      else if (!isset($settings[$key])) {
        $settings[$key] = $value;
      }
    }
  }

  return $settings;
}

/*
 * Return a list of possible output tags
 */
function _nodewords_get_possible_tags() {
  $defaults = _nodewords_get_settings(FALSE);
  return array_keys($defaults['head']);
}

/*
 * Return a list of viewable output tags
 */
function _nodewords_get_viewable_tags($where = 'head') {
  $settings = _nodewords_get_settings();
  $output = array();
  foreach ($settings[$where] as $name => $viewable) {
    if ($viewable) {
      $output[] = $name;
    }
  }
  return $output;
}

/************************************************************
 * Helper functions - other.
 ************************************************************/

/*
 * Prepare the tags so they are ready for output. This includes:
 * - setting default values if the tag is empty
 * - adding or altering some content (eg add global keywords)
 */
function _nodewords_prepare($type, $ids, $tags, $filtered = TRUE) {
  $settings = _nodewords_get_settings();

  /* Special handling of COPYRIGHT */
  if ((!isset($tags['copyright']) || $tags['copyright'] == '') && $settings['global']['copyright']) {
    $tags['copyright'] = $settings['global']['copyright'];
  }

  /* Special handling of DESCRIPTION */
  if ((!isset($tags['description']) || empty($tags['description'])) && $settings['use_teaser'] && count($ids) == 1) {
    switch ($type) {
      case 'node':
        $node = node_load($ids[0]);
        if ($node && node_access('view', $node)) {
          // HACK: see http://drupal.org/node/79315 (can't use php functions in
          // body of nodes).
          $cachable = filter_format_allowcache($node->format);
          if ($cachable) {
            // HACK: see http://drupal.org/node/89067 (teaser as description with
            // [inline] error).
            if (node_hook($node, 'view')) {
              node_invoke($node, 'view', TRUE);
            }
            else {
              $node = node_prepare($node, TRUE);
            }
            node_invoke_nodeapi($node, 'view', TRUE);
            $tags['description'] = $node->teaser;
          }
        }
        break;
      case 'term':
        // TODO: probably we have to do a db_rewrite_sql() query here so access is restricted
        $term = taxonomy_get_term($ids[0]);
        if ($term) {
          $tags['description'] = $term->description;
        }
        break;
      case 'vocabulary':
        // TODO: probably we have to do a db_rewrite_sql() query here so access is restricted
        $voc = taxonomy_get_vocabulary($ids[0]);
        if ($voc) {
          $tags['description'] = $voc->description;
        }
        break;
    }
  }

  /* Special handling of GEOURL */
  if ((!isset($tags['geourl']) || $tags['geourl'] == '') && $settings['global']['geourl'] && !($type == 'node' && variable_get('location_'. $type, 0) && module_exist('location'))) {
    $tags['geourl'] = $settings['global']['geourl'];
  }

  /* Special handling of KEYWORDS */
  if ($type == 'node' && function_exists('taxonomy_node_get_terms') && count($ids) == 1) {
    if (node_access('view', node_load($ids[0]))) {
      foreach (taxonomy_node_get_terms($ids[0]) as $term) {
        if (in_array($term->vid, $settings['keywords_vids'])) {
          $tags['keywords'] .= ($tags['keywords'] ? ',' : '') . $term->name;
        }
      }
    }
  }
  if ($settings['global']['keywords']) {
    $tags['keywords'] .= ($tags['keywords'] ? ',' : '') . $settings['global']['keywords'];
  }
  if ($tags['keywords']) {
    $tags['keywords'] = _nodewords_uniq($tags['keywords']);
  }

  /* Special handling of ROBOTS */
  if ((!isset($tags['robots']) || empty($tags['robots'])) && $settings['global']['robots']) {
    $tags['robots'] = $settings['global']['robots'];
  }

  /* Filter out tags the user has chosen not to see */
  if ($filtered) {
    $tags = _nodewords_filter_viewable($tags);
  }

  /* EXCEPTION - QUICK HACK - DANGER AHEAD - BRAIN SWITCH-OFF */
  if (isset($tags['geourl'])) {
    $tags['geourl'] = str_replace(array(' ', '	'), '', $tags['geourl']);
    $tags['geo.position'] = str_replace(',', ';', $tags['geourl']);
    $tags['ICBM'] = str_replace(';', ',', $tags['geourl']);
    // http://drupal.org/node/103399 : we are unable to drupal_get_title(),
    // so unfortunately, only node-pages can get a DC.title.
    if ($type == 'node' && count($ids) == 1) {
      $node = node_load($ids[0]);
      $tags['DC.title'] = $node->title;
    }
    else if ($type == 'panels' && count($ids) == 1) {
      $panel = db_fetch_object(db_query("SELECT * FROM {panels_info} WHERE did = %d", $ids[0]));
      if ($panel) {
        $tags['DC.title'] = $panel->title;
      }
    }
    unset($tags['geourl']);
  }
  
  /* Check 'content' of each tag */
  $tags = array_map(_nodewords_check_content, $tags);

  return $tags;
}

/*
 * Remove the meta tags from $tags that the user chose not to show.
 */
function _nodewords_filter_viewable($tags, $where = 'head') {
  $output = array();

  if (is_array($tags)) {
    $viewables = _nodewords_get_viewable_tags($where);

    foreach ($viewables as $name) {
      $output[$name] = $tags[$name];
    }
  }
  return $output;
}

/*
 * Remove any content from the $tag that is not allowed in a meta content attribute.
 */
function _nodewords_check_content($text) {
  $settings = _nodewords_get_settings();
  $size = $settings['max_size'];

  $text = strip_tags($text);
  $text = check_plain($text);

  $needles = array('&nbsp;', "\r", "\n");
  $text = str_replace($needles, ' ', $text);
  $text = trim($text);
  $text = preg_replace('/\s+/', ' ', $text);

  if ($size > 0 && drupal_strlen($text) > $size) {
    $text = truncate_utf8($text, $size);
    $length = strrpos($text, ' '); //TODO: is this UTF safe?
    if (!is_bool($length)) {
      $text = substr($text, 0, $length); //TODO: is this UTF safe?
    }
  }
  return $text;
}

/*
 * Uniq a string which is a seperated list to items, preserving the order in which they appeared.
 * Useful for the keywords meta-tag.
 */
function _nodewords_uniq($text, $max_items = 0, $seperator = ',') {
  if (empty($text)) {
    return $text;
  }

  $items = array_map(trim, explode($seperator, $text));
  $uniq_values = array();
  $uniq_lowers = array();

  foreach ($items as $item) {
    $lower = strtolower($item);
    if (!in_array($lower, $uniq_lowers)) {
      $uniq_values[] = $item;
      $uniq_lowers[] = $lower;
    }
  }

  if ($max_items > 0) {
    $uniq_values = array_slice($uniq_values, 0, $max_items);
  }

  return implode($seperator, $uniq_values);
}

/*
 * Try to guess the $type and $ids by looking at $_GET['q'].
 */
function _nodewords_detect_type_and_ids() {
  if ($_GET['q'] == variable_get('site_frontpage', 'node')) {
    return array('type' => 'page', 'ids' => array(''));
  }
  if (arg(0) == 'node' && is_numeric(arg(1))) {
    return array('type' => 'node', 'ids' => array(arg(1)));
  }
  if (arg(0) == 'taxonomy') {
    if (arg(1) == 'term' || arg(1) == 'vocabulary') {
      $ids = preg_split('![+, ]!', arg(2));
      if (count($ids)) {
        return array('type' => arg(1), 'ids' => $ids);
      }
    }
  }
  if (arg(0) == 'taxonomy_menu' && is_numeric(arg(1))) {
    return array('type' => 'term', 'ids' => arg(1));
  }
  if (module_exist('panels')) {
    if ($result = db_fetch_array(db_query("SELECT * FROM {panels_info} WHERE path = '%s'", $_GET['q']))) {
      return array('type' => 'panels', 'ids' => array($result['did']));
    }
  }
  return array('type' => 'none', 'ids' => array());
}
