You are here

entityreference_prepopulate.module in Entityreference prepopulate 7

Prepopulate entity reference values from URL.

File

entityreference_prepopulate.module
View source
<?php

/**
 * @file
 * Prepopulate entity reference values from URL.
 */

/**
 * Implements hook_ctools_plugin_directory().
 */
function entityreference_prepopulate_ctools_plugin_directory($module, $plugin) {
  if ($module == 'entityreference' || $module == 'ctools') {
    return 'plugins/' . $plugin;
  }
}

/**
 * Implements hook_theme().
 */
function entityreference_prepopulate_theme() {
  return array(
    'entityreference_prepopulate_providers_table' => array(
      'render element' => 'form',
    ),
  );
}

/**
 * Implements hook_field_create_instance().
 *
 * Add "default value function" setting to the field instance.
 * We have to do it from this hook, as we don't have another chance of setting
 * this option via the hook_field_info().
 */
function entityreference_prepopulate_field_create_instance($instance) {
  if (empty($instance['settings']['behaviors']['prepopulate']['status'])) {
    return;
  }
  $instance['default_value_function'] = 'entityreference_prepopulate_field_default_value';
  field_update_instance($instance);
}

/**
 * Implements hook_field_update_instance().
 */
function entityreference_prepopulate_field_update_instance($instance, $prior_instance) {
  if (empty($instance['settings']['behaviors']['prepopulate'])) {
    return;
  }
  if (isset($prior_instance['settings']['behaviors']['prepopulate']['status']) && $instance['settings']['behaviors']['prepopulate']['status'] == $prior_instance['settings']['behaviors']['prepopulate']['status']) {

    // Nothing changed.
    return;
  }
  $instance['default_value_function'] = !empty($instance['settings']['behaviors']['prepopulate']['status']) ? 'entityreference_prepopulate_field_default_value' : '';
  field_update_instance($instance);
}

/**
 * Implements hook_field_attach_form().
 */
function entityreference_prepopulate_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
  list($id, , $bundle) = entity_extract_ids($entity_type, $entity);

  // Check if there is a field that needs to be prepopulated attached to the
  // given entity.
  $found = FALSE;
  foreach (field_info_instances($entity_type, $bundle) as $instance) {
    if (!empty($instance['settings']['behaviors']['prepopulate']['status'])) {
      $found = TRUE;
      break;
    }
  }
  if (!$found) {
    return;
  }
  foreach (element_children($form_state['field']) as $field_name) {
    foreach ($form_state['field'][$field_name] as $lang => $value) {
      $instance = $value['instance'];
      if (empty($instance['settings']['behaviors']['prepopulate']['status'])) {
        continue;
      }
      $settings = $instance['settings']['behaviors']['prepopulate'];
      $field = $value['field'];

      // Store prepopulated values in the form state to make them persistent,
      // in case the form is rebuilt by AJAX requests.
      $field_name = $field['field_name'];
      if ($ids = entityreference_prepopulate_get_values($field, $instance)) {
        $form_state['entityreference_prepopulate'][$instance['entity_type']][$instance['bundle']][$field_name] = $ids;
      }
      if (!empty($settings['skip_perm']) && user_access($settings['skip_perm']) || $id && empty($settings['action_on_edit'])) {

        // User has access to skip the action, or the entity is already
        // saved, but "Apply action on edit", is disabled.
        continue;
      }
      if ($ids || $id && !empty($settings['action_on_edit'])) {

        // New entity with prepopulate values, or an existing entity,
        // we might need to disable/ hide the group-audience field.
        if ($settings['action'] == 'disable') {
          $form[$field_name][$lang]['#disabled'] = TRUE;
        }
        elseif ($settings['action'] == 'hide' && !$id) {

          // For new entities we don't hide the field via hook_field_access(),
          // as the default value won't be set. We use hook_field_access() only
          // on existing ones.
          $form[$field_name]['#access'] = FALSE;
        }
      }
      elseif (in_array($settings['fallback'], array(
        'form_error',
        'redirect',
        'hide',
      ))) {
        $message = t('Field @label must be populated via URL.', array(
          '@label' => $instance['label'],
        ));
        if ($settings['fallback'] == 'form_error') {
          form_error($form, $message);
        }
        elseif ($settings['fallback'] == 'redirect') {
          drupal_set_message($message, 'notice');
          drupal_goto();
        }
        elseif ($settings['fallback'] == 'hide') {
          $form[$field_name]['#access'] = FALSE;
        }
      }
    }
  }
}

