<?php
// $Id: relevant_content_cck.module,v 1.1.4.8 2010/01/22 23:58:35 njt1982 Exp $

/**
 * @file
 * Optional extension to CCK (Content Construction Kit) to provide a read-only field which allows embedding of relevant content within any node type.
 */


/**
 * Implementation of hook_theme().
 */
function relevant_content_cck_theme() {
  $hooks['relevant_content_cck_formatter_general'] = array(
    'path' => drupal_get_path('module', 'relevant_content_cck') .'/theme',
    'file' => 'relevant_content_cck.theme.inc',
    'arguments' => array('element' => NULL, 'type' => 'default'),
  );

  $base = array(
    'path' => drupal_get_path('module', 'relevant_content_cck') .'/theme',
    'file' => 'relevant_content_cck.theme.inc',
    'arguments' => array('element' => NULL),
  );

  $hooks['relevant_content_cck_formatter_default'] = $base;
  $hooks['relevant_content_cck_formatter_plain']   = $base;
  $hooks['relevant_content_cck_formatter_teaser']  = $base;
  $hooks['relevant_content_cck_formatter_full']    = $base;

  if (module_exists('token')) {
    $hooks['relevant_content_cck_formatter_token_teaser']  = $base;
    $hooks['relevant_content_cck_formatter_token_full']    = $base;
  }
  return $hooks;
}


/**
 * Implementation of hook_field_info().
 */
function relevant_content_cck_field_info() {
  return array(
    'relevant_content' => array(
      'label' => t('Relevant Content'),
      'description' => t('A read only field for listing relevant nodes.'),
    ),
  );
}


/**
 * Implementation of hook_field_settings().
 */
function relevant_content_cck_field_settings($op, $field) {
  switch ($op) {
    case 'form' :
      $form = array();

      $form['multiple'] = $form['required'] = array('#type' => 'value');

      $form['relevant_nodetypes'] = array(
        '#type' => 'checkboxes',
        '#title' => t('Relevant Node Types'),
        '#default_value' => is_array($field['relevant_nodetypes']) ? $field['relevant_nodetypes'] : array(),
        '#options' => node_get_types('names'),
        '#required' => TRUE,
        '#description' => t('Select the node types you would like to include in this <em>Relevant Content Field</em>'),
      );

      $vocabs = array();
      foreach (taxonomy_get_vocabularies() as $vid => $voc) {
        $vocabs[$vid] = $voc->name;
      }

      // If the vocabularies array is empty, then we should prompt the user to create a vocabulary before they configure the field.
      if (empty($vocabs)) {
        // If they have permission to admin taxonomy then configure a link with appropriate destinations/redirections
        if (user_access('administer taxonomy')) {
          $link_options = array('query' => array('destination' => $_GET['q']));
          $link = l('Create a vocabulary now', 'admin/content/taxonomy/add/vocabulary', $link_options);
        }
        // Otherwise, display an error message asking them to get an admin to do it.
        else {
          $link = t('You do not have permission to create vocabularies. An administrator must create one for you.');
        }

        // Display the error message on a page on its own - we dont want the user to complete the form as the form itself is incomplete
        // TODO: Is this OK for usability?
        drupal_set_message(
          t('You must define at least one vocabulary before you can continue. !link.', array('!link' => $link)),
          'error'
        );
        print theme('page', '');
        exit;
      }
      $form['relevant_vocabs'] = array(
        '#type' => 'checkboxes',
        '#title' => t('Relevant Vocabularies'),
        '#default_value' => is_array($field['relevant_vocabs']) ? $field['relevant_vocabs'] : array(),
        '#options' => $vocabs,
        '#required' => TRUE,
        '#description' => t('Select the vocabularies you would like to include in this <em>Relevant Content Field</em>. Only terms in the selected vocabularies will be used to find relevant content.'),
      );

      $form['relevant_result_limit'] = array(
        '#type' => 'textfield',
        '#title' => t('Results Per Page'),
        '#description' => t('Relevant content to display per page. Must be more than 0'),
        '#default_value' => isset($field['relevant_result_limit']) ? $field['relevant_result_limit'] : 5,
        '#required' => TRUE,
      );


      $form['relevant_header'] = array(
        '#tree' => TRUE,
        '#title' => t('Header Text'),
        '#type' => 'fieldset',
        '#description' => t('Optionally include some text to appear above the list and below the label (if the label is enabled).'),
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
      );
      $form['relevant_header']['body'] = array(
        '#type' => 'textarea',
        '#default_value' => isset($field['relevant_header']['body']) ? $field['relevant_header']['body'] : '',
      );
      $form['relevant_header']['format'] = filter_form($field['relevant_header']['format'], NULL, array('relevant_header', 'format'));

      return $form;

    case 'validate' :
      if (preg_match('|[^0-9]|', $field['relevant_result_limit']) || $field['relevant_result_limit'] < 1) {
        form_set_error('relevant_result_limit', t('The result limit should be a whole and positive number'));
      }
      break;

    case 'save' :
      return array('relevant_nodetypes', 'relevant_vocabs', 'relevant_result_limit', 'relevant_header');
  }
}


