<?php

/**
 * @file
 * Defines a domain reference field.
 */

/**
 * Implements hook_field_info().
 */
function domain_field_field_info() {
  return array(
    'domain_reference' => array(
      'label' => t('Domain reference'),
      'description' => t('This field stores a reference to a domain.'),
      'default_widget' => 'options_select',
      'default_formatter' => 'domain_reference_token',
      'property_type' => 'text',
      'property_callbacks' => array('domain_field_property_info_callback'),
    ),
  );
}

/**
 * Callback to define the property info of the domain reference field.
 *
 * @see domain_field_field_info()
 */
function domain_field_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
  $name = $field['field_name'];
  $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];

  $property['type'] = ($field['cardinality'] != 1) ? 'list<text>' : 'text';
  $property['getter callback'] = 'entity_metadata_field_property_get';
  $property['setter callback'] = 'entity_metadata_field_property_set';
  $property['options list'] = 'domain_field_get_allowed_values';

  unset($property['query callback']);
  unset($property['auto creation']);
}

/**
 * Implements hook_field_views_data_alter().
 */
function domain_field_field_views_data_alter(&$result, $field, $module) {
  if ($module == 'domain_field') {
    $field_name = $field['field_name'];
    foreach ($result as $table => $data) {
      $result[$table][$field_name . '_domain']['filter']['handler'] = 'views_handler_filter_in_operator';
      $result[$table][$field_name . '_domain']['filter']['options callback'] = 'domain_field_get_allowed_values';
    }
  }
}

/**
 * Implements hook_field_widget_info_alter().
 */
function domain_field_field_widget_info_alter(&$info) {
  $info['options_select']['field types'][] = 'domain_reference';
  $info['options_buttons']['field types'][] = 'domain_reference';
}

/**
 * Implements hook_field_is_empty().
 */
function domain_field_field_is_empty($item, $field) {
  if (!is_array($item) || empty($item['domain'])) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_field_validate().
 */
function domain_field_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  // Build an array of existing domain machine names.
  $machine_names = array();
  foreach ($items as $delta => $item) {
    if (!empty($item['domain'])) {
      $machine_names[] = $item['domain'];
    }
  }

  if (!empty($machine_names)) {
    if (array_diff($machine_names, domain_machine_names())) {
      $errors[$field['field_name']][$langcode][$delta][] = array(
        'error' => 'domain_reference_illegal_value',
        'message' => t('%name: illegal value.', array('%name' => $instance['label'])),
      );
    }
  }
}

/**
 * Implements hook_options_list().
 */
function domain_field_options_list($field, $instance, $entity_type, $entity) {
  $function = !empty($field['settings']['options_list_callback']) ? $field['settings']['options_list_callback'] : 'domain_field_get_allowed_values';
  return $function($field);
}

/**
 * Returns the set of valid domains for a domain reference field.
 */
function domain_field_get_allowed_values() {
  return domain_field_get_domain_options(array(
    'key' => 'machine_name', // Domain reference fields need machine names.
    'sanitize' => FALSE, // Field API takes care of sanitizing list options.
  ));
}

/**
 * Implements hook_field_formatter_info().
 */
function domain_field_field_formatter_info() {
  return array(
    'domain_reference_token' => array(
      'label' => t('Tokenized text or link'),
      'field types' => array('domain_reference'),
      'settings' => array(
        'text' => '[domain:name]',
        'link' => '[domain:path]',
      ),
    ),
    'domain_reference_link' => array(
      'label' => t('Link'),
      'field types' => array('domain_reference'),
    ),
    'domain_reference_plain' => array(
      'label' => t('Plain text'),
      'field types' => array('domain_reference'),
    ),
  );
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function domain_field_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $element = array();
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];

  switch ($display['type']) {
    case 'domain_reference_token':
      $element['text'] = array(
        '#type' => 'textfield',
        '#title' => t('Text to output.'),
        '#default_value' => $settings['text'],
        '#description' => t('This field accepts tokens.'),
        '#token_types' => array('domain'),
        '#required' => TRUE,
      );
      $element['link'] = array(
        '#type' => 'textfield',
        '#title' => t('Link destination'),
        '#description' => t('Leave blank to output the text only not as a link. This field accepts tokens.'),
        '#default_value' => $settings['link'],
        '#token_types' => array('domain'),
      );
      if (module_exists('token')) {
        $element['text']['#element_validate'] = array('token_element_validate');
        $element['link']['#element_validate'] = array('token_element_validate');
      }
      break;
  }

  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function domain_field_field_formatter_settings_summary($field, $instance, $view_mode) {
  $summary = array();
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];

  switch ($display['type']) {
    case 'domain_reference_token':
      $summary[] = t('Text: %text', array('%text' => trim($settings['text'])));
      if ($link = trim($settings['link'])) {
        $summary[] = t('Linked to %link', array('%link' => $link));
      }
      break;
  }

  return implode('<br />', $summary);
}

