You are here

ref_field_sync.module in (Entity)Reference Field Synchronization 7

File

ref_field_sync/ref_field_sync.module
View source
<?php

/**
 * Implements hook_help().
 */
function ref_field_sync_help($path, $arg) {
  switch ($path) {
    case 'admin/help#ref_field_sync':
      return '<p>' . t('With Entity Reference Field Synchronized you can link 2 fields so that they can back-reference their entities. When one of them is updated, a reference is created/updated/deleted in the other to keep a 2-way reference.') . '</p>';
  }
}

/**
 * Implements hook_field_info_alter().
 */
function ref_field_sync_field_info_alter(&$info) {

  // Inform Drupal we have a "sync" setting for ref_field.
  if (isset($info['ref_field'])) {
    $info['ref_field']['settings']['sync'] = '';
  }
}

/**
 * Implements hook_form_FORM-ID_alter
 */
function ref_field_sync_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
  if ($form['#field']['type'] == 'ref_field' && count($form['#field']['bundles']) == 1) {

    // Get fields of type ref_field
    $fields = field_read_fields(array(
      'type' => 'ref_field',
    ));

    // Remove current field
    unset($fields[$form['#field']['field_name']]);

    // Get referenceable entity type and entity info
    if (isset($form_state['values']['field']['settings']['entity'])) {
      $entity = $form_state['values']['field']['settings']['entity'];
      if (isset($form_state['values']['field']['settings']['bundles'])) {
        $bundles = $form_state['values']['field']['settings']['bundles'];
      }
    }
    else {
      $entity = $form['#field']['settings']['entity'];
      $bundles = $form['#field']['settings']['bundles'];
    }

    // Entity must support entity_save().
    if (!$entity || !entity_type_supports($entity, 'save')) {
      return;
    }
    $entity_info = entity_get_info($entity);

    // If no bundles selected, all are referenceable
    $bundles = empty($bundles) ? $entity_info['bundles'] : $bundles;

    // Init options array
    $options = array(
      '' => t('- Select -'),
    );

    // Loop through every field
    foreach ($fields as $field_name => $field) {

      // Get more info for the field (bundles where exist)
      $field = field_info_field_by_id($field['id']);

      // Update fields array with new info
      $fields[$field_name] = $field;

      // For now we can only deal with fields that have multiple values
      // TODO: Figure out how to deal with fields with single/limited values
      if ($field['cardinality'] >= 1) {
        continue;
      }

      // Field must only exist in referenced entity
      if (count($field['bundles']) > 1) {
        continue;
      }
      foreach ($bundles as $bundle => $data) {

        // Field must exist in all referenceable bundle
        if (!isset($field['bundles'][$entity]) || !in_array($bundle, $field['bundles'][$entity])) {
          continue 2;
        }
      }

      // Entity info for entity being referenced
      $ref_entity = $field['settings']['entity'];

      // Entity must support entity_save().
      if (!entity_type_supports($ref_entity, 'save')) {
        continue;
      }
      $ref_entity_info = entity_get_info($ref_entity);

      // If no bundles selected, all are referenceable
      $ref_bundles = empty($field['settings']['bundles']) ? $ref_entity_info['bundles'] : $field['settings']['bundles'];
      foreach ($ref_bundles as $bundle => $data) {

        // field must reference all/only bundles where this field exist
        if (!isset($form['#field']['bundles'][$ref_entity]) || !in_array($bundle, $form['#field']['bundles'][$ref_entity])) {
          continue 2;
        }
      }
      $in_sync = '';
      if (isset($field['settings']['sync']) && $field['settings']['sync']) {
        $sync_field = field_read_field($field['settings']['sync']);
        $in_sync = ' - ' . t('Currently syncing with: @field', array(
          '@field' => $sync_field['field_name'],
        ));
      }

      // If perfect back reference is found, add field as avaibale for sync
      $options[$field_name] = $field_name . $in_sync;
    }

    // Get referenceable entity type and entity info
    if (isset($form_state['values']['field']['settings']['sync'])) {
      $default = $form_state['values']['field']['settings']['sync'];
    }
    elseif (isset($form['#field']['settings']['sync'])) {
      $default = $form['#field']['settings']['sync'];
    }
    else {
      $default = '';
    }
    $form['field']['settings']['sync'] = array(
      '#default_value' => $default,
      '#type' => 'select',
      '#title' => 'Synchronize with',
      '#options' => $options,
      '#description' => t('Select a field to syncronize references with. Any changes made to a relationship on this field will automaticaly update the referenced entity on the selected field. If the selecetd field is being synced already, the current link for that field will be lost.'),
      '#element_validate' => array(
        'ref_field_sync_form_field_ui_field_edit_form_validate',
      ),
    );
  }
}
function ref_field_sync_form_field_ui_field_edit_form_validate($element, &$form_state, $form) {
  if ($form_state['values']['field']['cardinality'] >= 1 && $form_state['values']['field']['settings']['sync']) {
    form_error($element, t('Only fields with unlimited values can be kept in sync. please change to "Number of values" to unlimited or remove the syncronization option.'));
  }
}

/**
 * Implements hook_field_update_field().
 *
 * Link fields back automatically
 * And remove old link if one existed
 */
