You are here

comment_alter.module in Comment Alter 8

Same filename and directory in other branches
  1. 7 comment_alter.module

Allows to alter entities from comment form.

File

comment_alter.module
View source
<?php

/**
* @file
* Allows to alter entities from comment form.
*/
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\Core\Render\Element;
use Drupal\Core\Form\FormState;
use Drupal\comment\CommentInterface;
use Drupal\diff\EntityComparisonBase;
use Drupal\Component\Diff\Diff;
use Drupal\Core\Url;

/**
 * Implements hook_form_FORM_ID_alter().
 */
function comment_alter_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state) {

  /** @var FieldConfig $field */
  $field = $form_state
    ->getFormObject()
    ->getEntity();
  $form['third_party_settings']['comment_alter']['comment_alter_enabled'] = [
    '#type' => 'checkbox',
    '#title' => t('User may alter this field from comment.'),
    '#weight' => 0,
    '#default_value' => $field
      ->getThirdPartySetting('comment_alter', 'comment_alter_enabled', FALSE),
    '#description' => t('This allows user to alter this field from comment attached to the parent entity'),
  ];
  $form['third_party_settings']['comment_alter']['comment_alter_hide'] = [
    '#type' => 'checkbox',
    '#title' => t('Hide alterations of this field from diffs.'),
    '#weight' => 1,
    '#default_value' => $field
      ->getThirdPartySetting('comment_alter', 'comment_alter_hide', FALSE),
    '#description' => t('Allows user to hide the differences shown on the comment. Instead of the differences a link to the revision comparison is displayed for nodes and for rest of the entities "Changes are hidden" is shown.'),
    '#states' => [
      'invisible' => [
        ':input[name="third_party_settings[comment_alter][comment_alter_enabled]"]' => [
          'checked' => FALSE,
        ],
      ],
    ],
  ];
  $form['third_party_settings']['#weight'] = $form['required']['#weight'] + 0.7;

  // Previously (in the D7 version) these three checkboxes were available on the
  // node type edit form. It's not only complicated to add them there in D8, but
  // D8 even allows comments to be posted to any fieldable content entities.
  // Because of that, it makes more sense to place these checkboxes on the
  // comment field itself.
  $entity_type = explode('.', $field
    ->getOriginalId());
  if ($field
    ->getType() == 'comment') {
    $form['third_party_settings']['comment_alter']['comment_alter_default'] = [
      '#type' => 'checkbox',
      '#title' => t('Use the latest revision as the default values for comment alterable fields (if any)'),
      '#weight' => 2,
      '#default_value' => $field
        ->getThirdPartySetting('comment_alter', 'comment_alter_default', FALSE),
      '#description' => t('Loads the field values from the latest revision of the node (instead of the current revision) when the comment form is displayed.'),
    ];
    $form['third_party_settings']['comment_alter']['comment_alter_diff_link'] = [
      '#type' => 'checkbox',
      '#title' => t('Add a link to the usual diff of the two revisions to the links area of the comment'),
      '#weight' => 3,
      '#default_value' => $field
        ->getThirdPartySetting('comment_alter', 'comment_alter_diff_link', FALSE),
    ];
    $form['third_party_settings']['comment_alter']['comment_alter_reply'] = [
      '#type' => 'checkbox',
      '#title' => t('Allow altering fields when submitting reply comments (if replying allowed)'),
      '#weight' => 4,
      '#default_value' => $field
        ->getThirdPartySetting('comment_alter', 'comment_alter_reply', FALSE),
    ];
  }

  // Clearing cache to ensure that cache is removed if we select comment alter
  // option for a field.
  \Drupal::cache()
    ->delete('comment_alter_fields:' . $entity_type[0] . ':' . $entity_type[1]);
}

/**
 * Returns the comment alterable fields for an entity.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   Entity object for the parent entity.
 *
 * @return array
 *   An array of comment alterable fields on parent entity.
 */
