You are here

viewfield.module in Viewfield 6.2

Core functions.

@todo Double-check potential performance issues mentioned in code comments.

File

viewfield.module
View source
<?php

/**
 * @file
 * Core functions.
 *
 * @todo Double-check potential performance issues mentioned in code comments.
 */

/**
 * Implementation of hook_field_info().
 */
function viewfield_field_info() {
  return array(
    'viewfield' => array(
      // Should be "View", but that would translate into "view" (show) for many
      // languages due to missing string translation contexts.
      'label' => t('Views'),
      'description' => t('Displays a selected view in a node.'),
      'callbacks' => array(
        'tables' => CONTENT_CALLBACK_NONE,
        'arguments' => CONTENT_CALLBACK_NONE,
      ),
    ),
  );
}

/**
 * Implementation of hook_field_settings().
 */
function viewfield_field_settings($op, $field) {
  switch ($op) {
    case 'form':
      $form['allowed_views'] = array(
        '#type' => 'checkboxes',
        '#title' => t('Allowed values'),
        '#default_value' => is_array($field['allowed_views']) ? $field['allowed_views'] : array(),
        '#options' => drupal_map_assoc(array_keys(views_get_all_views())),
        '#description' => t('Only selected views will be available for content authors. Leave empty to allow all.'),
      );
      return $form;
    case 'validate':
      if ($field['force_default'] && $field['multiple']) {
        form_set_error('multiple', t('Multiple views are not supported if force default is enabled.'));
      }
      break;
    case 'save':
      return array(
        'allowed_views',
      );
    case 'database columns':
      return array(
        // Views requires at least 96 chars for the view name and display, plus
        // we need 1 for our delimiter. Round up to a common value of 128.
        'vname' => array(
          'type' => 'varchar',
          'not null' => FALSE,
          'length' => 128,
        ),
        'vargs' => array(
          'type' => 'varchar',
          'not null' => FALSE,
          'length' => 255,
        ),
      );
  }
}

/**
 * Implementation of hook_field().
 */
function viewfield_field($op, &$node, $field, &$items, $teaser, $page) {
  switch ($op) {
    case 'presave':
      foreach ($items as $delta => $item) {
        if (empty($item['vname'])) {
          unset($items[$delta]);
        }
      }
      break;
    case 'sanitize':

      // Replace field values with widget defaults when force_default is set.
      if ($field['widget']['force_default']) {
        $items = $field['widget']['default_value'];
      }
      break;
  }
}

/**
 * Implementation of hook_content_is_empty().
 */
function viewfield_content_is_empty($item, $field) {
  return empty($item['vname']);
}

/**
 * Implementation of hook_field_formatter_info().
 */
function viewfield_field_formatter_info() {
  return array(
    'default' => array(
      'label' => t('Default'),
      'field types' => array(
        'viewfield',
      ),
    ),
  );
}

/**
 * Return a themed view avoiding viewfield recursion.
 */
function theme_viewfield_formatter_default($element) {

  // $_viewfield_stack keeps a record of the current node to prevent infinite
  // recursion during the view rendering process.
  global $_viewfield_stack;
  $node = $element['#node'];
  if (!empty($element['#item']['vname']) && !isset($_viewfield_stack[$node->nid])) {

    // Push id of current node unless it's a new node being previewed.
    if ($node->nid) {
      $_viewfield_stack[$node->nid] = $node->nid;
    }
    list($view_name, $display) = explode('|', $element['#item']['vname'], 2);
    $view_args = _viewfield_get_view_args($element['#item']['vargs'], $element['#node']);

    // Render the view like Views would do.
    // @see views_embed_view()
    $view = views_get_view($view_name);
    if ($view && $view
      ->access($display)) {

      // Override the view's path to the current path. Otherwise, exposed views
      // filters would submit to the front page.
      $view->override_path = $_GET['q'];
      $output = $view
        ->preview($display, $view_args);
    }

    // This node is "safe" again.
    if ($node->nid) {
      unset($_viewfield_stack[$node->nid]);
    }

    // Only return an actual view result to not break empty value behavior.
    if (isset($output)) {
      return $output;
    }
  }
}

/**
 * Implementation of hook_widget_info().
 */
function viewfield_widget_info() {
  return array(
    'viewfield_select' => array(
      'label' => 'Select List',
      'field types' => array(
        'viewfield',
      ),
      'multiple_values' => CONTENT_HANDLE_CORE,
      'callbacks' => array(
        'default value' => CONTENT_CALLBACK_DEFAULT,
      ),
    ),
  );
}

