|
@@ -0,0 +1,359 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+/**
|
|
|
+ * @file
|
|
|
+ * Definition of Drupal\comment\WingStorageController.
|
|
|
+ */
|
|
|
+
|
|
|
+namespace Drupal\comment;
|
|
|
+
|
|
|
+use Drupal\Core\Entity\EntityInterface;
|
|
|
+use Drupal\Core\Entity\DatabaseStorageControllerNG;
|
|
|
+use Drupal\Component\Uuid\Uuid;
|
|
|
+use LogicException;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Defines the controller class for comments.
|
|
|
+ *
|
|
|
+ * This extends the Drupal\Core\Entity\DatabaseStorageController class, adding
|
|
|
+ * required special handling for comment entities.
|
|
|
+ */
|
|
|
+class WingStorageController extends DatabaseStorageControllerNG {
|
|
|
+ /**
|
|
|
+ * The thread for which a lock was acquired.
|
|
|
+ */
|
|
|
+ protected $threadLock = '';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Overrides Drupal\Core\Entity\DatabaseStorageController::buildQuery().
|
|
|
+ */
|
|
|
+ protected function buildQuery($ids, $revision_id = FALSE) {
|
|
|
+ $query = parent::buildQuery($ids, $revision_id);
|
|
|
+ // Specify additional fields from the user and node tables.
|
|
|
+ $query->innerJoin('node', 'n', 'base.nid = n.nid');
|
|
|
+ $query->addField('n', 'type', 'node_type');
|
|
|
+ $query->innerJoin('users', 'u', 'base.uid = u.uid');
|
|
|
+ // @todo: Move to a computed 'name' field instead.
|
|
|
+ $query->addField('u', 'name', 'registered_name');
|
|
|
+ return $query;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Overrides Drupal\Core\Entity\DatabaseStorageController::attachLoad().
|
|
|
+ */
|
|
|
+ protected function attachLoad(&$records, $load_revision = FALSE) {
|
|
|
+ // Prepare standard comment fields.
|
|
|
+ foreach ($records as $key => $record) {
|
|
|
+ $record->name = $record->uid ? $record->registered_name : $record->name;
|
|
|
+ $record->node_type = 'comment_node_' . $record->node_type;
|
|
|
+ $records[$key] = $record;
|
|
|
+ }
|
|
|
+ parent::attachLoad($records, $load_revision);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Overrides Drupal\Core\Entity\DatabaseStorageControllerNG::create().
|
|
|
+ */
|
|
|
+ public function create(array $values) {
|
|
|
+ if (empty($values['node_type']) && !empty($values['nid'])) {
|
|
|
+ $node = node_load(is_object($values['nid']) ? $values['nid']->value : $values['nid']);
|
|
|
+ $values['node_type'] = 'comment_node_' . $node->type;
|
|
|
+ }
|
|
|
+ return parent::create($values);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Overrides Drupal\Core\Entity\DatabaseStorageController::preSave().
|
|
|
+ *
|
|
|
+ * @see comment_int_to_alphadecimal()
|
|
|
+ * @see comment_alphadecimal_to_int()
|
|
|
+ */
|
|
|
+ protected function preSave(EntityInterface $comment) {
|
|
|
+ global $user;
|
|
|
+
|
|
|
+ if (!isset($comment->status->value)) {
|
|
|
+ $comment->status->value = user_access('skip comment approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED;
|
|
|
+ }
|
|
|
+ // Make sure we have a proper bundle name.
|
|
|
+ if (!isset($comment->node_type->value)) {
|
|
|
+ $comment->node_type->value = 'comment_node_' . $comment->nid->entity->type;
|
|
|
+ }
|
|
|
+ if ($comment->isNew()) {
|
|
|
+ // Add the comment to database. This next section builds the thread field.
|
|
|
+ // Also see the documentation for comment_view().
|
|
|
+ if (!empty($comment->thread->value)) {
|
|
|
+ // Allow calling code to set thread itself.
|
|
|
+ $thread = $comment->thread->value;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ if ($this->threadLock) {
|
|
|
+ // As preSave() is protected, this can only happen when this class
|
|
|
+ // is extended in a faulty manner.
|
|
|
+ throw new LogicException('preSave is called again without calling postSave() or releaseThreadLock()');
|
|
|
+ }
|
|
|
+ if ($comment->pid->target_id == 0) {
|
|
|
+ // This is a comment with no parent comment (depth 0): we start
|
|
|
+ // by retrieving the maximum thread level.
|
|
|
+ $max = db_query('SELECT MAX(thread) FROM {comment} WHERE nid = :nid', array(':nid' => $comment->nid->target_id))->fetchField();
|
|
|
+ // Strip the "/" from the end of the thread.
|
|
|
+ $max = rtrim($max, '/');
|
|
|
+ // We need to get the value at the correct depth.
|
|
|
+ $parts = explode('.', $max);
|
|
|
+ $n = comment_alphadecimal_to_int($parts[0]);
|
|
|
+ $prefix = '';
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // This is a comment with a parent comment, so increase the part of
|
|
|
+ // the thread value at the proper depth.
|
|
|
+
|
|
|
+ // Get the parent comment:
|
|
|
+ $parent = $comment->pid->entity;
|
|
|
+ // Strip the "/" from the end of the parent thread.
|
|
|
+ $parent->thread->value = (string) rtrim((string) $parent->thread->value, '/');
|
|
|
+ $prefix = $parent->thread->value . '.';
|
|
|
+ // Get the max value in *this* thread.
|
|
|
+ $max = db_query("SELECT MAX(thread) FROM {comment} WHERE thread LIKE :thread AND nid = :nid", array(
|
|
|
+ ':thread' => $parent->thread->value . '.%',
|
|
|
+ ':nid' => $comment->nid->target_id,
|
|
|
+ ))->fetchField();
|
|
|
+
|
|
|
+ if ($max == '') {
|
|
|
+ // First child of this parent. As the other two cases do an
|
|
|
+ // increment of the thread number before creating the thread
|
|
|
+ // string set this to -1 so it requires an increment too.
|
|
|
+ $n = -1;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // Strip the "/" at the end of the thread.
|
|
|
+ $max = rtrim($max, '/');
|
|
|
+ // Get the value at the correct depth.
|
|
|
+ $parts = explode('.', $max);
|
|
|
+ $parent_depth = count(explode('.', $parent->thread->value));
|
|
|
+ $n = comment_alphadecimal_to_int($parts[$parent_depth]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Finally, build the thread field for this new comment. To avoid
|
|
|
+ // race conditions, get a lock on the thread. If aother process already
|
|
|
+ // has the lock, just move to the next integer.
|
|
|
+ do {
|
|
|
+ $thread = $prefix . comment_int_to_alphadecimal(++$n) . '/';
|
|
|
+ } while (!lock()->acquire("comment:{$comment->nid->target_id}:$thread"));
|
|
|
+ $this->threadLock = $thread;
|
|
|
+ }
|
|
|
+ if (empty($comment->created->value)) {
|
|
|
+ $comment->created->value = REQUEST_TIME;
|
|
|
+ }
|
|
|
+ if (empty($comment->changed->value)) {
|
|
|
+ $comment->changed->value = $comment->created->value;
|
|
|
+ }
|
|
|
+ // We test the value with '===' because we need to modify anonymous
|
|
|
+ // users as well.
|
|
|
+ if ($comment->uid->target_id === $user->uid && $user->uid) {
|
|
|
+ $comment->name->value = $user->name;
|
|
|
+ }
|
|
|
+ // Add the values which aren't passed into the function.
|
|
|
+ $comment->thread->value = $thread;
|
|
|
+ $comment->hostname->value = ip_address();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave().
|
|
|
+ */
|
|
|
+ protected function postSave(EntityInterface $comment, $update) {
|
|
|
+ $this->releaseThreadLock();
|
|
|
+ // Update the {node_comment_statistics} table prior to executing the hook.
|
|
|
+ $this->updateNodeStatistics($comment->nid->target_id);
|
|
|
+ if ($comment->status->value == COMMENT_PUBLISHED) {
|
|
|
+ module_invoke_all('comment_publish', $comment);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete().
|
|
|
+ */
|
|
|
+ protected function postDelete($comments) {
|
|
|
+ // Delete the comments' replies.
|
|
|
+ $query = db_select('comment', 'c')
|
|
|
+ ->fields('c', array('cid'))
|
|
|
+ ->condition('pid', array(array_keys($comments)), 'IN');
|
|
|
+ $child_cids = $query->execute()->fetchCol();
|
|
|
+ comment_delete_multiple($child_cids);
|
|
|
+
|
|
|
+ foreach ($comments as $comment) {
|
|
|
+ $this->updateNodeStatistics($comment->nid->target_id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Updates the comment statistics for a given node.
|
|
|
+ *
|
|
|
+ * The {node_comment_statistics} table has the following fields:
|
|
|
+ * - last_comment_timestamp: The timestamp of the last comment for this node,
|
|
|
+ * or the node created timestamp if no comments exist for the node.
|
|
|
+ * - last_comment_name: The name of the anonymous poster for the last comment.
|
|
|
+ * - last_comment_uid: The user ID of the poster for the last comment for
|
|
|
+ * this node, or the node author's user ID if no comments exist for the
|
|
|
+ * node.
|
|
|
+ * - comment_count: The total number of approved/published comments on this
|
|
|
+ * node.
|
|
|
+ *
|
|
|
+ * @param $nid
|
|
|
+ * The node ID.
|
|
|
+ */
|
|
|
+ protected function updateNodeStatistics($nid) {
|
|
|
+ // Allow bulk updates and inserts to temporarily disable the
|
|
|
+ // maintenance of the {node_comment_statistics} table.
|
|
|
+ if (!variable_get('comment_maintain_node_statistics', TRUE)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $count = db_query('SELECT COUNT(cid) FROM {comment} WHERE nid = :nid AND status = :status', array(
|
|
|
+ ':nid' => $nid,
|
|
|
+ ':status' => COMMENT_PUBLISHED,
|
|
|
+ ))->fetchField();
|
|
|
+
|
|
|
+ if ($count > 0) {
|
|
|
+ // Comments exist.
|
|
|
+ $last_reply = db_query_range('SELECT cid, name, changed, uid FROM {comment} WHERE nid = :nid AND status = :status ORDER BY cid DESC', 0, 1, array(
|
|
|
+ ':nid' => $nid,
|
|
|
+ ':status' => COMMENT_PUBLISHED,
|
|
|
+ ))->fetchObject();
|
|
|
+ db_update('node_comment_statistics')
|
|
|
+ ->fields(array(
|
|
|
+ 'cid' => $last_reply->cid,
|
|
|
+ 'comment_count' => $count,
|
|
|
+ 'last_comment_timestamp' => $last_reply->changed,
|
|
|
+ 'last_comment_name' => $last_reply->uid ? '' : $last_reply->name,
|
|
|
+ 'last_comment_uid' => $last_reply->uid,
|
|
|
+ ))
|
|
|
+ ->condition('nid', $nid)
|
|
|
+ ->execute();
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // Comments do not exist.
|
|
|
+ $node = db_query('SELECT uid, created FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject();
|
|
|
+ db_update('node_comment_statistics')
|
|
|
+ ->fields(array(
|
|
|
+ 'cid' => 0,
|
|
|
+ 'comment_count' => 0,
|
|
|
+ 'last_comment_timestamp' => $node->created,
|
|
|
+ 'last_comment_name' => '',
|
|
|
+ 'last_comment_uid' => $node->uid,
|
|
|
+ ))
|
|
|
+ ->condition('nid', $nid)
|
|
|
+ ->execute();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Release the lock acquired for the thread in preSave().
|
|
|
+ */
|
|
|
+ protected function releaseThreadLock() {
|
|
|
+ if ($this->threadLock) {
|
|
|
+ lock()->release($this->threadLock);
|
|
|
+ $this->threadLock = '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Implements \Drupal\Core\Entity\DataBaseStorageControllerNG::basePropertyDefinitions().
|
|
|
+ */
|
|
|
+ public function baseFieldDefinitions() {
|
|
|
+ $properties['cid'] = array(
|
|
|
+ 'label' => t('ID'),
|
|
|
+ 'description' => t('The comment ID.'),
|
|
|
+ 'type' => 'integer_field',
|
|
|
+ 'read-only' => TRUE,
|
|
|
+ );
|
|
|
+ $properties['uuid'] = array(
|
|
|
+ 'label' => t('UUID'),
|
|
|
+ 'description' => t('The comment UUID.'),
|
|
|
+ 'type' => 'string_field',
|
|
|
+ );
|
|
|
+ $properties['pid'] = array(
|
|
|
+ 'label' => t('Parent ID'),
|
|
|
+ 'description' => t('The parent comment ID if this is a reply to a comment.'),
|
|
|
+ 'type' => 'entity_reference_field',
|
|
|
+ 'settings' => array('target_type' => 'comment'),
|
|
|
+ );
|
|
|
+ $properties['nid'] = array(
|
|
|
+ 'label' => t('Node ID'),
|
|
|
+ 'description' => t('The ID of the node of which this comment is a reply.'),
|
|
|
+ 'type' => 'entity_reference_field',
|
|
|
+ 'settings' => array('target_type' => 'node'),
|
|
|
+ 'required' => TRUE,
|
|
|
+ );
|
|
|
+ $properties['langcode'] = array(
|
|
|
+ 'label' => t('Language code'),
|
|
|
+ 'description' => t('The comment language code.'),
|
|
|
+ 'type' => 'language_field',
|
|
|
+ );
|
|
|
+ $properties['subject'] = array(
|
|
|
+ 'label' => t('Subject'),
|
|
|
+ 'description' => t('The comment title or subject.'),
|
|
|
+ 'type' => 'string_field',
|
|
|
+ );
|
|
|
+ $properties['uid'] = array(
|
|
|
+ 'label' => t('User ID'),
|
|
|
+ 'description' => t('The user ID of the comment author.'),
|
|
|
+ 'type' => 'entity_reference_field',
|
|
|
+ 'settings' => array('target_type' => 'user'),
|
|
|
+ );
|
|
|
+ $properties['name'] = array(
|
|
|
+ 'label' => t('Name'),
|
|
|
+ 'description' => t("The comment author's name."),
|
|
|
+ 'type' => 'string_field',
|
|
|
+ );
|
|
|
+ $properties['mail'] = array(
|
|
|
+ 'label' => t('e-mail'),
|
|
|
+ 'description' => t("The comment author's e-mail address."),
|
|
|
+ 'type' => 'string_field',
|
|
|
+ );
|
|
|
+ $properties['homepage'] = array(
|
|
|
+ 'label' => t('Homepage'),
|
|
|
+ 'description' => t("The comment author's home page address."),
|
|
|
+ 'type' => 'string_field',
|
|
|
+ );
|
|
|
+ $properties['hostname'] = array(
|
|
|
+ 'label' => t('Hostname'),
|
|
|
+ 'description' => t("The comment author's hostname."),
|
|
|
+ 'type' => 'string_field',
|
|
|
+ );
|
|
|
+ $properties['created'] = array(
|
|
|
+ 'label' => t('Created'),
|
|
|
+ 'description' => t('The time that the comment was created.'),
|
|
|
+ 'type' => 'integer_field',
|
|
|
+ );
|
|
|
+ $properties['changed'] = array(
|
|
|
+ 'label' => t('Changed'),
|
|
|
+ 'description' => t('The time that the comment was last edited.'),
|
|
|
+ 'type' => 'integer_field',
|
|
|
+ );
|
|
|
+ $properties['status'] = array(
|
|
|
+ 'label' => t('Publishing status'),
|
|
|
+ 'description' => t('A boolean indicating whether the comment is published.'),
|
|
|
+ 'type' => 'boolean_field',
|
|
|
+ );
|
|
|
+ $properties['thread'] = array(
|
|
|
+ 'label' => t('Thread place'),
|
|
|
+ 'description' => t("The alphadecimal representation of the comment's place in a thread, consisting of a base 36 string prefixed by an integer indicating its length."),
|
|
|
+ 'type' => 'string_field',
|
|
|
+ );
|
|
|
+ $properties['node_type'] = array(
|
|
|
+ // @todo: The bundle property should be stored so it's queryable.
|
|
|
+ 'label' => t('Node type'),
|
|
|
+ 'description' => t("The comment node type."),
|
|
|
+ 'type' => 'string_field',
|
|
|
+ 'queryable' => FALSE,
|
|
|
+ );
|
|
|
+ $properties['new'] = array(
|
|
|
+ 'label' => t('Comment new marker'),
|
|
|
+ 'description' => t("The comment 'new' marker for the current user (0 read, 1 new, 2 updated)."),
|
|
|
+ 'type' => 'integer_field',
|
|
|
+ 'computed' => TRUE,
|
|
|
+ 'class' => '\Drupal\comment\FieldNewItem',
|
|
|
+ );
|
|
|
+ return $properties;
|
|
|
+ }
|
|
|
+}
|