function comment_alter_get_alterable_fields($entity_type, $bundle) {
  $cid = 'comment_alter_fields:' . $entity_type . ':' . $bundle;
  $comment_alter_fields =& drupal_static(__FUNCTION__);
  if (!isset($comment_alter_fields[$entity_type][$bundle])) {
    if ($cache = \Drupal::cache()
      ->get($cid)) {
      $comment_alter_fields[$entity_type][$bundle] = $cache->data;
    }
    else {
      $field_definitions = \Drupal::entityManager()
        ->getFieldDefinitions($entity_type, $bundle);
      foreach ($field_definitions as $field_name => $field_item) {

        // Check if we are dealing with an actual field. The
        // getThirdPartySetting method is available only for an actual field.
        if (is_a($field_item, 'Drupal\\field\\Entity\\FieldConfig') && $field_item
          ->getThirdPartySetting('comment_alter', 'comment_alter_enabled', FALSE)) {
          $comment_alter_fields[$entity_type][$bundle][$field_name] = $field_name;
        }
      }
      if (!isset($comment_alter_fields[$entity_type][$bundle])) {
        $comment_alter_fields[$entity_type][$bundle] = [];
      }
      \Drupal::cache()
        ->set($cid, $comment_alter_fields[$entity_type][$bundle]);
    }
  }
  return $comment_alter_fields[$entity_type][$bundle];
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function comment_alter_form_comment_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  /** @var \Drupal\comment\Entity\Comment $comment_entity */
  $comment_entity = $form_state
    ->getFormObject()
    ->getEntity();
  $parent_entity = $comment_entity
    ->getCommentedEntity();
  $entity_type = $parent_entity
    ->getEntityTypeId();

  /** @var FieldConfig $comment_field */
  $comment_field = $parent_entity
    ->getFieldDefinition($comment_entity
    ->getFieldName());

  // Load the latest revision instead of the current if asked for.
  if ($comment_field
    ->getThirdPartySetting('comment_alter', 'comment_alter_default', FALSE) && $parent_entity
    ->getEntityType()
    ->hasKey('revision')) {

    // Retrieve the revision and ID keys for the parent entity.
    $parent_entity_keys = $parent_entity
      ->getEntityType()
      ->getKeys();
    $revision_id = \Drupal::database()
      ->select($entity_type . '_revision', 'per')
      ->fields('per', [
      $parent_entity_keys['revision'],
    ])
      ->condition($parent_entity_keys['id'], $parent_entity
      ->id())
      ->orderBy($parent_entity_keys['revision'], 'DESC')
      ->range(0, 1)
      ->execute()
      ->fetchField();
    $parent_entity = \Drupal::entityTypeManager()
      ->getStorage($entity_type)
      ->loadRevision($revision_id);
  }
  if (!$comment_entity
    ->isNew()) {
    return;
  }
  elseif ($comment_entity
    ->hasParentComment() && !$comment_field
    ->getThirdPartySetting('comment_alter', 'comment_alter_reply', FALSE)) {

    // Bail out early if comment_altering is disallowed when posting replies.
    return;
  }
  else {
    $bundle = $parent_entity
      ->bundle();
    $comment_alterable_fields = comment_alter_get_alterable_fields($entity_type, $bundle);
    if (empty($comment_alterable_fields)) {
      return;
    }

    // The _comment_alter_submit_node_fields() function needs these two arrays.
    // This one is a list of comment_alterable fields.
    $alterable_fields = [];

    // It turns out that we don't need to retrieve pseudo field's weight as
    // here Drupal assigns the weight of pseudo fields automatically. So,
    // we may reorder the pseudo fields and Drupal will take care of the
    // position of its widget. Hence, we are not assigning the weight property
    // to the field widgets that are created here.
    $parent_form_display = entity_get_form_display($parent_entity
      ->getEntityTypeId(), $parent_entity
      ->bundle(), 'default');

    // Attach parent entity fields to the comment form. Do it on a copy of the
    // $form, so that we know we are only getting the field element itself and
    // no other side-effects.
    $parent_form = $form;

    // To support same name fields on both comment and parent entity, provide
    // #parent property so that submitted field values for our alterable field
    // widgets appears at different location not at the top level of
    // $form_state->getValues().
    $parent_form['#parents'] = [
      'comment_alter_fields',
    ];
    $parent_form_display
      ->buildForm($parent_entity, $parent_form, $form_state);
    foreach ($comment_alterable_fields as $alterable_field_name) {
      if (empty($parent_form[$alterable_field_name])) {
        continue;
      }
      $form[$entity_type . '_' . $bundle . '_' . 'comment_alter_' . $alterable_field_name] = $parent_form[$alterable_field_name];

      // Remember that this field is alterable.
      $alterable_fields[$alterable_field_name] = $alterable_field_name;
    }
    if (!empty($alterable_fields)) {
      $form['comment_alter'] = [
        '#type' => 'value',
        '#value' => [
          'fields' => $alterable_fields,
          'old_vid' => $parent_entity
            ->getRevisionId(),
        ],
      ];

      // Adding our own afterBuild call to this form, so we could add the
      // column information into the $form itself.
      $form['#after_build'][] = 'comment_alter_form_comment_form_alter_after_build';
      $form['#validate'][] = 'comment_alter_form_comment_form_alter_validate';
      $form['actions']['submit']['#submit'][] = 'comment_alter_form_comment_form_alter_submit';
    }
  }
}

