You are here

relation_entity_collector.module in Relation 7

Relation Entity Collector Block.

File

relation_entity_collector/relation_entity_collector.module
View source
<?php

/**
 * @file
 * Relation Entity Collector Block.
 */

/**
 * Implements hook_block_info().
 */
function relation_entity_collector_block_info() {
  return array(
    'block' => array(
      'info' => t('Relation Entity Collector'),
      'status' => 1,
      'weight' => 100,
      'region' => 'content',
    ),
  );
}

/**
 * Implements hook_block_view().
 */
function relation_entity_collector_block_view() {
  if (_relation_entity_collector_user_has_access()) {
    $block['subject'] = t('Entity Collector');
    $block['content']['#pre_render'] = array(
      'relation_entity_collector_pre_render',
    );
    return $block;
  }
}

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

/**
 * Implements hook_menu().
 */
function relation_entity_collector_menu() {
  $items['relation_entity_collector/%relation'] = array(
    'title' => '',
    'access callback' => TRUE,
    'page callback' => 'relation_entity_collector_store',
    'page arguments' => array(
      1,
    ),
  );
  return $items;
}

/**
 * Page callback copying a relation into SESSION.
 */
function relation_entity_collector_store($relation) {
  $_SESSION['relation_edit'] = $relation;
  $_SESSION['relation_type'] = $relation->relation_type;
  $_SESSION['relation_entity_keys'] = array();
  foreach ($relation->endpoints[LANGUAGE_NONE] as $delta => $endpoint) {
    $entities = entity_load($endpoint['entity_type'], array(
      $endpoint['entity_id'],
    ));
    $entity = $entities[$endpoint['entity_id']];
    list(, , $entity_bundle) = entity_extract_ids($endpoint['entity_type'], $entity);
    $_SESSION['relation_entity_keys'][] = array(
      'entity_type' => $endpoint['entity_type'],
      'entity_id' => $endpoint['entity_id'],
      'entity_bundle' => $entity_bundle,
      'r_index' => $delta,
      'entity_label' => "{$entity_bundle}: " . entity_label($endpoint['entity_type'], $entity),
      'entity_key' => $endpoint['entity_type'] . ':' . $endpoint['entity_id'],
    );
  }
  drupal_set_message(t('The relation is ready for edit'));
  drupal_goto();
}

/**
 * Implements hook_entity_view_alter().
 */
function relation_entity_collector_entity_view_alter(&$build, $entity_type) {
  if ($entity_type == 'relation' && _relation_entity_collector_user_has_access()) {
    $relation = $build['#entity'];
    $text = t('Edit @relation_type endpoints', array(
      '@relation_type' => $relation->relation_type,
    ));
    $build['link']['#markup'] = l($text, "relation_entity_collector/{$relation->rid}", drupal_get_destination());
  }
}

/**
 * Access check helper.
 */
function _relation_entity_collector_user_has_access() {
  return user_access('administer relations') || user_access('create relations');
}

/**
 * Pre render callback for the entity_collector block.
 */
function relation_entity_collector_pre_render($element) {
  $element['form'] = drupal_get_form('relation_entity_collector');
  return $element;
}

/**
 * Implements hook_entity_load().
 */
function relation_entity_collector_entity_load($entities, $type) {
  $entities_store =& drupal_static('relation_entities', array());
  $entities_store += array(
    $type => array(),
  );
  $entities_store[$type] += $entities;
}

/**
 * The entity_collector form.
 */