/**
 * Implementation of hook_field().
 */
function relevant_content_cck_field($op, &$node, $field, &$items, $teaser, $page) {
  switch ($op) {
    case 'load':
      //Get the terms using the handy term wrapper in the parent module
      $terms = relevant_content_get_page_terms($node);

      // If there are no terms, return an empty item set - there will be nothing in common with this.
      if (empty($terms)) return array($field['field_name'] => array());

      // Grab the types & vocabs
      $types  = array_values(array_filter($field['relevant_nodetypes']));
      $vocabs = array_values(array_filter($field['relevant_vocabs']));

      // Filter the terms from the vocabs associated with this field.
      foreach ($terms as $tid => $term) {
        if (in_array($term->vid, $vocabs)) {
          $terms[$tid] = $tid;
        }
        else {
          unset($terms[$tid]);
        }
      }

      // If there are no terms *after* filtering, return an empty item set - there will be nothing in common with this.
      if (empty($terms)) return array($field['field_name'] => array());

      // Search for items and return the item set.
      $items = relevant_content_get_nodes($types, $terms, array($node->nid), $field['relevant_result_limit']);
      if (empty($items)) return array($field['field_name'] => array());

      // We need the test as the function "relevant_content_get_nodes" returns false on an empty set. Here we need just an array.
      return array($field['field_name'] => array('items' => $items, 'header' => $field['relevant_header']));

    case 'sanitize' :
      // Apply the filter to the body and store in 'safe'
      if (!empty($items['header']['body'])) {
        $items['header']['safe'] = check_markup($items['header']['body'], $items['header']['format'], FALSE);
      }
      break;
  }
}


/**
 * Implementation of hook_field_formatter_info().
 */
function relevant_content_cck_field_formatter_info() {
  if (module_exists('token')) {
    $optional = array(
      'token_teaser' => array('multiple values' => CONTENT_HANDLE_MODULE, 'field types' => array('relevant_content'), 'label' => t('Custom Tokens Teaser View')),
      'token_full'   => array('multiple values' => CONTENT_HANDLE_MODULE, 'field types' => array('relevant_content'), 'label' => t('Custom Tokens Full View')),
    );
  }
  else {
    $optional = array();
  }

  return array(
    'default' => array('multiple values' => CONTENT_HANDLE_MODULE, 'field types' => array('relevant_content'), 'label' => t('Node Title - Link')),
    'plain'   => array('multiple values' => CONTENT_HANDLE_MODULE, 'field types' => array('relevant_content'), 'label' => t('Node Title - Plain Text')),
    'teaser'  => array('multiple values' => CONTENT_HANDLE_MODULE, 'field types' => array('relevant_content'), 'label' => t('Node Teaser')),
    'full'    => array('multiple values' => CONTENT_HANDLE_MODULE, 'field types' => array('relevant_content'), 'label' => t('Node Full View')),
  ) + $optional;
}