/**
 * Custom #after_build callback for comment_form to add comment alterable
 * columns info and values.
 */
function comment_alter_form_comment_form_alter_after_build(&$form, FormStateInterface &$form_state) {
  $parent_entity = $form_state
    ->getFormObject()
    ->getEntity()
    ->getCommentedEntity();
  $entity_type = $parent_entity
    ->getEntityTypeId();
  $bundle = $parent_entity
    ->bundle();

  // This one informs about comment_alterable columns per comment_alterable
  // fields. First-level key is the field name, second-level keys (and values)
  // are the columns which do have their form elements.
  $alterable_columns = [];
  foreach ($form['comment_alter']['#value']['fields'] as $alterable_field) {

    // Store the old/current column value for this alterable field.
    $form_state
      ->setValue([
      'comment_alter_fields',
      'comment_alter_' . $alterable_field . '_old',
    ], $parent_entity->{$alterable_field}
      ->getValue());
    $field_widget = $form[$entity_type . '_' . $bundle . '_' . 'comment_alter_' . $alterable_field]['widget'];

    // Store the column information also.
    $field_items = Element::children($field_widget);

    // For select list/checkboxes we have #key_column which stores the column
    // information of the field so, get it from there and continue.
    if (isset($field_widget['#key_column'])) {
      $alterable_columns[$alterable_field][$field_widget['#key_column']] = $field_widget['#key_column'];
      continue;
    }

    // If there is no column info available like in case of select list bail
    // out early or Element::children() will produce an fatal error.
    if (is_null($field_items['0'])) {
      continue;
    }

    // Get the column info from first level if it is available there else get
    // it from the first item. In case it is available at the first level then
    // field_items should be string there.
    $columns = is_string($field_items['0']) ? $field_items : Element::children($field_widget['0']);
    foreach ($columns as $column) {
      $alterable_columns[$alterable_field][$column] = $column;
    }
  }
  $form_state
    ->setValue('comment_alterable_columns', $alterable_columns);
  return $form;
}

/**
 * Helper function to clean up field values for comparison.
 *
 * Removes and non-alterable columns so we can compare the old and new field
 * values and find changes.
 *
 * @param array $values
 *   Array whose elements should be cleaned up.
 * @param string $field_name
 *   Clean up this field of the array.
 * @param string $old
 *   (optional) Suffix for old value cleanup.
 * @param string $prefix
 *   (optional) Prefix for old value cleanup.
 */