function relation_entity_collector($form, &$form_state) {
  $form['#attached']['css'] = array(
    drupal_get_path('module', 'relation_entity_collector') . '/relation_entity_collector.css',
  );
  $types = relation_get_types();
  if (empty($types)) {
    $form['explanation']['#markup'] = t('Before you can create relations, you need to create one or more !link. Once you\'ve done that, visit any page that loads one or more entities, and use this block to add entities to a new relation. Picked entities stay in the entity_collector until cleared or a relation is created so it is possible to collect the entities from several pages.', array(
      '!link' => module_exists('relation_ui') ? l(t('relation types'), 'admin/structure/relation') : t('relation types'),
    ));
    return $form;
  }

  // If nothing is picked, forget last relation type picked
  if (isset($_SESSION['relation_entity_keys']) && count($_SESSION['relation_entity_keys']) < 1) {
    unset($_SESSION['relation_type']);
  }
  $relation_types = array();
  foreach ($types as $type) {
    $relation_types[$type->relation_type] = $type->label;
  }

  // Picking entities in progress or not
  $relation_type = isset($_SESSION['relation_type']) ? $_SESSION['relation_type'] : '';

  // forget the selected relation type if it's no longer available
  if (!isset($relation_types[$relation_type])) {
    unset($_SESSION['relation_type']);
    $relation_type = '';
  }
  if (empty($relation_type)) {

    // User may have chosen a type, but not picked any entities yet
    if (isset($form_state['values']['relation_type'])) {
      $relation_type = $form_state['values']['relation_type'];
    }
    else {
      if (empty($relation_type) && count($types) == 1) {
        $relation_type = reset($types)->relation_type;
      }
    }
  }
  $relation_type_object = !empty($relation_type) ? relation_type_load($relation_type) : NULL;
  $options = array();
  $all_entity_cache = isset($form_state['all_entity_cache']) ? $form_state['all_entity_cache'] : array();
  if (!isset($form_state['all_entity_cache'])) {
    $form_state['all_entity_cache'] = array();
  }
  if ($relation_entities = drupal_static('relation_entities', array())) {
    $form_state['all_entity_cache'] += $relation_entities;
  }
  foreach ($form_state['all_entity_cache'] as $entity_type => $entities) {
    foreach ($entities as $entity_id => $entity) {
      list(, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
      if (!is_null($relation_type_object)) {
        $valid = FALSE;
        foreach (array(
          'source_bundles',
          'target_bundles',
        ) as $property) {
          foreach ($relation_type_object->{$property} as $allowed_bundle) {
            if ($allowed_bundle == "{$entity_type}:{$entity_bundle}" || $allowed_bundle == "{$entity_type}:*") {
              $valid = TRUE;
              break 2;
            }
          }
        }
      }
      else {
        $valid = TRUE;
      }
      if ($valid) {
        $bundle_label = _relation_get_bundle_label($entity_type, $entity_bundle);
        $options["{$entity_type}:{$entity_id}"] = $bundle_label . ': ' . entity_label($entity_type, $entity);
      }
    }
  }
  asort($options);
  $entity_key_default = count($options) == 1 ? key($options) : '';
  $form['entity_picker'] = array(
    '#prefix' => '<span id="relation_entity_collector_picker">',
    '#suffix' => '</span>',
  );
  $form['entity_picker']['relation_type'] = array(
    '#prefix' => '<span id="relation_entity_collector_pick_type">',
    '#suffix' => '</span>',
    '#type' => 'select',
    '#title' => t('Relation type'),
    '#default_value' => $relation_type,
    '#options' => $relation_types,
    '#empty_value' => '',
    '#empty_option' => t('- Select a relation type -'),
    '#access' => empty($_SESSION['relation_edit']),
    '#ajax' => array(
      'event' => 'change',
      'callback' => '_relation_entity_collector_ajax_picker',
      'wrapper' => 'relation_entity_collector_picker',
    ),
  );
  if (!empty($_SESSION['relation_type']) && count($_SESSION['relation_entity_keys']) > 0) {
    $form['entity_picker']['relation_type']['#attributes'] = array(
      'disabled' => TRUE,
    );
  }
  $form['entity_picker']['entity_key'] = array(
    '#type' => 'select',
    '#title' => t('Select an entity'),
    '#options' => $options,
    '#default_value' => $entity_key_default,
    '#empty_value' => '',
    '#empty_option' => t('- Select an entity -'),
    '#description' => t('Selector shows all valid !entities loaded on this page.', array(
      '!entities' => l(t('entities'), 'http://drupal.org/glossary#entity', array(
        'absolute' => TRUE,
        'external' => TRUE,
      )),
    )),
  );
  $form['entity_picker']['pick'] = array(
    '#type' => 'submit',
    '#value' => t('Pick'),
    '#submit' => array(
      'relation_entity_collector_pick',
    ),
    '#ajax' => array(
      'wrapper' => 'relation_entity_collector_picker',
      'callback' => '_relation_entity_collector_ajax_picker',
    ),
  );
  $form['entity_picker']['reload'] = array(
    '#type' => 'fieldset',
    '#title' => t('Picked entities'),
  );
  if (!empty($_SESSION['relation_entity_keys'])) {
    $form['entity_picker']['reload']['table']['#entity_collector_columns'] = array(
      'weight',
      'remove',
    );
    foreach ($_SESSION['relation_entity_keys'] as $delta => $entity_key) {

      // The structure is (entity_type, entity_id, entity label).
      $form['entity_picker']['reload']['table']['weight'][] = array(
        '#type' => 'weight',
        '#delta' => count($_SESSION['relation_entity_keys']),
        '#default_value' => $delta,
        '#title_display' => 'invisible',
        '#title' => '',
      );
      $form['entity_picker']['reload']['table']['remove'][] = array(
        '#name' => 'remove-' . $entity_key['entity_key'],
        '#type' => 'submit',
        '#value' => t('Remove'),
        '#entity_key' => $entity_key,
        '#submit' => array(
          'relation_entity_collector_remove',
        ),
        '#ajax' => array(
          'wrapper' => 'relation_entity_collector_picker',
          'callback' => '_relation_entity_collector_ajax_picker',
        ),
      );
      $form['entity_picker']['reload']['table']['#tree'] = TRUE;
      $form['entity_picker']['reload']['table']['#theme'] = 'relation_entity_collector_table';
    }
    if (!isset($relation_type_object) && !empty($relation_type)) {
      $relation_type_object = relation_type_load($relation_type);
    }
    $min_arity = isset($relation_type_object->min_arity) ? $relation_type_object->min_arity : 1;
    if (count($_SESSION['relation_entity_keys']) >= $min_arity) {
      $form['entity_picker']['reload']['save'] = array(
        '#type' => 'submit',
        '#value' => t('Save relation'),
        '#submit' => array(
          'relation_entity_collector_save',
        ),
      );
    }
    if (isset($_SESSION['relation_entity_keys'])) {
      $form['entity_picker']['reload']['clear'] = array(
        '#type' => 'submit',
        '#value' => t('Clear'),
        '#submit' => array(
          'relation_entity_collector_clear',
        ),
        '#ajax' => array(
          'wrapper' => 'relation_entity_collector_picker',
          'callback' => '_relation_entity_collector_ajax_picker',
        ),
      );
    }
  }
  $form['explanation'] = array(
    '#prefix' => '<div id=\'relation-entity-collector-explanation\'>',
    '#markup' => t('Picked entities stay in the Entity Collector until cleared or a relation is created so it is possible to collect the entities from several pages.'),
    '#suffix' => '</div>',
  );
  return $form;
}

/**
 * Reload the picker when relation type changes.
 */
function _relation_entity_collector_ajax_picker($form, &$form_state) {
  return $form['entity_picker'];
}

/**
 * Helper to get a item_list render structure out of the entities in session.
 */
function _relation_stored_entity_keys_list() {
  $list = array();
  foreach ($_SESSION['relation_entity_keys'] as $entity_key) {
    $list[] = $entity_key['entity_label'];
  }
  return array(
    '#theme' => 'item_list',
    '#items' => $list,
  );
}

/**
 * Validate form submission for the entity_collector.
 */
function relation_entity_collector_validate($form, &$form_state) {
  switch ($form_state['triggering_element']['#value']) {
    case t('Pick'):

      // Require values.
      $relation_type = $form_state['values']['relation_type'];
      $entity_key = $form_state['values']['entity_key'];
      $errors = FALSE;
      if (empty($relation_type)) {
        form_set_error('relation_type', t('Please select a relation type.'));
        $errors = TRUE;
      }
      if (empty($entity_key)) {
        form_set_error('entity_key', t('Please select an entity.'));
        $errors = TRUE;
      }

      // If either of these are not selected we can not continue.
      if ($errors) {
        return;
      }

      // Get entity info from key ('{entity_type}:{entity_id}').
      list($entity_type, $entity_id) = explode(':', $entity_key);

      // Add the label for later display. #options is check_plain'd but we need
      // to do that ourselves.
      $entity_label = check_plain($form['entity_picker']['entity_key']['#options'][$entity_key]);

      // Indexes are added in ascending order, starting from 0.
      $_SESSION += array(
        'relation_entity_keys' => array(),
      );
      $next_index = count($_SESSION['relation_entity_keys']);

      // If validation succeeds we will add this in the submit handler.
      $form_state['pick'] = array(
        'r_index' => $next_index,
        'entity_key' => $entity_key,
        'entity_label' => $entity_label,
        'entity_type' => $entity_type,
        'entity_id' => $entity_id,
      );
      $endpoints = $_SESSION['relation_entity_keys'];
      $endpoints[] = $form_state['pick'];
      $relation = _relation_entity_collector_get_entity($form_state['values']['relation_type'], $endpoints);
      $relation->in_progress = TRUE;
      _relation_entity_collector_endpoints_validate($relation, $form, $form_state);
      break;
    case t('Save relation'):
      _relation_entity_collector_endpoints_validate(_relation_entity_collector_get_entity(), $form, $form_state);
      break;
  }
}
function _relation_entity_collector_endpoints_validate($relation, $form, &$form_state) {

  // Perform field_level validation.
  try {
    field_attach_validate('relation', $relation);
  } catch (FieldValidationException $e) {
    $index = 0;

    // We do not look anything like a field widget so just pile the errors on
    // nonexistent form elements.
    foreach ($e->errors as $field_name => $field_errors) {
      foreach ($field_errors as $langcode => $multiple_errors) {
        foreach ($multiple_errors as $delta => $item_errors) {
          foreach ($item_errors as $item_error) {
            form_set_error('error' . $index++, $item_error['message']);
          }
        }
      }
    }
  }
}

/**
 * Retrieves the relation being edited or picked.
 */
function _relation_entity_collector_get_entity($relation_type = NULL, $endpoints = NULL) {
  if (!isset($relation_type) && isset($_SESSION['relation_type'])) {
    $relation_type = $_SESSION['relation_type'];
  }
  if (!isset($endpoints) && isset($_SESSION['relation_entity_keys'])) {
    $endpoints = $_SESSION['relation_entity_keys'];
  }
  if (isset($_SESSION['relation_edit'])) {
    $relation = $_SESSION['relation_edit'];
    if (isset($endpoints)) {
      $relation->endpoints[LANGUAGE_NONE] = $endpoints;
    }
    return $relation;
  }
  if (isset($relation_type)) {
    return relation_create($relation_type, $endpoints);
  }
}

/**
 * Submit handler for the pick button.
 */
function relation_entity_collector_pick($form, &$form_state) {
  $_SESSION['relation_entity_keys'][] = $form_state['pick'];
  $_SESSION['relation_type'] = $form_state['values']['relation_type'];
  $form_state['rebuild'] = TRUE;
}

/**
 * Submit handler for the remove button.
 */
function relation_entity_collector_remove($form, &$form_state) {
  $entity_key = $form_state['triggering_element']['#entity_key']['entity_key'];
  foreach ($_SESSION['relation_entity_keys'] as $key => $entity) {
    if ($entity['entity_key'] == $entity_key) {
      unset($_SESSION['relation_entity_keys'][$key]);
      $form_state['rebuild'] = TRUE;
      return;
    }
  }
}

/**
 * Submit handler for the save button.
 */
function relation_entity_collector_save($form, $form_state) {
  $relation = _relation_entity_collector_get_entity();
  if ($relation) {
    array_multisort($form_state['values']['table']['weight'], SORT_ASC, $relation->endpoints[LANGUAGE_NONE]);
    $rid = relation_save($relation);
    if ($rid) {
      $relation_uri = entity_uri('relation', $relation);
      $link = l(relation_get_type_label($relation), $relation_uri['path']);
      $list = _relation_stored_entity_keys_list();
      $rendered_list = drupal_render($list);
      $t_arguments = array(
        '!link' => $link,
        '!list' => $rendered_list,
      );
      if (isset($_SESSION['relation_edit'])) {
        $message = t('Edited !link containing !list', $t_arguments);
      }
      else {
        $message = t('Created new !link from !list', $t_arguments);
      }
      drupal_set_message($message);
      relation_entity_collector_clear($form, $form_state);
    }
    else {
      drupal_set_message(t('Relation not created.'), 'error');
    }
  }
}

/**
 * Submit handler for the clear button.
 */
function relation_entity_collector_clear($form, &$form_state) {
  unset($_SESSION['relation_type'], $_SESSION['relation_entity_keys'], $_SESSION['relation_edit']);
  $form_state['rebuild'] = TRUE;
}

/**
 * Implements hook_views_post_execute().
 *
 * Make sure entities are loaded even if only fields are used.
 */
function relation_entity_collector_views_post_execute($view) {
  if (_relation_entity_collector_user_has_access()) {
    $properties = get_object_vars($view->query);
    if (!empty($properties['fields']) && !empty($view->result)) {
      foreach (entity_get_info() as $entity_type => $entity_info) {
        $map[$entity_info['base table']] = array(
          'id' => $entity_info['entity keys']['id'],
          'entity_type' => $entity_type,
        );
      }
      $collect = array();
      foreach ($view->query->fields as $alias => $field) {
        if (isset($field['table'])) {
          $table_name = $view->query->table_queue[$field['table']]['table'];
          if (isset($map[$table_name]) && $map[$table_name]['id'] == $field['field']) {
            $collect[$map[$table_name]['entity_type']] = $alias;
          }
        }
      }
      $ids = array();
      foreach ($view->result as $row) {
        foreach ($collect as $entity_type => $alias) {

          // Skip empty values, which may happen for entities that are obtained
          // via a non-required relationship in the view.
          if (!empty($row->{$alias})) {
            $ids[$entity_type][] = $row->{$alias};
          }
        }
      }
      foreach ($ids as $entity_type => $entity_ids) {
        entity_load($entity_type, $entity_ids);
      }
    }
  }
}

/**
 * Creates a draggable table out of the entities already picked.
 */
function theme_relation_entity_collector_table($variables) {
  $form = $variables['form'];
  $table['header'] = array();
  $table['attributes']['id'] = 'relation-entity-collector-table';
  $table['rows'] = array();
  drupal_add_tabledrag($table['attributes']['id'], 'order', 'sibling', 'relation-entity-collector-weight');
  foreach (element_children($form['weight']) as $key) {
    $form['weight'][$key]['#attributes']['class'] = array(
      'relation-entity-collector-weight',
    );
    $data = array(
      $form['remove'][$key]['#entity_key']['entity_label'],
    );
    foreach ($form['#entity_collector_columns'] as $column) {
      $data[] = drupal_render($form[$column][$key]);
    }
    $table['rows'][] = array(
      'data' => $data,
      'class' => array(
        'draggable',
      ),
    );
  }
  $output = '';
  if ($table['rows']) {
    $output .= theme('table', $table);
  }
  return $output . drupal_render_children($form);
}

/**
 * Implements hook_preprocess_username().
 *
 * We capture every user printed this way.
 */
function relation_entity_collector_preprocess_username($variables) {
  if (_relation_entity_collector_user_has_access() && isset($variables['account']->nid)) {

    // This looks like a node passed to theme('username') in
    // template_preprocess_node() and user_node_load() doesn't load the user
    // so we do instead. It does not work with modules using render arrays
    // because it is called too late but Views renders early.
    user_load($variables['account']->uid);
  }
}

Functions

Namesort descending Description
relation_entity_collector The entity_collector form.
relation_entity_collector_block_info Implements hook_block_info().
relation_entity_collector_block_view Implements hook_block_view().
relation_entity_collector_clear Submit handler for the clear button.
relation_entity_collector_entity_load Implements hook_entity_load().
relation_entity_collector_entity_view_alter Implements hook_entity_view_alter().
relation_entity_collector_menu Implements hook_menu().
relation_entity_collector_pick Submit handler for the pick button.
relation_entity_collector_preprocess_username Implements hook_preprocess_username().
relation_entity_collector_pre_render Pre render callback for the entity_collector block.
relation_entity_collector_remove Submit handler for the remove button.
relation_entity_collector_save Submit handler for the save button.
relation_entity_collector_store Page callback copying a relation into SESSION.
relation_entity_collector_theme Implements hook_theme().
relation_entity_collector_validate Validate form submission for the entity_collector.
relation_entity_collector_views_post_execute Implements hook_views_post_execute().
theme_relation_entity_collector_table Creates a draggable table out of the entities already picked.
_relation_entity_collector_ajax_picker Reload the picker when relation type changes.
_relation_entity_collector_endpoints_validate
_relation_entity_collector_get_entity Retrieves the relation being edited or picked.
_relation_entity_collector_user_has_access Access check helper.
_relation_stored_entity_keys_list Helper to get a item_list render structure out of the entities in session.