<?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_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);

  if (!empty($form_state['triggering_element']['#ajax'])) {
    // We are inside AJAX, so values can't be taken from URL at the
    // moment.
    return;
  }

  // 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'];

      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;
      }

      $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 ($ids || ($id && !empty($settings['action_on_edit']))) {
        // New entity with prepopualte 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') {
          // We don't hide the field via hook_field_access(), as the
          // default value won't be set.
          $form[$field_name]['#access'] = FALSE;
        }
      }
      elseif (in_array($settings['fallback'], array('form_error', 'redirect'))) {
        $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();
        }
      }
    }
  }
}

/**
 * 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);
  if ($id) {
    // Entity is already saved.
    return;
  }

  $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;
  }
  $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,
  );

  if (module_exists('og') && og_is_group_audience_field($field_name)) {
    if (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);
        }
      }

      // Return the values.
      return $ids;
    }

    $identifier[] = $instance['field_mode'];
  }

  $identifier = implode(':', $identifier);

  if (isset($cache[$identifier])) {
    return $cache[$identifier];
  }

  $cache[$identifier] = $ids = array();

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

  // Check if we have OG-context integration.
  if (!$ids) {
    $ids = entityreference_prepopulate_get_values_from_og_context($field, $instance);
  }

  // Check if there are values in the URL.
  if (!$ids) {
    $ids = entityreference_prepopulate_get_values_from_url($field, $instance);
  }

  if (!$ids || !$validate) {
    // No IDs found, or no validation is needed.
    $cache[$identifier] = $ids;
    return $ids;
  }

  $handler = entityreference_get_selection_handler($field, $instance);
  if (!$ids = $handler->validateReferencableEntities($ids)) {
    $cache[$identifier] = FALSE;
    return;
  }

  // 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) {
  $field_name = $field['field_name'];
  if (!empty($_GET[$field_name]) && is_string($_GET[$field_name])) {
    return explode(',', $_GET[$field_name]);
  }
}


/**
 * 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_is_group_audience_field($field_name) || !$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;
}