function ref_field_sync_field_update_field($field, $prior_field, $has_data) {

  // Add new association
  // Only add association if the sync field is not poiting to this one already
  if (isset($field['settings']['sync']) && $field['settings']['sync']) {
    $sync_field_name = $field['settings']['sync'];
    $sync_field = field_read_field($sync_field_name);
    if ($sync_field && $sync_field['settings']['sync'] != $field['field_name']) {
      $sync_field['settings']['sync'] = $field['field_name'];
      field_update_field($sync_field);
    }
  }

  // Check old association
  // If there was an association, call old sync field and remove association to
  // this one
  if (isset($prior_field['settings']['sync'])) {
    $prior_sync_field_name = $prior_field['settings']['sync'];
    $prior_sync_field = field_read_field($prior_sync_field_name);
    if ($prior_sync_field && $prior_sync_field['settings']['sync'] == $field['field_name']) {
      $prior_sync_field['settings']['sync'] = FALSE;
      field_update_field($prior_sync_field);
    }
  }
}

/**
 * Implements hook_entity_insert().
 */
function ref_field_sync_entity_insert($entity, $type) {
  $lng = isset($entity->language) && $entity->language ? $entity->language : LANGUAGE_NONE;
  $info = entity_get_info($type);

  // If entity support entity_save() and is not called by this module
  if (entity_type_supports($type, 'save') && (!isset($entity->ref_field_caller) || $entity->ref_field_caller != TRUE)) {

    // Get fields of type ref_field
    $fields = field_read_fields(array(
      'type' => 'ref_field',
    ));

    // Loop through every field
    foreach ($fields as $field_name => $field) {

      // Get more info for the field (bundles where exist)
      $field = field_info_field_by_id($field['id']);

      // Update fields array with new info
      $fields[$field_name] = $field;

      // Go through fields that exist in this entity/bundle
      if (isset($entity->{$field_name}) && isset($field['settings']['sync']) && $field['settings']['sync']) {
        $new = array();

        // Save values from new entity
        if (isset($entity->{$field_name}[$lng])) {
          foreach ($entity->{$field_name}[$lng] as $value) {
            $new[] = $value['eid'];
          }
        }
        foreach ($new as $eid) {
          $entities = entity_load($field['settings']['entity'], array(
            $eid,
          ));

          // Add reference to synced field
          ref_field_sync_add_reference($field['settings']['entity'], $entities[$eid], $entity->{$info['entity keys']['id']}, $field['settings']['sync']);
        }
      }
    }
  }
}

/**
 * Implements hook_entity_update().
 */
function ref_field_sync_entity_update($entity, $type) {
  $lng = isset($entity->language) && $entity->language ? $entity->language : LANGUAGE_NONE;
  $info = entity_get_info($type);

  // If entity support entity_save() and is not called byt this module
  if (entity_type_supports($type, 'save') && (!isset($entity->ref_field_caller) || $entity->ref_field_caller != TRUE)) {

    // Save original entity in variable
    $original = $entity->original;
    $org_lng = isset($original->language) && $original->language ? $original->language : LANGUAGE_NONE;

    // Get fields of type ref_field
    $fields = field_read_fields(array(
      'type' => 'ref_field',
    ));

    // Loop through every field
    foreach ($fields as $field_name => $field) {

      // Get more info for the field (bundles where exist)
      $field = field_info_field_by_id($field['id']);

      // Update fields array with new info
      $fields[$field_name] = $field;

      // Go through fields that exist in this entity/bundle
      if (isset($entity->{$field_name}) && isset($field['settings']['sync']) && $field['settings']['sync']) {
        $new = $old = array();

        // Save values from new entity
        if (isset($entity->{$field_name}[$lng])) {
          foreach ($entity->{$field_name}[$lng] as $value) {
            $new[] = $value['eid'];
          }
        }

        // Save values from original entity
        if (isset($original->{$field_name}[$lng])) {
          foreach ($original->{$field_name}[$org_lng] as $value) {
            $old[] = $value['eid'];
          }
        }

        // Get removed values
        $removed = array_diff($old, $new);
        foreach ($removed as $eid) {
          $entities = entity_load($field['settings']['entity'], array(
            $eid,
          ));

          // Remove reference from synced field
          ref_field_sync_remove_reference($field['settings']['entity'], $entities[$eid], $entity->{$info['entity keys']['id']}, $field['settings']['sync']);
        }

        // Get added values
        $added = array_diff($new, $old);
        foreach ($added as $eid) {
          $entities = entity_load($field['settings']['entity'], array(
            $eid,
          ));

          // Add reference to synced field
          ref_field_sync_add_reference($field['settings']['entity'], $entities[$eid], $entity->{$info['entity keys']['id']}, $field['settings']['sync']);
        }
      }
    }
  }
}

/**
 * Add a ref_field value form an entity
 */
function ref_field_sync_remove_reference($type, $entity, $eid, $field_name) {
  $lng = isset($entity->language) && $entity->language ? $entity->language : LANGUAGE_NONE;
  if (isset($entity->{$field_name}[$lng])) {
    foreach ($entity->{$field_name}[$lng] as $key => $value) {
      if ($value['eid'] == $eid) {
        unset($entity->{$field_name}[$lng][$key]);

        // Set flag to not process this entity_save again
        $entity->ref_field_caller = TRUE;
        entity_save($type, $entity);
      }
    }
  }
}

/**
 * Remove a ref_field value form an entity
 */
function ref_field_sync_add_reference($type, $entity, $eid, $field_name) {
  $lng = isset($entity->language) && $entity->language ? $entity->language : LANGUAGE_NONE;
  $entity->{$field_name}[$lng][]['eid'] = $eid;
  $entity->ref_field_caller = TRUE;

  // Set flag to not process this entity_save again
  entity_save($type, $entity);
}