<?php
// $Id: taxonomy_redirect.module,v 1.7.2.6 2008/10/30 06:30:51 agileware Exp $

define('FILTERED_HTML', 1);

/**
 * Implementation of hook_help().
 */
function taxonomy_redirect_help($path, $arg) { 
  switch ($path) {
    case 'admin/build/taxonomy_redirect':
      return t('On this form you may tell Drupal where taxonomy terms should link to. By default, modules handled by the taxonomy modules link to <b>taxonomy/term/!tid</b>; however, there are many instances where a user may want to override this behavior and provide custom content. You can create redirections for all terms of a vocabulary or you can create them for individual terms.  Indiviual term redirects take precedence over whole vocabulary redirects.  Variables available for the path are <b>!tid</b> (term id), <b>!name</b> (term name), <b>!parent_ids</b> (a path of parent term ids eg/ great_grandparent/grandparent/parent) and <b>!parent_names</b> (a path of parent terms by name).');
  }
}

/**
 * Implementation of hook_menu().
 */
function taxonomy_redirect_menu() {
  $items['admin/build/taxonomy_redirect'] = array(
    'title' => 'Taxonomy redirect',
    'description' => 'Override the default url paths for taxonomy terms.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('taxonomy_redirect_admin'),
    'access arguments' => array('administer taxonomy'),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

/**
 * Admin form
 */
function taxonomy_redirect_admin() {
  drupal_add_js(drupal_get_path('module','taxonomy_redirect').'/taxonomy_redirect.js');

  $form = array();

  // Display a list of redirects

  $result = db_query("SELECT * FROM {taxonomy_redirect}");

  $redirects = array();
  while ($data = db_fetch_object($result)) {
    $redirects[] = $data;
  }

  $form['redirects']['#tree'] = TRUE;

  $any_php = FALSE;

  foreach ($redirects as $i => $redirect) {

    $form['redirects'][$i]['#tree'] = TRUE;

    $vocab = taxonomy_vocabulary_load($redirect->vid);
    $form['redirects'][$i]['vocabulary'] = array(
      '#type' => 'markup',
      '#value' => $vocab->name,
    );
    $form['redirects'][$i]['vid'] = array(
      '#type' => 'hidden',
      '#value' => $redirect->vid,
    );

    if ($redirect->tid && $redirect->tid > 0) {
      $term = taxonomy_get_term($redirect->tid);
      $term_name = $term->name;
    }
    else {
      $term_name = '';
    }

    $form['redirects'][$i]['term'] = array(
      '#type' => 'markup',
      '#value' => $term_name,
    );
    $form['redirects'][$i]['tid'] = array(
      '#type' => 'hidden',
      '#value' => $redirect->tid,
    );

    $form['redirects'][$i]['path'] = array(
      '#type' => 'markup',
    );

    $phpcode = taxonomy_redirect_get_php_filter();
    if ($redirect->filter == $phpcode) {
      $form['redirects'][$i]['path']['#value'] = 'PHP Code';
      $any_php = TRUE;
    }
    else {
      $form['redirects'][$i]['path']['#value'] = $redirect->path;
    }

    $form['redirects'][$i]['path_case'] = array(
      '#type' => 'markup',
      '#value' => $redirect->path_case,
    );

    $form['redirects'][$i]['separator'] = array(
      '#type' => 'markup',
      '#value' => $redirect->separator_replace,
    );

    $text_array = array_filter(preg_split("/\n|\r/", $redirect->remove_text), 'taxonomy_redirect_filter_empty_lines');
    $remove_text = '';
    foreach ($text_array as $text) {
      $remove_text .= $remove_text ? ", '".$text."'" : "'".$text."'";
    }

    $form['redirects'][$i]['remove_text'] = array(
      '#type' => 'markup',
      '#value' => $remove_text,
    );

   // Value for the onclick attribute of the 'load into editor' link.
   $onclick = 'document.getElementById(\'edit-path\').value              = decodeURIComponent( \''.rawurlencode($redirect->path).'\');
               document.getElementById(\'edit-path-case\').value         = \''.$redirect->path_case.'\';
               document.getElementById(\'edit-filter\').value            = \''.$redirect->filter.'\';
               document.getElementById(\'edit-separator-replace\').value = decodeURIComponent( \''.rawurlencode($redirect->separator_replace).'\');
               document.getElementById(\'edit-remove-text\').value       = decodeURIComponent( \''.rawurlencode($redirect->remove_text).'\');
               return false;';

   $form['redirects'][$i]['load'] = array(
     '#type' => 'markup',
     '#value' => l('load into editor', '', array('attributes' =>  array('onclick' => $onclick))),
   );

    $form['redirects'][$i]['delete'] = array(
      '#type' => 'checkbox',
      '#return_value' => 1,
      '#default_value' => 0,
    );
  }

  $form['delete_message'] = array(
    '#type' => 'item',
    '#value'=> t("Redirects checked 'delete' will be deleted on submit."),
    '#prefix' => '<span style="color:red;font-weight:bold">',
    '#suffix' => '</span>',
  );

  if (!module_exists('php') && $any_php) {
    $form['php_warning'] = array(
      '#type' => 'item',
      '#value'=> t("*** Warning:  The PHP filter module is disabled.  Your PHP Code redirects will be ignored. ***"),
      '#prefix' => '<span style="color:red;font-weight:bold">',
      '#suffix' => '</span>',
    );
  }

  // Display a select / path combo for adding another term

  $vocabs = taxonomy_get_vocabularies();

  $vocab_options[0] = "- None selected -";

  foreach ($vocabs as $vocab) {
/*
    if(strpos($vocab->module, 'taxonomy') !== FALSE) {
      $vocab_options[$vocab->vid] = $vocab->name;
    }
*/
    $vocab_options[$vocab->vid] = $vocab->name;

    $vid = $vocab->vid;
    $term_select = taxonomy_form($vid);
    $term_select['#attributes'] = array(
      'style' => 'display:none',
      'onchange' => 'term(this.value);return false'
    );
    $term_select['#title'] = "";
    $term_select['#description'] = "";
    $term_select['#id'] = "term_select_$vid";
    $form["term_select_$vid"] = $term_select;
  }

  $form['add_redirect'] = array(
    '#type' => 'fieldset',
    '#title' => t('Add new redirect'),
    '#tree' => TRUE,
    '#id' => 'div_addnew',
    '#prefix' => '<b>',
    '#suffix' => '</b>',
  );

  $form['add_redirect']['vocab_select'] = array(
    '#type' => 'select',
    '#options' => $vocab_options,
    '#title' => 'Select vocabulary',
    '#name' => 'vocab_select',
    '#attributes' => array('onchange' => 'vocab(this.value);//return false'),
    '#suffix' => '<div id="div_termselect"></div>',
  );

  $form['term_id'] = array(
    '#type' => 'hidden',
    '#value' => 0,
    '#id' => 'term_id'  
  );

  $form['add_redirect']['filter'] = array(
    '#id' => 'edit-filter',
    '#type' => 'select',
    '#title' => t('Filter'),
    '#options' => _taxonomy_redirect_get_filters(),
    '#description' => t('Select an input format for the term path. If using PHP use opening and closing brackets.  *** NOTE: If the PHP Filter module is not enabled you cannot select the PHP Code option.'),
  );

  $form['add_redirect']['path'] = array(
    '#id' => 'edit-path',
    '#type' => 'textarea',
    '#title' => t('Path'),
    '#description' => t('Do not place a leading or trailing /.  Available variables:  !tid, !name, !parent_ids, !parent_names.  You can also enter PHP code (use enclosing PHP tags) to create the path if PHP Code is selected in the filter option above.  Just make your PHP code return the desired path.  Define the function taxonomy_redirect_custom_term_path($term, $path, $separator, $remove_text) to customise further.'),
    '#cols' => 60,
    '#rows' => 5,
  );

  $form['add_redirect']['path_case'] = array(
    '#id' => 'edit-path-case',
    '#type' => 'select',
    '#title' => t('Path Case'),
    '#description' => t('How to transform the case of the path.'),
    '#options' => array(
      'No transform' => 'No transform',
      'Uppercase' => 'Uppercase',
      'Lowercase' => 'Lowercase',
     ),
    '#default_value' => 'No transform',
  );

  $form['add_redirect']['separator_replace'] = array(
    '#id' => 'edit-separator-replace',
    '#type' => 'textfield',
    '#title' => t('Separator'),
    '#description' => t('Character used to separate words in titles. This will replace any spaces and + characters. Using a space or + character can cause unexpected results.  Leave empty if you don\'t want to replace these characters.'),
    '#maxlength' => 1,
    '#size' => 1,
  );

  $form['add_redirect']['remove_text'] = array(
    '#id' => 'edit-remove-text',
    '#type' => 'textarea',
    '#title' => t('Remove text'),
    '#description' => t('A list of text to be removed from the url.  Put each piece of text to be removed on a new line.  Text can be one or more characters.  This may be useful to remove punctuation.  This removal will be done before separators are replaced. (case sensitive)'),
    '#rows' => 4,
  );

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

  $form['#submit'] = array('taxonomy_redirect_admin_form_submit_handler');

  return $form;
}

/**
 * Function that returns the format value of the PHP Code filter or zero if it doesn't exist.
 */
function taxonomy_redirect_get_php_filter() {
  $phpfilter = 0;
  $filters = filter_formats();
  foreach ($filters as $filter) {
    if ($filter->name == 'PHP code') {
      $phpfilter = $filter->format;
    }
  }
  return $phpfilter;
}

/**
 * Implementation of hook_theme().
 */
function taxonomy_redirect_theme($existing, $type, $theme, $path) {
  return array(
    'taxonomy_redirect_admin' => array(
      'arguments' => array('$form' => NULL),
    ),
  );
}

/**
 * Theme the taxonomy_redirect_admin form.
 */
function theme_taxonomy_redirect_admin($form) {
  foreach ($form['redirects'] as $i => $redirect) {
    if (is_numeric($i)) {
      $rows[] = array(
        drupal_render($form['redirects'][$i]['vocabulary']),
        drupal_render($form['redirects'][$i]['term']),
        drupal_render($form['redirects'][$i]['path']),
        drupal_render($form['redirects'][$i]['path_case']),
        drupal_render($form['redirects'][$i]['separator']),
        drupal_render($form['redirects'][$i]['remove_text']),
        drupal_render($form['redirects'][$i]['load']),
        drupal_render($form['redirects'][$i]['delete']),
        drupal_render($form['redirects'][$i]['vid']),
        drupal_render($form['redirects'][$i]['tid']),
      );
    }
  }
  if (!$rows) {
    $rows[] = array(array('data' => t("There are currently no taxonomy redirect entries."), 'colspan' => 6));
  }

  $output .= theme('table', array(t('Vocabulary'), t('Term'), t('Path'), t('Path Case'), t('Separator'), t('Remove Text'), t('View in editor'), t('delete')), $rows);

  $output .= drupal_render($form);

  return $output;
}

/**
 * Implementation of hook_validate for taxonomy_redirect_admin form.
 */
function taxonomy_redirect_admin_validate($form, &$form_state) {

  if ($form_state['values']['add_redirect']['filter'] != FILTERED_HTML) {
    if (!filter_access($form_state['values']['add_redirect']['filter'])) {
      form_set_error('add_redirect][filter', t('You are not authorised to use this input format'));
    }

    $phpcode = taxonomy_redirect_get_php_filter();
    if ($form_state['values']['add_redirect']['filter'] == $phpcode) {
      $test = _taxonomy_redirect_exec_filter($form_state['values']['add_redirect']['path'], $form_state['values']['add_redirect']['filter']);
    }
  }

  $vid = $_POST['vocab_select'];

  $any_deletes = FALSE;
  if ($form_state['values']['redirects']) {
    foreach ($form_state['values']['redirects'] as $i => $row) {
      if ($row['delete']) {
        $any_deletes = TRUE;
      }
    }
  }

  if ($any_deletes == FALSE) {
    if (!$vid || $vid < 1) {
      form_set_error('add_redirect][vocab_select', t("Error: Please select a vocabulary."));
    }

    if (!$form_state['values']['add_redirect']['path']) {
      form_set_error('add_redirect][path', t("Error: Please enter a path."));
    }
  }
}

/**
 * Implementation of hook_submit for taxonomy_redirect_admin form.
 */
function taxonomy_redirect_admin_form_submit_handler($form, &$form_state) {

  // Selected term
  $tid = $_POST['term_id'];
  $vid = $_POST['vocab_select'];
  $vocab = taxonomy_vocabulary_load($vid);
  $original = $vocab->module;

  // If there is already a redirect on a vocab and the vocabulary table has been updated to say 'taxonomy_redirect'
  // then when creating a new redirect on the same vid 'taxonomy_redirect' would be brought across as the original module.
  // This code stops that from happening so the original module isn't lost.
  if ($original == 'taxonomy_redirect') {
    $result = db_query("SELECT * 
                        FROM {taxonomy_redirect}
                        WHERE vid = %d", $vid);
    $data = db_fetch_object($result);
    $original = $data->module;
    if ($original == 'taxonomy_redirect' || !$original) {
      $original = 'taxonomy';
    }
  }

  $path = $form_state['values']['add_redirect']['path'];
  $path_case = $form_state['values']['add_redirect']['path_case'];
  $separator = $form_state['values']['add_redirect']['separator_replace'];
  $remove_text = $form_state['values']['add_redirect']['remove_text'];
  $filter = (int)trim($form_state['values']['add_redirect']['filter']);

  if ($path != '' && $vid > 0) {
    $vocab->module = 'taxonomy_redirect';

    db_query("UPDATE {vocabulary} 
              SET module = 'taxonomy_redirect' 
              WHERE vid = %d", $vid);

    if ($tid > 0) {
      db_query("DELETE FROM {taxonomy_redirect} WHERE vid = %d AND tid = %d", $vid, $tid);
      db_query("INSERT INTO {taxonomy_redirect} (vid, tid, module, path, separator_replace, remove_text, filter, path_case) 
                VALUES (%d, %d, '%s', '%s', '%s', '%s', %d, '%s')", $vid, $tid, $original, $path, $separator, $remove_text, $filter, $path_case);
    }
    else {
      db_query("DELETE FROM {taxonomy_redirect} WHERE vid = %d AND tid is NULL", $vid);
      db_query("INSERT INTO {taxonomy_redirect} (vid, tid, module, path, separator_replace, remove_text, filter, path_case) 
                VALUES (%d, NULL, '%s', '%s', '%s', '%s', %d, '%s')", $vid, $original, $path, $separator, $remove_text, $filter, $path_case);
    }

    drupal_set_message("Saved redirect");
  }

  // Delete checked terms
  $count_deleted = 0;
  if ($form_state['values']['redirects']) {
    foreach($form_state['values']['redirects'] as $i => $row) {
      if ($row['delete']) {
        $vid = $row['vid'];
        $tid = $row['tid'];
        if ($tid) {
          db_query("DELETE FROM {taxonomy_redirect} 
                    WHERE vid = %d 
                    AND tid = %d", $vid, $tid);
        }
        else {
          db_query("DELETE FROM {taxonomy_redirect} 
                    WHERE vid = %d 
                    AND tid is null", $vid);
        }
        $count_deleted++;
      }
    }
    if ($count_deleted > 0) {
      drupal_set_message("Redirects deleted");
    }
  }

  $form_state['redirect'] = 'admin/build/taxonomy_redirect';
}

/**
 * Returns the available filters for the path field.
 */
function _taxonomy_redirect_get_filters($i = NULL) {
  $filters = array(
    FILTERED_HTML => t("Plain text"),
  );

  if (module_exists('php')) {
    $phpcode = taxonomy_redirect_get_php_filter();
    $filters[$phpcode] = 'PHP Code';
  }

  if (isset($i)) {
    return $filters[$i];
  }

  return $filters;
}

/**
 * Function for executing PHP code entered into the path field.
 * If PHP code is not selected the path is returned as is.
 */
function _taxonomy_redirect_exec_filter($text, $filter) {
  $phpcode = taxonomy_redirect_get_php_filter();

  switch($filter) {
    case $phpcode:
      return check_markup($text, $phpcode, FALSE);
    case FILTERED_HTML:
    default:
      return $text;
  }
}

/**
 * Implementation of hook_term_path() from the taxonomy module.
 */
function taxonomy_redirect_term_path($term) {

  // Get term data in case the term passed in is incomplete.
  $t = taxonomy_get_term($term->tid);
  $term->name = $t->name;
  $term->description = $t->description;
  $term->weight = $t->weight;

  $redirect = db_fetch_object(db_query("SELECT *
                                        FROM {taxonomy_redirect} 
                                        WHERE vid = %d 
                                        AND tid = %d", $term->vid, $term->tid));

  if (!$redirect || !$redirect->path) {
    $redirect = db_fetch_object(db_query("SELECT *
                                          FROM {taxonomy_redirect} 
                                          WHERE vid = %d 
                                          AND tid IS NULL", $term->vid));
  }

  if (!$redirect || !$redirect->path) {
    return 'taxonomy/term/' . $term->tid;
  }

  // If the php filter module is not enabled and this is a php filter path creation don't run it.
  if ($redirect->filter != FILTERED_HTML && !module_exists('php')) {
    return 'taxonomy/term/' . $term->tid;
  }

  $path = trim(_taxonomy_redirect_exec_filter($redirect->path, $redirect->filter));
  $separator = $redirect->separator_replace;
  $remove_text = $redirect->remove_text;
  $path_case = $redirect->path_case;

  if (function_exists('taxonomy_redirect_custom_term_path')) {
    return taxonomy_redirect_custom_term_path($term, $path, $separator, $remove_text, $path_case);
  }

  return taxonomy_redirect_default_term_path($term, $path, $separator, $remove_text, $path_case);
}

/**
 * Builds the default taxonomy_redirect term path.
 * Can be overridden by the creation of a taxonomy_redirect_custom_term_path function
 */
function taxonomy_redirect_default_term_path($term, $path, $separator = NULL, $remove_text = NULL, $path_case = 'No transform') {
  $parents = taxonomy_get_parents_all($term->tid);
  $parents = array_reverse($parents);
  // Remove the child term from the array
  array_pop($parents);
  $parent_path = '';
  foreach ($parents as $parent) {
    $parent_names = $parent_names ? $parent_names.'/'.$parent->name : $parent->name;
    $parent_ids = $parent_ids ? $parent_ids.'/'.$parent->tid : $parent->tid;
  }

  $path = t($path, array('!tid' => $term->tid, '!name' => $term->name, '!parent_names' => $parent_names, '!parent_ids' => $parent_ids));

  // Remove text if necessary
  $text = array_filter(preg_split("/\n|\r/", $remove_text), 'taxonomy_redirect_filter_empty_lines');
  if (count($text) != 0) {
      $path = str_replace($text, "", $path);
  }

  // Replace separators if necessary.
  if ($separator || $separator === '0') {
    $path = str_replace(array(' ', '+'), $separator, $path);
  }

  // Change case if necessary
  switch ($path_case) {
    case 'No transform':
      break;
    case 'Uppercase':
      $path = strtoupper($path);
      break;
    case 'Lowercase':
      $path = strtolower($path);
      break;
  }

  // Remove any multiple slashes - They may have been created by !parent_names or !parent_ids being empty.
  while (strpos($path, '//') !== FALSE) {
    $path = str_replace('//', '/', $path);
  }

  return t($path);
}

/**
 * Function that removes empty entries from the array containing strings for removal.
 */
function taxonomy_redirect_filter_empty_lines($var) {
  // 13 = carriage return & 0 = null
  if (ord($var) == 13 || ord($var) == 0) {
    return FALSE;
  }
  else {
    return TRUE;
  }

}

/**
 * Builds a custom taxonomy_redirect term path.
 * Use this function if you want to override the normal path creation functionality.
 */
//function taxonomy_redirect_custom_term_path($term, $path, $separator = NULL, $remove_text = NULL) {

//}