function _comment_alter_cleanup_field_values(&$values, $field_name, $old = '', $prefix = '') {

  // Sort the Multiple valued field via there column _weight and then remove
  // the weight column from values.
  if (isset($values['comment_alterable_columns'][$prefix . $field_name . $old]['_weight'])) {
    usort($values['comment_alter_fields'][$prefix . $field_name . $old], 'comment_alter_cmp');
  }
  foreach ($values['comment_alter_fields'][$prefix . $field_name . $old] as $level => $deltas) {

    // Remove the non-alterable columns from comparison.
    foreach ($values['comment_alter_fields'][$prefix . $field_name . $old][$level] as $column => $value) {

      // Unset the _weight column from $values as our stored old alterable
      // column value doesn't contain _weight column.
      if (!isset($values['comment_alterable_columns'][$field_name][$column]) || $column === '_weight') {
        unset($values['comment_alter_fields'][$prefix . $field_name . $old][$level][$column]);
        continue;
      }
    }
  }
  _comment_alter_cleanup_arrays($values['comment_alter_fields'][$prefix . $field_name . $old]);
}

/**
 * usort comparison function.
 *
 * @link http://php.net/manual/en/function.usort.php @endlink
 */
function comment_alter_cmp($a, $b) {
  if ($a['_weight'] == $b['_weight']) {
    return 0;
  }
  return $a['_weight'] < $b['_weight'] ? -1 : 1;
}

/**
 * Helper function to recursively clean up semi-empty arrays.
 *
 * Eg. array('foo' => array('bar' => array('baz' => ''))) becomes array().
 *
 * @param array $a
 *   Array whose empty elements should be removed.
 */
function _comment_alter_cleanup_arrays(&$a) {
  if (is_array($a)) {
    foreach ($a as $key => &$value) {
      if (is_array($value)) {
        _comment_alter_cleanup_arrays($value);
      }
      if (empty($value)) {
        unset($a[$key]);
      }
    }
  }
}

/**
 * Validation callback for the altered comment form.
 */
function comment_alter_form_comment_form_alter_validate($form, FormStateInterface &$form_state) {
  $original_value = $form_state
    ->getValues();
  $parent_entity = $form_state
    ->getFormObject()
    ->getEntity()
    ->getCommentedEntity();
  $parent_form_display = entity_get_form_display($parent_entity
    ->getEntityTypeId(), $parent_entity
    ->bundle(), 'default');
  $parent_form = [];
  $parent_form_state = new FormState();
  $parent_form_display
    ->buildForm($parent_entity, $parent_form, $parent_form_state);
  foreach ($original_value['comment_alter']['fields'] as $alterable_field) {
    $parent_form_state
      ->setValue($alterable_field, $original_value['comment_alter_fields'][$alterable_field]);
  }
  $parent_form_display
    ->extractFormValues($parent_entity, $parent_form, $parent_form_state);
  $parent_form_display
    ->validateFormValues($parent_entity, $parent_form, $parent_form_state);
}

/**
 * Submit callback for the altered comment form.
 */
