You are here

function conflict_node_validate in Conflict 7

Implements hook_node_validate().

File

./conflict.module, line 52
Fieldwise conflict prevention and resolution. @author Brandon Bergren

Code

function conflict_node_validate($node, &$form, &$form_state) {
  if (isset($node->nid) && node_last_changed($node->nid) > $node->changed && variable_get('conflict_enable_' . $node->type, FALSE)) {

    // We only support nodes for now.
    $entity_type = 'node';

    // Bypass the core conflict detector.
    $errors =& drupal_static('form_set_error', array());
    if (!empty($errors['changed'])) {
      unset($errors['changed']);

      // Remove the message as well.
      foreach ($_SESSION['messages']['error'] as $k => $v) {
        if ($v == t('The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved.')) {
          unset($_SESSION['messages']['error'][$k]);
          $_SESSION['messages']['error'] = array_values($_SESSION['messages']['error']);
        }
        if (empty($_SESSION['messages']['error'])) {
          unset($_SESSION['messages']['error']);
        }
      }
    }
    $nodes = array();

    // Base node: The common ancestor that was cached when beginning the node
    // edit.
    $nodes['base'] = clone $form_state['node'];

    // Theirs: The current state of the node, with the changes that happened in
    // parallel.
    $nodes['theirs'] = clone node_load($node->nid, NULL, TRUE);

    // This workaround is done because the $node object in this hook does not
    // have processed fields and would be inconsistent during comparison.
    _field_invoke_multiple('load', 'node', array(
      $nodes['theirs']->nid => $nodes['theirs'],
    ));

    // Mine: The node that was about to be saved.
    $tmp_form_state = $form_state;
    $nodes['mine'] = node_form_submit_build_node($form, $tmp_form_state);
    _field_invoke_multiple('load', 'node', array(
      $nodes['mine']->nid => $nodes['mine'],
    ));

    // Store fields names to highlight them after form rebuild.
    $error_fields = array();

    // Dig through the fields looking for conflicts.
    $updated = FALSE;
    $remote_changes = _conflict_get_node_changes($nodes['base'], $nodes['theirs']);
    if (!empty($remote_changes)) {
      $local_changes = _conflict_get_node_changes($nodes['mine'], $nodes['base']);
      $real_changes = _conflict_get_node_changes($nodes['mine'], $nodes['theirs']);
      $conflicted_fields = array_intersect($local_changes, $remote_changes, $real_changes);
      $changed_fields = array_diff($remote_changes, array_intersect($local_changes, $remote_changes));
      foreach ($conflicted_fields as $field_name => $field_title) {

        // If the field can have unlimited values, check if the added values
        // from mine & theirs can be merged.
        $field = field_info_field($field_name);
        if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED && $field['type'] != 'taxonomy_term_reference') {

          // todo handling taxonomy_term_reference fields would be nice.
          $field_language = field_language('node', $form_state['node'], $field_name);
          $columns = array_keys($field['columns']);
          $same = TRUE;
          foreach ($nodes['base']->{$field_name}[$field_language] as $delta => $value) {
            foreach ($columns as $column) {
              if (!isset($nodes['mine']->{$field_name}[$field_language][$delta][$column]) || $value[$column] != $nodes['mine']->{$field_name}[$field_language][$delta][$column] || !isset($nodes['theirs']->{$field_name}[$field_language][$delta][$column]) || $value[$column] != $nodes['theirs']->{$field_name}[$field_language][$delta][$column]) {
                $same = FALSE;
                break;
              }
            }
          }
          if ($same) {

            // The values in base match both mine & theirs. Merge added values.
            unset($form_state['input'][$field_name]);
            $nodes['theirs']->{$field_name}[$field_language] = array_merge($nodes['theirs']->{$field_name}[$field_language], array_slice($nodes['mine']->{$field_name}[$field_language], $delta + 1));
            drupal_set_message(t('The @label field was also changed by another user. Your additions have been merged together. Please verify the updated values and save.', [
              '@label' => $field_title,
            ]), 'warning');
            $updated = TRUE;
            continue;
          }
        }
        drupal_set_message(t('The @label field was changed by another user while you were editing it. Save again to overwrite it.', array(
          '@label' => $field_title,
        )), 'error');
        $updated = TRUE;
        $error_fields[] = $field_name;
      }
      foreach ($changed_fields as $field_name => $field_title) {

        // Forget the user-submitted value.
        unset($form_state['input'][$field_name]);
        drupal_set_message(t('The @label field was changed by another user. Please verify the updated value.', array(
          '@label' => $field_title,
        )), 'warning');
        $updated = TRUE;
        $error_fields[] = $field_name;
      }
    }
    if ($updated) {

      // Reload the node to pick up the updated data.
      $node = clone $nodes['theirs'];
      node_object_prepare($node);
      $form_state['node'] = $node;
      $form_state['rebuild'] = TRUE;
      $form_state['temporary']['conflict_fields'] = $error_fields;
    }
  }
}