/**
 * Implementation of hook_widget_settings().
 */
function viewfield_widget_settings($op, $widget) {
  switch ($op) {
    case 'form':
      $form['force_default'] = array(
        '#type' => 'checkbox',
        '#title' => t('Always use default value'),
        '#default_value' => $widget['force_default'],
        '#description' => t('Hides this field in forms and forces the default value defined below.'),
      );
      return $form;
    case 'save':
      return array(
        'force_default',
      );
  }
}

/**
 * Implementation of hook_widget().
 */
function viewfield_widget(&$form, &$form_state, $field, $items, $delta = 0) {
  $element = array(
    '#type' => $field['widget']['type'],
    '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL,
  );
  return $element;
}

/**
 * Implementation of FAPI hook_elements().
 */
function viewfield_elements() {
  return array(
    'viewfield_select' => array(
      '#input' => TRUE,
      '#columns' => array(
        'vname',
        'vargs',
      ),
      '#delta' => 0,
      '#process' => array(
        'viewfield_select_process',
      ),
    ),
  );
}
function viewfield_select_process($element, $edit, $form_state, $form) {

  // This form is used for both the default value field in the admin as well as
  // the node edit form, so we have to make sure we show the default value field
  // always.
  $is_field_settings_form = !isset($form['#node']);
  $field = isset($form['#field_info'][$element['#field_name']]) ? $form['#field_info'][$element['#field_name']] : NULL;
  $element['#field'] = $field;

  // Display the form to let the user pick a view.
  $options = _viewfield_potential_references($field, $element['#delta']);
  $element['vname'] = array(
    '#type' => 'select',
    '#title' => $element['#title'],
    '#options' => $options,
    '#default_value' => isset($element['#default_value']['vname']) ? $element['#default_value']['vname'] : NULL,
    '#required' => $element['#required'],
    '#access' => $is_field_settings_form || !$field['widget']['force_default'],
    '#description' => $element['#description'],
  );

  // If there is only one option, only show arguments.
  if (count($options) == 1 && !$is_field_settings_form) {
    list($key, $label) = each($options);
    $element['vname']['#access'] = FALSE;
    $element['vname']['#default_value'] = $key;
  }
  $element['vargs'] = array(
    '#type' => 'textfield',
    '#title' => !isset($label) ? t('Arguments') : t('%field (@value) arguments', array(
      '%field' => $field['widget']['label'],
      '@value' => $label,
    )),
    '#default_value' => isset($element['#default_value']['vargs']) ? $element['#default_value']['vargs'] : NULL,
    '#access' => $is_field_settings_form || !$field['widget']['force_default'],
    '#description' => t('A comma separated list of arguments to pass to the selected view. Wrap arguments containing commas in double quotes. Replace double quotes in arguments with two double quotes.'),
  );
  $token_description = t('Available tokens: %nid for the id of the current node; %author for the node author; %viewer for the viewing user');
  if (module_exists('token')) {
    $element['vargs']['#description'] .= '<br />' . $token_description . '; ' . t('or any token from the placeholder token list.') . '<br />' . t('Note: Using placeholder tokens in combination with the %fields row style may negatively affect site performance.', array(
      '%fields' => t('Fields'),
    ));

    // Only show token help for first value or in field settings form.
    if ($element['#delta'] == 0 && ($is_field_settings_form || !$field['widget']['force_default'])) {
      $element['token_help'] = array(
        '#type' => 'fieldset',
        '#title' => t('Placeholder tokens'),
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
      );
      $element['token_help']['tokens'] = array(
        '#value' => theme('token_help', 'node'),
      );
    }
  }
  else {
    $element['vargs']['#description'] .= '<br />' . $token_description . '.';
  }
  return $element;
}

/**
 * Prepare a list of views for selection.
 */
function _viewfield_potential_references($field, $delta = 0) {
  $options = array();
  if (isset($field['allowed_views']) && is_array($field['allowed_views'])) {
    $field['allowed_views'] = array_filter($field['allowed_views']);
  }
  if (empty($field['allowed_views'])) {
    $field['allowed_views'] = array_keys(views_get_all_views());
  }
  foreach ($field['allowed_views'] as $view_name) {

    // Check for deleted views saved in allowed_views.
    if ($view = views_get_view($view_name)) {
      foreach ($view->display as $display) {
        $options[$view->name . '|' . $display->id] = $view->name . ' - ' . $display->display_title;
      }
    }
  }

  // Add a '0' option for non-required or subsequent values of multiple fields.
  if (empty($field['required']) || $field['multiple'] && $delta > 0) {
    array_unshift($options, '<' . t('none') . '>');
  }
  return $options;
}