/**
 * Implements hook_field_access().
 */
function entityreference_prepopulate_field_access($op, $field, $entity_type, $entity, $account) {
  if ($op != 'edit' || $field['type'] != 'entityreference') {
    return;
  }
  if (empty($entity)) {

    // $entity might be NULL, so return early.
    // @see field_access().
    return;
  }
  list($id, , $bundle) = entity_extract_ids($entity_type, $entity);
  $instance = field_info_instance($entity_type, $field['field_name'], $bundle);
  if (empty($instance['settings']['behaviors']['prepopulate']['status'])) {
    return;
  }
  $settings = $instance['settings']['behaviors']['prepopulate'];
  if (!empty($settings['skip_perm']) && user_access($settings['skip_perm'])) {
    return;
  }
  if ($id && empty($entity->is_new)) {

    // The field need to be hidden when editing the entity.
    return $settings['action'] == 'hide' && !empty($settings['action_on_edit']) ? FALSE : NULL;
  }
  if ($settings['action'] == 'hide') {

    // If entity is already saved and not just inserted, deny access, otherwise
    // ignore.
    return $id && empty($entity->is_new) ? FALSE : NULL;
  }
  $ids = entityreference_prepopulate_get_values($field, $instance);
  if (!$ids && $settings['fallback'] == 'hide') {
    return FALSE;
  }
}

/**
 * Field default value callback.
 *
 * Set the default from the URL context. This works even if the widget is
 * not shown, e.g. due to restricted field access.
 *
 * @todo Check field cardinality.
 */
function entityreference_prepopulate_field_default_value($entity_type, $entity, $field, $instance, $langcode) {
  $items = array();
  if ($ids = entityreference_prepopulate_get_values($field, $instance)) {
    $items = array();
    foreach ($ids as $id) {
      $items[] = array(
        'target_id' => $id,
      );
    }
  }
  return $items;
}

/**
 * Wrapper function to get context (e.g. from URL or OG-context).
 *
 * @param $entity_type
 *   The entity type the entity.
 * @param $entity
 *   The entity object that is being checked.
 * @param $field
 *   The field info array.
 * @param $instance
 *   The instance info array.
 * @param $validate
 *   Determine if access validation should be performed. Defaults to TRUE.
 *
 * @return
 *   Array of IDs a user may view.
 */