/**
 * Implements hook_field_formatter_prepare_view().
 */
function domain_field_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
  $machine_names = array();

  // Collect every possible term attached to any of the fieldable entities.
  foreach ($entities as $id => $entity) {
    foreach ($items[$id] as $item) {
      // Force the array key to prevent duplicates.
      $machine_names[$item['domain']] = $item['domain'];
    }
  }

  if ($machine_names) {
    $domains = domain_field_machine_name_load_multiple($machine_names, TRUE);

    // Iterate through the fieldable entities again to attach the loaded domain data.
    foreach ($entities as $id => $entity) {
      $rekey = FALSE;

      foreach ($items[$id] as $delta => $item) {
        // Check whether the domain field instance value could be loaded.
        if (isset($domains[$item['domain']])) {
          // Replace the instance value with the domain data.
          $items[$id][$delta]['domain'] = $domains[$item['domain']];
        }
        // Otherwise, unset the instance value, since the domain does not exist.
        else {
          unset($items[$id][$delta]);
          $rekey = TRUE;
        }
      }

      if ($rekey) {
        // Rekey the items array.
        $items[$id] = array_values($items[$id]);
      }
    }
  }
}

/**
 * Loads multiple domains from their machine names.
 *
 * @param array $machine_names
 *  The machine names of the domain records.
 * @param $full
 *  Boolean indicator to run hook_domain_load() or not.
 *
 * @return array
 *  An array of Domain arrays.
 */
function domain_field_machine_name_load_multiple(array $machine_names, $full = FALSE) {
  $domains = db_query('SELECT domain_id, subdomain, sitename, scheme, valid, weight, is_default, machine_name FROM {domain} WHERE machine_name IN (:machine_names)', array(':machine_names' => $machine_names))->fetchAllAssoc('machine_name', PDO::FETCH_ASSOC);
  if ($full && !empty($domains)) {
    foreach ($domains as $key => $domain) {
      $domains[$key] = domain_api($domain, TRUE);
    }
  }
  return $domains;
}

/**
 * Implements hook_field_formatter_view().
 */
function domain_field_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  $settings = $display['settings'];

  switch ($display['type']) {
    case 'domain_reference_token':
      foreach ($items as $delta => $item) {
        // Build the token data and options.
        $token_data = array(
          'domain' => $item['domain'],
        );
        $token_options = array(
          // Get unsanitized tokens since we are calling check_plain later.
          'santize' => FALSE,
          'clear' => TRUE,
        );

        $text = token_replace(trim($settings['text']), $token_data, $token_options);
        if ($link = token_replace(trim($settings['link']), $token_data, $token_options)) {
          $element[$delta] = array(
            '#type' => 'link',
            '#title' => $text,
            '#href' => $link,
          );
        }
        else {
          $element[$delta] = array('#markup' => check_plain($text));
        }
      }
      break;

    case 'domain_reference_link':
      foreach ($items as $delta => $item) {
        $domain = $item['domain'];
        $element[$delta] = array(
          '#type' => 'link',
          '#title' => $domain['sitename'],
          '#href' => $domain['path'],
        );
      }
      break;

    case 'domain_reference_plain':
      foreach ($items as $delta => $item) {
        $element[$delta] = array(
          '#markup' => check_plain($item['domain']['sitename']),
        );
      }
      break;
  }

  return $element;
}

/**
 * Provides domains as an array of options that can be used in FAPI #options.
 *
 * Note that when this array is used for checkboxes, the array must be run
 * through $options = array_map($options, 'check_plain') to sanitize the
 * domain names.
 */
function domain_field_get_domain_options(array $options = array()) {
  $options += array(
    'key' => 'domain_id',
    'value' => 'sitename',
    'sanitize' => TRUE,
    'valid' => TRUE,
  );

  $return = array();
  foreach (domain_domains() as $domain) {
    // The domain must be valid.
    if (!empty($options['valid']) && empty($domain['valid']) && !user_access('access inactive domains')) {
      continue;
    }

    // Add the domain to the list of available options.
    $return[$domain[$options['key']]] = $domain[$options['value']];
  }

  if (!empty($options['sanitize'])) {
    $return = array_map('check_plain', $return);
  }

  return $return;
}