/**
 * Implementation of hook_widget_info().
 */
function relevant_content_cck_widget_info() {
  return array(
    'relevant_content_list' => array(
      'label' => t('Relevant Nodes (Read Only)'),
      'field types' => array('relevant_content'),
      'multiple values' => CONTENT_HANDLE_MODULE,
      'callbacks' => array(
        'default value' => CONTENT_CALLBACK_NONE,
      ),
    ),
  );
}


/**
 * Implementation of hook_elements().
 */
function relevant_content_cck_elements() {
  return array(
    'relevant_content_list' => array(
      '#input' => FALSE,
    ),
  );
}


/**
 * Implementation of hook_widget().
 */
function relevant_content_cck_widget(&$form, &$form_state, $field, $items, $delta = 0) {
  return array();
}


/**
 * Implementation of hook_widget_settings().
 */
function relevant_content_cck_widget_settings($op, $widget) {
  // If token isn't installed then dont bother with the form below
  if (!module_exists('token')) return;

  switch ($op) {
    case 'form' :
      $form = array();


      $form['token_settings'] = array(
        '#type' => 'fieldset',
        '#title' => t('Token Output Settings'),
        '#collapsible' => TRUE,
        '#collapsed' => FALSE,
      );
      $form['token_settings']['token_teaser'] = array(
        '#type' => 'fieldset',
        '#title' => t('Tokens for teaser view formatter'),
        '#tree' => TRUE,
      );
      $form['token_settings']['token_teaser']['body'] = array(
        '#type' => 'textarea',
        '#description' => t('Tokens entered in here will be used for the optional token teaser formatter. This allows customized output.'),
        '#default_value' => $widget['token_teaser']['body'],
        '#rows' => 4,
      );
      $form['token_settings']['token_teaser']['format'] = filter_form($widget['token_teaser']['format'], NULL, array('token_teaser', 'format'));


      $form['token_settings']['token_full'] = array(
        '#type' => 'fieldset',
        '#title' => t('Tokens for full view formatter'),
        '#tree' => TRUE,
      );
      $form['token_settings']['token_full']['body'] = array(
        '#type' => 'textarea',
        '#description' => t('Tokens entered in here will be used for the optional token full formatter. This allows customized output.'),
        '#default_value' => $widget['token_full']['body'],
        '#rows' => 4,
      );
      $form['token_settings']['token_full']['format'] = filter_form($widget['token_full']['format'], NULL, array('token_full', 'format'));


      $form['token_settings']['token_help'] = array(
        '#type' => 'fieldset',
        '#title' => t('Token Help'),
        '#collapsed' => TRUE,
        '#collapsible' => TRUE,
      );
      $form['token_settings']['token_help']['view'] = array(
        '#type' => 'markup',
        '#value' => theme('token_help'),
      );
      return $form;

    case 'save' :
      return array('token_teaser', 'token_full');
  }
}




function relevant_content_cck_preprocess_content_field(&$variables) {
  // If the field is a relevant content field, we need to add more suggestion types (so we can easily theme all RC fields).
  // This is necessary so that we can override the default content-field template (which doesn't work correctly for us)
  if ($variables['field_type'] == 'relevant_content') {
    $suggestions = array(
      'content-field-relevant-content',
      'content-field-relevant-content-'. $variables['field_name'],
      'content-field-relevant-content-'. $variables['node']->type,
      'content-field-relevant-content-'. $variables['field_name'] .'-'. $variables['node']->type,
    );

    $variables['template_files'] = array_merge($variables['template_files'], $suggestions);

    $variables['field_items'] = $variables['items']['0']['view'];
    $variables['field_header'] = $variables['items']['header']['safe'];
  }
}

function relevant_content_cck_theme_registry_alter(&$theme_registry) {
  // This seems necessary to allow the theme registry to also search the RC folder for template files.
  $theme_registry['content_field']['theme paths'][] = drupal_get_path('module', 'relevant_content_cck') .'/theme';
}

function relevant_content_cck_content_is_empty() {
  return false;
}