function entityreference_prepopulate_get_values($field, $instance, $validate = TRUE) {
  if (!$instance['settings']['behaviors']['prepopulate']['status']) {

    // Do nothing when prepopulate is disabled for this field.
    return;
  }
  $field_name = $field['field_name'];
  $cache =& drupal_static(__FUNCTION__, array());
  $identifier = array(
    $instance['entity_type'],
    $instance['bundle'],
    $field_name,
    $validate,
  );
  $is_audience_field = module_exists('og') && og_is_group_audience_field($field_name);

  // Add field mode to ID if the audience field has one.
  if ($is_audience_field && !empty($instance['field_mode'])) {
    $identifier[] = $instance['field_mode'];
  }
  $identifier = implode(':', $identifier);
  if (isset($cache[$identifier])) {
    return $cache[$identifier];
  }
  if ($is_audience_field && empty($instance['field_mode'])) {

    // Group audience field, but no field-mode provided.
    // So we iterate over the "default" and possibly "admin" field-modes,
    // and return those values together.
    $ids = array();
    $field_modes = !user_access('administer group') ? array(
      'default',
    ) : array(
      'default',
      'admin',
    );
    foreach ($field_modes as $field_mode) {
      $instance['field_mode'] = $field_mode;
      if ($og_ids = entityreference_prepopulate_get_values($field, $instance)) {
        $ids = array_merge($ids, $og_ids);
      }
    }

    // Cache and return the values.
    return $cache[$identifier] = $ids;
  }
  $cache[$identifier] = $ids = array();

  // Check if we have cached values.
  if (!($ids = entityreference_prepopulate_get_values_from_cache($field, $instance))) {

    // Get the providers.
    $enabled_providers = !empty($instance['settings']['behaviors']['prepopulate']['providers']) ? array_filter($instance['settings']['behaviors']['prepopulate']['providers']) : array();
    if (!$enabled_providers) {

      // If the instance isn't updated yet, we assume the URL provider is enabled.
      $enabled_providers += array(
        'url' => TRUE,
      );
    }

    // For backwards compatibility with version 1.4, we check the "og_context"
    // property.
    if (!empty($instance['settings']['behaviors']['prepopulate']['og_context'])) {
      $enabled_providers += array(
        'og_context' => TRUE,
      );
    }
    $providers = entityreference_prepopulate_providers_info();
    foreach (array_keys($enabled_providers) as $name) {
      if (empty($providers[$name]) || !empty($providers[$name]['disabled'])) {

        // Provider doesn't exist any more or is disabled.
        continue;
      }
      $provider = $providers[$name];
      $function = $provider['callback'];
      if ($ids = $function($field, $instance)) {

        // We found values, so we can break.
        break;
      }
    }
  }
  if (!$ids || !$validate) {

    // No IDs found, or no validation is needed.
    $cache[$identifier] = $ids;
    return $ids;
  }
  $handler = entityreference_get_selection_handler($field, $instance);
  if (!($valid_ids = $handler
    ->validateReferencableEntities($ids))) {
    $cache[$identifier] = FALSE;
    return;
  }

  // Only include valid ids.
  $ids = array_intersect($ids, $valid_ids);

  // Check access to the provided entities.
  $target_type = $field['settings']['target_type'];
  entity_load($target_type, $ids);
  foreach ($ids as $delta => $id) {
    $entity = entity_load_single($target_type, $id);
    if (!$entity || !entity_access('view', $target_type, $entity)) {
      unset($ids[$delta]);
    }
  }
  $cache[$identifier] = $ids;
  return $ids;
}

/**
 * Get the values from the cached form.
 *
 * @param $field
 *   The field info array.
 * @param $instance
 *   The instance info array.
 *
 * @see
 *   entityreference_prepopulate_get_values()
 */
function entityreference_prepopulate_get_values_from_cache($field, $instance) {

  // Try to get the form out of cache.
  if (!($form_build_id = isset($_GET['form_build_id']) ? $_GET['form_build_id'] : isset($_POST['form_build_id']) ? $_POST['form_build_id'] : NULL)) {
    return;
  }
  $field_name = $field['field_name'];
  $form_state = array();
  form_get_cache($form_build_id, $form_state);

  // If successful, get the value from the form_state.
  return isset($form_state['entityreference_prepopulate'][$instance['entity_type']][$instance['bundle']][$field_name]) ? $form_state['entityreference_prepopulate'][$instance['entity_type']][$instance['bundle']][$field_name] : FALSE;
}

/**
 * Get values for prepopulating fields via URL.
 *
 * @param $field
 *   The field info array.
 * @param $instance
 *   The instance info array.
 *
 * @see
 *   entityreference_prepopulate_get_values()
 */
function entityreference_prepopulate_get_values_from_url($field, $instance) {
  $settings = $instance['settings']['behaviors']['prepopulate'];
  $field_identifier = empty($settings['identifier']) ? $field['field_name'] : $settings['identifier'];
  if (!empty($_GET[$field_identifier]) && is_string($_GET[$field_identifier])) {
    return explode(',', $_GET[$field_identifier]);
  }
}

