You are here

linked_field.module in Linked Field 7

Same filename and directory in other branches
  1. 8 linked_field.module

Main file of Linked Field module.

File

linked_field.module
View source
<?php

/**
 * @file
 * Main file of Linked Field module.
 */

/**
 * Implements hook_help().
 */
function linked_field_help($path, $arg) {
  switch ($path) {
    case 'admin/reports/fields/linked-fields':
      return '<p>' . t('This list shows all fields which are linked via !linked-field module.', array(
        '!linked-field' => l('Linked Field', 'https://drupal.org/project/linked_field'),
      )) . '</p>';
  }
}

/**
 * Implements hook_permission().
 */
function linked_field_permission() {
  return array(
    'access linked field list' => array(
      'title' => t('Access linked field list'),
      'description' => user_access('access linked field list') ? t('Get an overview of <a href="@url">all linked fields</a>.', array(
        '@url' => url('admin/reports/fields/linked-fields'),
      )) : t('Get an overview of all linked fields.'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function linked_field_menu() {
  $items = array();
  $items['admin/reports/fields/linked-fields'] = array(
    'title' => 'Linked fields',
    'description' => 'Overview of all linked fields.',
    'page callback' => 'linked_field_field_list',
    'type' => MENU_LOCAL_TASK,
    'access callback' => 'user_access',
    'access arguments' => array(
      'access linked field list',
    ),
    'file' => 'linked_field.admin.inc',
    'weight' => 0,
  );
  return $items;
}

/**
 * Implements hook_token_info().
 *
 * Provides tokens for the current field item.
 */
function linked_field_token_info() {
  $info = array();
  $info['types']['field-item'] = array(
    'name' => t('Field item'),
    'description' => t('Tokens related to delta value of field.'),
    'needs-data' => 'field-item',
  );
  $info['tokens']['field-item']['delta'] = array(
    'name' => t('Field item delta'),
    'description' => t('The delta of this field item.'),
  );
  $info['tokens']['field-item']['raw'] = array(
    'name' => t('Raw item value'),
    'description' => t('The raw value of this field item.'),
  );
  return $info;
}

/**
 * Implements hook_tokens().
 *
 * Replace our tokens that we provide in linked_field_token_info().
 */
function linked_field_tokens($type, $tokens, array $data = array(), array $options = array()) {
  $replacements = array();
  if ($type == 'field-item') {
    foreach ($tokens as $name => $original) {
      $replacements[$original] = $data['field-item'][$name];
    }
  }
  return $replacements;
}

/**
 * Implements hook_menu_alter().
 */
function linked_field_menu_alter(&$items) {
  $items['admin/reports/fields/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
}

/**
 * Implements hook_field_formatter_info_alter().
 */
function linked_field_field_formatter_info_alter(&$infos) {
  foreach ($infos as &$info) {

    // Add a settings array if no settings were found.
    if (!isset($info['settings']) || !is_array($info['settings'])) {
      $info['settings'] = array();
    }
    $info['settings'] += array(
      'linked_field' => array(
        'linked' => 0,
        'destination' => '',
        'advanced' => array(
          'title' => '',
          'target' => '',
          'class' => '',
          'rel' => '',
          'text' => '',
        ),
      ),
    );
  }
}

/**
 * Implements hook_field_formatter_settings_summary_alter().
 */
function linked_field_field_formatter_settings_summary_alter(&$summary, $context) {
  $display = $context['instance']['display'][$context['view_mode']];
  $settings = $display['settings'];

  // Break when no linked field settings were found.
  if (empty($settings['linked_field']['linked'])) {

    // We have to put something in the summary so that we can ever
    // set Linked Field settings.
    $summary .= ' ';
    return;
  }

  // Normalize the settings.
  $linked = $settings['linked_field']['linked'];
  $destination = $settings['linked_field']['destination'];
  $title = $settings['linked_field']['advanced']['title'];
  $target = $settings['linked_field']['advanced']['target'];
  $class = $settings['linked_field']['advanced']['class'];
  $rel = $settings['linked_field']['advanced']['rel'];
  $text = $settings['linked_field']['advanced']['text'];
  $summary_items = array();
  if ($destination) {
    $summary_items[] = t('Destination: @destination', array(
      '@destination' => $destination,
    ));
  }
  if ($title) {
    $summary_items[] = t('Title: @title', array(
      '@title' => $title,
    ));
  }
  if ($target) {
    $summary_items[] = t('Target: <code>@target</code>', array(
      '@target' => $target,
    ));
  }
  if ($class) {
    $summary_items[] = t('Class: <code>@class</code>', array(
      '@class' => $class,
    ));
  }
  if ($rel) {
    $summary_items[] = t('Relationship: <code>@rel</code>', array(
      '@rel' => $rel,
    ));
  }
  if ($text) {
    $summary_items[] = t('Text: @text', array(
      '@text' => $text,
    ));
  }
  if ($linked) {
    $summary .= theme('item_list', array(
      'items' => $summary_items,
      'title' => 'Linked Field',
    ));
  }
  else {
    $summary .= ' ';
  }
}

/**
 * Implements hook_field_formatter_settings_form_alter().
 */
function linked_field_field_formatter_settings_form_alter(&$settings_form, $context) {
  $field = $context['field'];
  $entity_type = $context['instance']['entity_type'];
  $display = $context['instance']['display'][$context['view_mode']];
  $settings = $display['settings'];
  $settings_form['linked_field'] = array(
    '#type' => 'container',
    '#attributes' => array(
      'class' => array(
        'linked-field-linked-wrapper',
      ),
    ),
    '#attached' => array(
      'css' => array(
        drupal_get_path('module', 'linked_field') . '/css/linked-field.css',
      ),
    ),
  );
  $settings_form['linked_field']['linked'] = array(
    '#title' => t('Link this field'),
    '#type' => 'checkbox',
    '#default_value' => $settings['linked_field']['linked'],
  );
  $settings_form['linked_field']['destination'] = array(
    '#title' => t('Destination'),
    '#type' => 'textfield',
    '#default_value' => $settings['linked_field']['destination'],
    '#description' => t('Enter the link destination here. If you need a dynamic destination you can set it via the <a href="@alter-hook">alter hook</a>.', array(
      '@alter-hook' => 'http://cgit.drupalcode.org/linked_field/tree/linked_field.api.php',
    )),
    '#states' => array(
      'visible' => array(
        'input[name$="[settings][linked_field][linked]"]' => array(
          'checked' => TRUE,
        ),
      ),
    ),
  );
  $settings_form['linked_field']['advanced'] = array(
    '#title' => t('Advanced'),
    '#type' => 'fieldset',
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#states' => array(
      'visible' => array(
        'input[name$="[settings][linked_field][linked]"]' => array(
          'checked' => TRUE,
        ),
      ),
    ),
  );
  $settings_form['linked_field']['advanced']['title'] = array(
    '#title' => t('Title'),
    '#type' => 'textfield',
    '#default_value' => $settings['linked_field']['advanced']['title'],
    '#description' => t('This value will be used as <em>title</em> attribute.'),
  );
  $settings_form['linked_field']['advanced']['target'] = array(
    '#title' => t('Target'),
    '#type' => 'textfield',
    '#default_value' => $settings['linked_field']['advanced']['target'],
    '#description' => t('This value will be used as <em>target</em> attribute.'),
  );
  $settings_form['linked_field']['advanced']['class'] = array(
    '#title' => t('Class'),
    '#type' => 'textfield',
    '#default_value' => $settings['linked_field']['advanced']['class'],
    '#description' => t('This value will be used as <em>class</em> attribute.'),
  );
  $settings_form['linked_field']['advanced']['rel'] = array(
    '#title' => t('Relationship'),
    '#type' => 'textfield',
    '#default_value' => $settings['linked_field']['advanced']['rel'],
    '#description' => t('This value will be used as <em>rel</em> attribute.'),
  );
  $settings_form['linked_field']['advanced']['text'] = array(
    '#title' => t('Text'),
    '#type' => 'textfield',
    '#default_value' => $settings['linked_field']['advanced']['text'],
    '#description' => t('This value will be used as link text and overrides the actual field output.'),
  );
  $settings_form['linked_field']['token'] = array(
    '#states' => array(
      'visible' => array(
        'input[name$="[settings][linked_field][linked]"]' => array(
          'checked' => TRUE,
        ),
      ),
    ),
  );

  // Display a token browser and show a warning to the user if Token
  // module is not installed.
  if (module_exists('token')) {
    $settings_form['linked_field']['token'] += array(
      '#type' => 'container',
      '#theme' => 'token_tree',
      '#token_types' => 'all',
      '#dialog' => TRUE,
    );
  }
  else {
    $field_name = drupal_clean_css_identifier($field['field_name']);
    $settings_form['linked_field']['token']['#prefix'] = '<div class="form-item" id="edit-fields-' . $field_name . '-settings-edit-form-settings-linked-field-token">';
    $settings_form['linked_field']['token']['#prefix'] .= '<div style="display: inline-block;" class="messages warning">';
    $settings_form['linked_field']['token']['#suffix'] = '</div></div>';
    $settings_form['linked_field']['token']['#markup'] = t('Install the <a href="@url">Token</a> module to browse available tokens.', array(
      '@url' => url('https://www.drupal.org/project/token'),
    ));
  }
}

/**
 * Implements hook_field_attach_view_alter().
 */
function linked_field_field_attach_view_alter(&$output, $context) {
  foreach (element_children($output) as $field_name) {
    $element = $output[$field_name];
    if (empty($element['#entity_type']) || empty($element['#bundle'])) {
      continue;
    }
    $view_modes = field_view_mode_settings($element['#entity_type'], $element['#bundle']);

    // Check whether set view mode has custom settings.
    if (isset($view_modes[$context['view_mode']]) && !$view_modes[$context['view_mode']]['custom_settings']) {
      $context['view_mode'] = 'default';
    }
    $instance = field_info_instance($element['#entity_type'], $field_name, $element['#bundle']);

    // Check whether view mode exists in the field instance.
    if (isset($instance['display'][$context['view_mode']])) {
      $display = $instance['display'][$context['view_mode']];
    }
    elseif ($context['view_mode'] == '_custom' && is_array($context['display'])) {
      $display = $context['display'];
    }
    else {
      $display = $instance['display']['default'];
    }
    $settings = $display['settings'];

    // We need special behavior for Display Suite fields.
    if ($element['#field_type'] == 'ds') {
      $field_settings = ds_get_field_settings($element['#entity_type'], $element['#bundle'], $element['#view_mode']);
      $settings = isset($field_settings[$element['#field_name']]['formatter_settings']) ? $field_settings[$element['#field_name']]['formatter_settings'] : array();
    }

    // Continue to next if no Linked Field settings were found.
    if (empty($settings['linked_field']['linked'])) {
      continue;
    }

    // Some entities don't have a language so we need a fallback.
    $language = isset($element['#object']->language) ? $element['#object']->language : NULL;

    // We need special behavior for Display Suite fields.
    // @TODO: Where does this come from?
    if ($element['#field_type'] != 'ds') {
      $field_items = field_get_items($element['#entity_type'], $element['#object'], $field_name, $language);
    }
    foreach (element_children($element) as $delta) {

      // Parse the destination to get queries and fragments working.
      $destination_parsed = drupal_parse_url($settings['linked_field']['destination']);

      // Generate a correct link.
      $path = $destination_parsed['path'];
      unset($destination_parsed['path']);

      // Building an array which looks like the parameters you pass to l().
      $linked_field_settings = array(
        'linked' => $settings['linked_field']['linked'],
        'text' => $settings['linked_field']['advanced']['text'],
        'path' => $path,
        'options' => array(
          'attributes' => array(
            'title' => $settings['linked_field']['advanced']['title'],
            'target' => $settings['linked_field']['advanced']['target'],
            'class' => $settings['linked_field']['advanced']['class'],
            'rel' => $settings['linked_field']['advanced']['rel'],
          ),
        ) + $destination_parsed,
      );

      // Let other modules alter all the settings.
      drupal_alter('linked_field_settings', $linked_field_settings, $context);

      // If there is no destination or Linked Field is disabled continue.
      if (!$linked_field_settings['path'] || !$linked_field_settings['linked'] && $linked_field_settings['path']) {
        continue;
      }
      $replace_tokens = array(
        $element['#entity_type'] => $element['#object'],
      );

      // We need special behavior for Display Suite fields.
      // @TODO: Where does this come from?
      if (isset($field_items[$delta])) {

        // Get raw value of field.
        $field_raw_value = array_shift($field_items[$delta]);

        // Replace tokens and filter the value. The field tokens have to be
        // first in the array so they are processed first.
        $replace_tokens = array(
          'field-item' => array(
            'delta' => $delta,
            'raw' => $field_raw_value,
          ),
        ) + $replace_tokens;
      }
      $options = array();
      if ($context['language'] != LANGUAGE_NONE) {
        $languages = language_list();
        if (isset($languages[$context['language']])) {
          $options['language'] = $languages[$context['language']];
        }
      }

      /*
       * Replace all field tokens and then entity tokens. This allows field
       * tokens to be nested such as [comment:field-tags:[field:delta]:name].
       */
      foreach ($replace_tokens as $type => $data) {
        $replace_token = array(
          $type => $data,
        );
        if ($type == 'field-item') {

          // The tokens should not be cleared when replacing field item tokens.
          $options['clear'] = FALSE;
        }
        else {
          $options['clear'] = TRUE;
        }
        $linked_field_settings['path'] = token_replace($linked_field_settings['path'], $replace_token, $options);
        $linked_field_settings['options']['attributes']['title'] = $linked_field_settings['options']['attributes']['title'] ? filter_xss_admin(token_replace($linked_field_settings['options']['attributes']['title'], $replace_token, $options)) : '';
        $linked_field_settings['options']['attributes']['target'] = $linked_field_settings['options']['attributes']['target'] ? check_plain(token_replace($linked_field_settings['options']['attributes']['target'], $replace_token, $options)) : '';
        $linked_field_settings['options']['attributes']['class'] = $linked_field_settings['options']['attributes']['class'] ? check_plain(token_replace($linked_field_settings['options']['attributes']['class'], $replace_token, $options)) : '';
        $linked_field_settings['options']['attributes']['rel'] = $linked_field_settings['options']['attributes']['rel'] ? check_plain(token_replace($linked_field_settings['options']['attributes']['rel'], $replace_token, $options)) : '';

        // Would be better to have own set with allowed tags so that only
        // inline elements are allowed.
        $linked_field_settings['text'] = $linked_field_settings['text'] ? filter_xss_admin(token_replace($linked_field_settings['text'], $replace_token, $options)) : '';
      }

      // Continue to next field if destination is empty.
      if (!$linked_field_settings['path']) {
        continue;
      }

      // We need the destination as link attribute so let's move it.
      $linked_field_settings['options']['attributes']['href'] = url($linked_field_settings['path'], $linked_field_settings['options']);
      unset($linked_field_settings['path']);
      if (!$linked_field_settings['text']) {
        $rendered = drupal_render($element[$delta]);
      }
      else {
        $rendered = $linked_field_settings['text'];
      }

      // Convert HTML code to a DOMDocument object.
      $html_dom = filter_dom_load($rendered);

      // Getting the <body> element.
      $body = $html_dom
        ->getElementsByTagName('body');
      $nodes = $body
        ->item(0);

      // Recursively walk over the DOMDocument body and place the links.
      linked_field_link_field($nodes, $html_dom, $linked_field_settings['options']['attributes']);

      // Converting the DOMDocument object back to HTML code.
      $rendered = filter_dom_serialize($html_dom);
      $output[$field_name][$delta] = array(
        '#markup' => $rendered,
      );
    }
  }
}

/**
 * Recursive function for linking text parts and images in DOMNodes.
 *
 * @param DOMNode $node
 *   An object which gets investigated.
 * @param DOMDocument $dom
 *   An object which represents an entire HTML or XML document.
 * @param array $attributes
 *   An array containing element attributes.
 */
function linked_field_link_field($node, $dom, $attributes) {

  // Some elements need the <a> as wrapper.
  $wrapped_elements = array(
    'img',
    'picture',
  );
  if ($node
    ->hasChildNodes() && $node->nodeName != 'a') {
    $c = $node->childNodes->length;
    for ($i = $c; $i > 0; --$i) {
      $child = $node->childNodes
        ->item($i - 1);
      if (in_array($child->nodeName, $wrapped_elements)) {

        // Create new <a> element, set the href and append the wrapped element.
        $element = $dom
          ->createElement('a');

        // Adding the attributes.
        foreach ($attributes as $name => $value) {
          if ($value) {

            // Convert all HTML entities back to their applicable characters.
            $value = html_entity_decode($value, ENT_QUOTES);
            $element
              ->setAttribute($name, $value);
          }
        }
        $node
          ->replaceChild($element, $child);
        $element
          ->appendChild($child);
      }
      else {
        linked_field_link_field($child, $dom, $attributes);
        if ($child->nodeType == XML_TEXT_NODE) {
          $text = $child->textContent;
          if (strlen(trim($text))) {

            // Convert all applicable characters to HTML entities.
            $text = htmlentities($text, ENT_QUOTES, 'UTF-8');

            // Create new <a> element, set the text and the href attribute.
            $element = $dom
              ->createElement('a', $text);

            // Adding the attributes.
            foreach ($attributes as $name => $value) {
              if ($value) {

                // Convert all HTML entities back to their applicable characters.
                $value = html_entity_decode($value, ENT_QUOTES);
                $element
                  ->setAttribute($name, $value);
              }
            }

            // Replace the the original element with the new one.
            $node
              ->replaceChild($element, $child);
          }
        }
      }
    }
  }
}