function comment_alter_form_comment_form_alter_submit($form, FormStateInterface &$form_state) {
  $original_value = $form_state
    ->getValues();
  $values = $original_value;

  // Do not try to save anything if there is nothing that was allowed to be
  // changed from the comment form.
  if (isset($values['comment_alter'])) {
    $changed_fields = [];
    foreach ($values['comment_alter']['fields'] as $field_name) {
      _comment_alter_cleanup_field_values($values, $field_name);
      _comment_alter_cleanup_field_values($values, $field_name, '_old', 'comment_alter_');

      // If field values have changed, add it to the list.
      if ($values['comment_alter_fields']['comment_alter_' . $field_name . '_old'] != $values['comment_alter_fields'][$field_name]) {
        $changed_fields[$field_name] = $field_name;
      }
    }
    if (!empty($changed_fields)) {

      // Creating parent entity display form again rather than storing in cache
      // or something as it would take a lot of resources and cache invalidation
      // is another issue.
      $parent_entity = $form_state
        ->getFormObject()
        ->getEntity()
        ->getCommentedEntity();
      $parent_form_display = entity_get_form_display($parent_entity
        ->getEntityTypeId(), $parent_entity
        ->bundle(), 'default');

      // Build parent entity form display again and copy the values of
      // alterable fields from altered comment form into it, then save the form.
      $parent_form = [];
      $parent_form_state = new FormState();
      $parent_form_display
        ->buildForm($parent_entity, $parent_form, $parent_form_state);
      foreach ($original_value['comment_alter']['fields'] as $alterable_field) {
        $parent_form_state
          ->setValue($alterable_field, $original_value['comment_alter_fields'][$alterable_field]);
      }
      $parent_form_display
        ->extractFormValues($parent_entity, $parent_form, $parent_form_state);

      // If the parent entity does not support revisions, just save the changes
      // and return. For revisionable parent entities, create a new revision
      // with the appropriate user and date, then save both the old and new
      // revisions' ID for displaying the changes.
      if (!$parent_entity
        ->getEntityType()
        ->hasKey('revision')) {
        $parent_entity
          ->save();
        return;
      }
      $parent_entity
        ->setNewRevision(TRUE);
      if ($parent_entity instanceof \Drupal\Core\Entity\RevisionLogInterface) {
        $parent_entity
          ->setRevisionCreationTime(REQUEST_TIME);
        $parent_entity
          ->setRevisionUserId(\Drupal::currentUser()
          ->id());
      }
      $parent_entity
        ->save();

      // Store in comment_alter database table the old and new revision IDs
      // of the parent entity along with comment ID.
      $comment_alter = [
        'old_vid' => $values['comment_alter']['old_vid'],
        'new_vid' => $parent_entity
          ->getRevisionId(),
        'cid' => $original_value['cid'],
        'parent_entity_type' => $parent_entity
          ->getEntityTypeId(),
      ];
      db_insert('comment_alter')
        ->fields($comment_alter)
        ->execute();
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_load().
 */
function comment_alter_comment_load($comments) {
  $result = \Drupal::database()
    ->select('comment_alter', 'c')
    ->fields('c', array(
    'cid',
    'old_vid',
    'new_vid',
  ))
    ->condition('cid', array_keys($comments), 'IN')
    ->execute();
  foreach ($result as $row) {
    $comments[$row->cid]->comment_alter['old_vid'] = $row->old_vid;
    $comments[$row->cid]->comment_alter['new_vid'] = $row->new_vid;
  }
}

/**
 * Returns a table showing the differences committed with a particular comment.
 *
 * Uses the 'Diff' module to actually generate the differences.
 *
 * @param \Drupal\comment\CommentInterface $comment
 *   The comment object.
 *
 * @return array
 *   Table showing the differences made by the comment on the parent entity.
 */
function comment_alter_get_changed_fields(CommentInterface $comment) {
  $changed_fields = [];
  if (isset($comment->comment_alter) && isset($comment->comment_alter['new_vid'])) {
    $parent_entity = $comment
      ->getCommentedEntity();
    $entity_type = $parent_entity
      ->getEntityTypeId();
    $bundle = $parent_entity
      ->bundle();
    $entity_storage = \Drupal::entityTypeManager()
      ->getStorage($entity_type);
    $old_entity = $entity_storage
      ->loadRevision($comment->comment_alter['old_vid']);
    $new_entity = $entity_storage
      ->loadRevision($comment->comment_alter['new_vid']);
    $comment_alter_fields = comment_alter_get_alterable_fields($entity_type, $bundle);

    // Use diff module's function to generate the row-wise differences between
    // the two revisions.
    $container = \Drupal::getContainer();
    $entity_comparison = EntityComparisonBase::create($container);
    $fields = $entity_comparison
      ->compareRevisions($old_entity, $new_entity);

    // Build the diff rows for each field and append the field rows
    // to the changed field array as new and old values.
    $diff_rows = [];
    $filter = 'raw';
    foreach ($fields as $field_name => $field) {

      // Because of some core changes the format of $field_name changed. For
      // some entities it became 'EntityType.ActualFieldName' and for the rest
      // it was still the actual field name. Get the field name from both of
      // these cases.
      $field_name_array = explode('.', $field_name);
      $actual_field_name = $field_name_array[sizeof($field_name_array) - 1];
      if (isset($comment_alter_fields[$actual_field_name]) && !empty($field['#name'])) {
        $field_diff_rows = comment_alter_get_rows_diff($field['#states'][$filter]['#left'], $field['#states'][$filter]['#right']);

        // Hide the differences if user chooses to hide them.
        $parent_field = $parent_entity
          ->getFieldDefinition($actual_field_name);
        if ($parent_field
          ->getThirdPartySetting('comment_alter', 'comment_alter_hide', FALSE)) {
          $changes = t('Changes are hidden');
          if ($parent_entity
            ->getEntityTypeId() == 'node') {
            $url = Url::fromUserInput('/node/' . $parent_entity
              ->id() . '/revisions/view/' . $comment->comment_alter['old_vid'] . '/' . $comment->comment_alter['new_vid']);
            $changes = \Drupal\Core\Link::fromTextAndUrl(t('View Changes'), $url);
          }
          $changed_fields[] = [
            'name' => [
              'data' => [
                '#markup' => '<b>' . $field['#name'] . '</b>',
              ],
            ],
            'changes' => $changes,
          ];
          continue;
        }
        $line = 0;
        foreach ($field_diff_rows as $row) {
          $changed_fields[] = [
            'name' => $line ? '' : [
              'data' => [
                '#markup' => '<b>' . $field['#name'] . '</b>',
              ],
            ],
            'old' => [
              'data' => empty($row[1]['data']) ? '' : $row[1]['data'],
            ],
            'arrow' => [
              'data' => [
                '#markup' => '&Rightarrow;',
              ],
            ],
            'new' => [
              'data' => empty($row[3]['data']) ? '' : $row[3]['data'],
            ],
          ];
          $line++;
        }
      }
    }
  }
  if (!empty($changed_fields)) {
    return [
      '#type' => 'table',
      '#rows' => $changed_fields,
      '#attributes' => [
        'class' => [
          'comment-alter-diff',
        ],
      ],
    ];
  }
}

/**
 * Prepare the table rows for theme 'table'.
 *
 * @param string $a
 *   The source string to compare from.
 * @param string $b
 *   The target string to compare to.
 * @param boolean $show_header
 *   Display diff context headers. For example, "Line x".
 * @param array $line_stats
 *   This structure tracks line numbers across multiple calls to DiffFormatter.
 *
 * @return array
 *   Array of rows usable with theme('table').
 */
function comment_alter_get_rows_diff($a, $b, $show_header = FALSE, &$line_stats = NULL) {
  $a = is_array($a) ? $a : explode("\n", $a);
  $b = is_array($b) ? $b : explode("\n", $b);

  // Temporary workaround: when comparing with an empty string, Diff Component
  // returns a change OP instead of an add OP.
  if (count($a) == 1 && $a[0] == "") {
    $a = [];
  }
  if (!isset($line_stats)) {
    $line_stats = [
      'counter' => [
        'x' => 0,
        'y' => 0,
      ],
      'offset' => [
        'x' => 0,
        'y' => 0,
      ],
    ];
  }

  // Header is the line counter.
  $container = \Drupal::getContainer();
  $diff_formatter = $container
    ->get('diff.diff.formatter');
  $diff_formatter->show_header = $show_header;
  $diff = new Diff($a, $b);
  return $diff_formatter
    ->format($diff);
}

/**
 * Implements hook_comment_links_alter().
 */
function comment_alter_comment_links_alter(array &$links, CommentInterface $comment, array &$context) {

  // Since Diff module shows the differences for nodes only so create diff link
  // for nodes.
  $parent_entity = $comment
    ->getCommentedEntity();
  if ($parent_entity
    ->getEntityTypeId() == 'node') {
    $comment_field = $parent_entity
      ->getFieldDefinition($comment
      ->getFieldName());
    if (!$comment_field
      ->getThirdPartySetting('comment_alter', 'comment_alter_diff_link', FALSE)) {
      return;
    }
    $links['comment_alter'] = [
      '#attributes' => [
        'class' => [
          'links',
          'inline',
        ],
      ],
      '#links' => [
        'comment-diff' => [
          'title' => t('Diff'),
          'url' => Url::fromUserInput('/node/' . $parent_entity
            ->id() . '/revisions/view/' . $comment->comment_alter['old_vid'] . '/' . $comment->comment_alter['new_vid']),
        ],
      ],
    ];
  }
}

/**
 * Implements hook_ENTITY_TYPE_view().
 */
function comment_alter_comment_view(&$build, EntityInterface $comment, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode) {

  // Show the differences irrespective of the view modes.
  if ($display
    ->getComponent('comment_alter')) {
    $build['#attached']['library'][] = 'comment_alter/comment_alter.diff';
    $build['comment_alter'] = comment_alter_get_changed_fields($comment);
  }
}

/**
 * Implements hook_ENTITY_TYPE_delete().
 */
function comment_alter_comment_delete(EntityInterface $comment) {
  \Drupal::database()
    ->delete('comment_alter')
    ->condition('cid', $comment
    ->id())
    ->execute();
}

/**
 * Implements hook_entity_revision_delete().
 */
function comment_alter_entity_revision_delete(EntityInterface $entity) {

  // Delete entries from the comment_alter table when a parent entity revision
  // is deleted.
  $conditions = new \Drupal\Core\Database\Query\Condition('OR');
  if ($entity
    ->getEntityType()
    ->hasKey('revision')) {
    \Drupal::database()
      ->delete('comment_alter')
      ->condition('parent_entity_type', $entity
      ->getEntityTypeId())
      ->condition($conditions
      ->condition('old_vid', $entity
      ->getRevisionId())
      ->condition('new_vid', $entity
      ->getRevisionId()))
      ->execute();
  }
}

/**
 * Implements hook_entity_extra_field_info().
 */
function comment_alter_entity_extra_field_info() {
  $extra = [];
  $comment_bundles = [];
  foreach (\Drupal::entityManager()
    ->getDefinitions() as $entity_type => $entity_definition) {

    // Handle only non-comment content entity types.
    if (!$entity_definition instanceof Drupal\Core\Entity\ContentEntityTypeInterface || $entity_type == 'comment') {
      continue;
    }

    // Store the comment type or the comment bundle using FieldStorageDefinition
    // associated with this entity type, which can be accessed for any of its
    // bundle using the field machine name.
    $field_storage_definitions = \Drupal::entityManager()
      ->getFieldStorageDefinitions($entity_type);
    foreach ($field_storage_definitions as $field_name => $field_storage_definition) {
      if (!$field_storage_definition instanceof Drupal\Core\Field\BaseFieldDefinition && $field_storage_definition
        ->getType() == 'comment') {
        $comment_bundles[$entity_type][$field_name] = $field_storage_definition
          ->getSetting('comment_type');
        $weight[$comment_bundles[$entity_type][$field_name]] = 0;
      }
    }

    // No need to further processing if there are no comment fields on this
    // content entity bundle.
    if (empty($comment_bundles[$entity_type])) {
      continue;
    }
    foreach (entity_get_bundles($entity_type) as $bundle => $bundle_info) {

      // Retrieve the list of comment_alterable_fields for this entity_type's
      // current bundle.
      $comment_alterable_fields = comment_alter_get_alterable_fields($entity_type, $bundle);

      // Skip to the next bundle if there are no comment_alterable_fields.
      if (empty($comment_alterable_fields)) {
        continue;
      }

      // If this content entity type and bundle has any comment_alterable
      // field, then _ALL_ the comment types (bundles) should have the extra
      // field info on them (about _ALL_ these comment_alterable fields). But:
      // only those comment types (bundles) should have this info, which are
      // attached to this content entity type and bundle.
      // To retrieve the list of comment types (bundles) attached to this
      // entity type and bundle, we'll use the above created comment_bundles
      // array. So, we need to store all the comment fields for this entity type
      // and bundle in order to get comment bundle for the corresponding field.
      $comment_fields = [];
      $field_definitions = \Drupal::entityManager()
        ->getFieldDefinitions($entity_type, $bundle);
      foreach ($field_definitions as $field_name => $field_definition) {
        if ($field_definition
          ->getType() == 'comment') {
          $comment_fields[] = $field_name;
        }
      }

      // Now that we have all the info needed, let's build the extra field
      // info array.
      foreach ($comment_fields as $comment_field) {
        foreach ($comment_alterable_fields as $field_name) {
          $field = FieldConfig::loadByName($entity_type, $bundle, $field_name);
          $extra['comment'][$comment_bundles[$entity_type][$comment_field]]['form'][$entity_type . '_' . $bundle . '_' . 'comment_alter_' . $field_name] = [
            'label' => t('Comment alter: %type . %bundle : %label', [
              '%type' => $entity_type,
              '%bundle' => $bundle,
              '%label' => $field
                ->getLabel(),
            ]),
            'description' => $field
              ->getDescription(),
            'weight' => $weight[$comment_bundles[$entity_type][$comment_field]],
          ];
          $weight[$comment_bundles[$entity_type][$comment_field]]++;
        }
        $extra['comment'][$comment_bundles[$entity_type][$comment_field]]['display']['comment_alter'] = [
          'label' => t('Comment changes'),
          'description' => t('Changes made to the parent node\'s fields in this comment.'),
          'weight' => -1,
        ];
      }
    }
  }
  return $extra;
}

Functions

Namesort descending Description
comment_alter_cmp usort comparison function.
comment_alter_comment_delete Implements hook_ENTITY_TYPE_delete().
comment_alter_comment_links_alter Implements hook_comment_links_alter().
comment_alter_comment_load Implements hook_ENTITY_TYPE_load().
comment_alter_comment_view Implements hook_ENTITY_TYPE_view().
comment_alter_entity_extra_field_info Implements hook_entity_extra_field_info().
comment_alter_entity_revision_delete Implements hook_entity_revision_delete().
comment_alter_form_comment_form_alter Implements hook_form_BASE_FORM_ID_alter().
comment_alter_form_comment_form_alter_after_build Custom #after_build callback for comment_form to add comment alterable columns info and values.
comment_alter_form_comment_form_alter_submit Submit callback for the altered comment form.
comment_alter_form_comment_form_alter_validate Validation callback for the altered comment form.
comment_alter_form_field_config_edit_form_alter Implements hook_form_FORM_ID_alter().
comment_alter_get_alterable_fields Returns the comment alterable fields for an entity.
comment_alter_get_changed_fields Returns a table showing the differences committed with a particular comment.
comment_alter_get_rows_diff Prepare the table rows for theme 'table'.
_comment_alter_cleanup_arrays Helper function to recursively clean up semi-empty arrays.
_comment_alter_cleanup_field_values Helper function to clean up field values for comparison.