/**
 * Get values for prepopulating fields OG-context.
 *
 * @param $field
 *   The field info array.
 * @param $instance
 *   The instance info array.
 *
 * @see
 *   entityreference_prepopulate_get_values()
 */
function entityreference_prepopulate_get_values_from_og_context($field, $instance) {
  $field_name = $field['field_name'];
  if (!module_exists('og_context') || !($og_context = og_context())) {
    return;
  }
  if ($og_context['group_type'] != $field['settings']['target_type']) {

    // Context is of invalid group-type.
    return;
  }
  return array(
    $og_context['gid'],
  );
}

/**
 * Return a form element with crafted links to create nodes for a group.
 *
 * @param $entity_type
 *   The entity type of the referenced entity.
 * @param $entity_id
 *   The entity ID of the referenced entity.
 * @param $destination
 *   Optional; The destination after a node is created. Defaults to the
 *   destination passed in the URL if exists, otherwise back to the current
 *   page.
 * @param $types
 *   Optional; An array of type names. Restrict the created links to the given
 *   types.
 */
function entityreference_prepopulate_create_node_links($entity_type, $entity_id, $field_name, $destination = NULL, $types = NULL) {
  $wrapper = entity_metadata_wrapper($entity_type, $entity_id);
  $field = field_info_field($field_name);
  $entity = entity_load_single($entity_type, $entity_id);
  list(, , $bundle) = entity_extract_ids($entity_type, $entity);
  $types = isset($types) ? $types : array_keys(node_type_get_types());
  $names = array();
  foreach ($types as $type_name) {
    if ($field['settings']['target_type'] != $entity_type) {

      // The entity type isn't referenced by the field.
      continue;
    }
    if (!empty($field['settings']['handler_settings']['target_bundles']) && !in_array($bundle, $field['settings']['handler_settings']['target_bundles'])) {

      // The entity bundle isn't referenced by the field.
      continue;
    }
    $instance = field_info_instance('node', $field_name, $type_name);
    if (empty($instance['settings']['behaviors']['prepopulate']['status'])) {

      // The field doesn't exist on the node type, or doesn't have prepopulate
      // enabled.
      continue;
    }
    if (!node_access('create', $type_name)) {
      continue;
    }
    $names[$type_name] = node_type_get_name($type_name);
  }
  if (empty($names)) {
    return;
  }

  // Sort names.
  asort($names);

  // Build links.
  $options = array(
    'query' => array(
      $field_name => $entity_id,
    ) + drupal_get_destination(),
  );
  $items = array();
  foreach ($names as $type => $name) {
    $items[] = array(
      'data' => l($name, 'node/add/' . str_replace('_', '-', $type), $options),
    );
  }
  $element = array();
  $element['entityreference_prepopulate'] = array(
    '#theme' => 'item_list',
    '#items' => $items,
  );
  return $element;
}

/**
 * Return array of providers.
 */
function entityreference_prepopulate_providers_info() {
  $description = t('Determine if values that should be prepopulated should "listen" to the OG-context.');
  if ($disabled = !module_exists('og_context')) {
    $description .= '<br / >' . t('Organic groups integration: Enable OG-context module.');
  }
  $providers = array(
    'url' => array(
      'title' => t('URL'),
      'description' => t('Prepopulate from URL'),
      'callback' => 'entityreference_prepopulate_get_values_from_url',
    ),
    'og_context' => array(
      'title' => t('OG Context'),
      'description' => $description,
      'disabled' => $disabled,
      'callback' => 'entityreference_prepopulate_get_values_from_og_context',
    ),
  );
  $providers = array_merge($providers, module_invoke_all('entityreference_prepopulate_providers_info'));

  // Allow other module to change the providers list.
  drupal_alter('entityreference_prepopulate_providers_info', $providers);
  return $providers;
}