/**
 * Implementation of hook_theme().
 */
function viewfield_theme() {
  return array(
    'viewfield_select' => array(
      'arguments' => array(
        'element' => NULL,
      ),
    ),
    'viewfield_formatter_default' => array(
      'arguments' => array(
        'element' => NULL,
      ),
    ),
  );
}

/**
 * Add CSS to force help text to wrap correctly on node edit form.
 */
function theme_viewfield_select($element) {
  if (!empty($element['#children'])) {
    $field = $element['#field'];
    if ($field['multiple'] && $element['#delta'] == 0) {

      // This is needed only for multiple viewfields.
      drupal_add_css(drupal_get_path('module', 'viewfield') . '/viewfield.css');
    }
    return '<div class="viewfield-select">' . $element['#children'] . '</div>';
  }
}

/**
 * Perform argument replacement
 */
function _viewfield_get_view_args($vargs, $node) {
  $args = array();

  // Prevent token_replace() from running this function a second time
  // before it completes the first time.
  static $tokens = TRUE;
  if ($tokens && !empty($vargs)) {
    $pos = 0;
    while ($pos < strlen($vargs)) {
      $found = FALSE;

      // If string starts with a quote, start after quote and get everything
      // before next quote.
      if (strpos($vargs, '"', $pos) === $pos) {
        if (($quote = strpos($vargs, '"', ++$pos)) !== FALSE) {

          // Skip pairs of quotes.
          while (!(($ql = strspn($vargs, '"', $quote)) & 1)) {
            $quote = strpos($vargs, '"', $quote + $ql);
          }
          $args[] = str_replace('""', '"', substr($vargs, $pos, $quote + $ql - $pos - 1));
          $pos = $quote + $ql + 1;
          $found = TRUE;
        }
      }
      elseif (($comma = strpos($vargs, ',', $pos)) !== FALSE) {

        // Otherwise, get everything before next comma.
        $args[] = substr($vargs, $pos, $comma - $pos);

        // Skip to after comma and repeat
        $pos = $comma + 1;
        $found = TRUE;
      }
      if (!$found) {
        $args[] = substr($vargs, $pos);
        $pos = strlen($vargs);
      }
    }

    // Try to replace tokens if $args might contain one.
    if (strpos($vargs, '[') !== FALSE && module_exists('token')) {
      $tokens = FALSE;

      // If the view field is being loaded as a "view field" of "view row",
      // instead of a simple "node field", the node object is not fully populated:
      // we need a full node to perform a correct replacement.
      $node = node_load($node->nid);
      foreach ($args as $key => $text) {
        $args[$key] = token_replace($text, 'node', $node);
      }
      $tokens = TRUE;
    }

    // For backwards compatibility, we scan for %nid, etc.
    foreach ($args as $key => $value) {
      $args[$key] = strtr($value, array(
        '%nid' => $node->nid,
        '%author' => isset($node->uid) ? $node->uid : (isset($node_values->uid) ? $node_values->uid : NULL),
        '%viewer' => $GLOBALS['user']->uid,
      ));
    }
  }
  return $args;
}

Functions

Namesort descending Description
theme_viewfield_formatter_default Return a themed view avoiding viewfield recursion.
theme_viewfield_select Add CSS to force help text to wrap correctly on node edit form.
viewfield_content_is_empty Implementation of hook_content_is_empty().
viewfield_elements Implementation of FAPI hook_elements().
viewfield_field Implementation of hook_field().
viewfield_field_formatter_info Implementation of hook_field_formatter_info().
viewfield_field_info Implementation of hook_field_info().
viewfield_field_settings Implementation of hook_field_settings().
viewfield_select_process
viewfield_theme Implementation of hook_theme().
viewfield_widget Implementation of hook_widget().
viewfield_widget_info Implementation of hook_widget_info().
viewfield_widget_settings Implementation of hook_widget_settings().
_viewfield_get_view_args Perform argument replacement
_viewfield_potential_references Prepare a list of views for selection.