Chris@0: isNew()) { Chris@0: // Add the comment to database. This next section builds the thread field. Chris@0: // @see \Drupal\comment\CommentViewBuilder::buildComponents() Chris@0: $thread = $this->getThread(); Chris@0: if (empty($thread)) { Chris@0: if ($this->threadLock) { Chris@0: // Thread lock was not released after being set previously. Chris@0: // This suggests there's a bug in code using this class. Chris@0: throw new \LogicException('preSave() is called again without calling postSave() or releaseThreadLock()'); Chris@0: } Chris@0: if (!$this->hasParentComment()) { Chris@0: // This is a comment with no parent comment (depth 0): we start Chris@0: // by retrieving the maximum thread level. Chris@0: $max = $storage->getMaxThread($this); Chris@0: // Strip the "/" from the end of the thread. Chris@0: $max = rtrim($max, '/'); Chris@0: // We need to get the value at the correct depth. Chris@0: $parts = explode('.', $max); Chris@0: $n = Number::alphadecimalToInt($parts[0]); Chris@0: $prefix = ''; Chris@0: } Chris@0: else { Chris@0: // This is a comment with a parent comment, so increase the part of Chris@0: // the thread value at the proper depth. Chris@0: Chris@0: // Get the parent comment: Chris@0: $parent = $this->getParentComment(); Chris@0: // Strip the "/" from the end of the parent thread. Chris@0: $parent->setThread((string) rtrim((string) $parent->getThread(), '/')); Chris@0: $prefix = $parent->getThread() . '.'; Chris@0: // Get the max value in *this* thread. Chris@0: $max = $storage->getMaxThreadPerThread($this); Chris@0: Chris@0: if ($max == '') { Chris@0: // First child of this parent. As the other two cases do an Chris@0: // increment of the thread number before creating the thread Chris@0: // string set this to -1 so it requires an increment too. Chris@0: $n = -1; Chris@0: } Chris@0: else { Chris@0: // Strip the "/" at the end of the thread. Chris@0: $max = rtrim($max, '/'); Chris@0: // Get the value at the correct depth. Chris@0: $parts = explode('.', $max); Chris@0: $parent_depth = count(explode('.', $parent->getThread())); Chris@0: $n = Number::alphadecimalToInt($parts[$parent_depth]); Chris@0: } Chris@0: } Chris@0: // Finally, build the thread field for this new comment. To avoid Chris@0: // race conditions, get a lock on the thread. If another process already Chris@0: // has the lock, just move to the next integer. Chris@0: do { Chris@0: $thread = $prefix . Number::intToAlphadecimal(++$n) . '/'; Chris@0: $lock_name = "comment:{$this->getCommentedEntityId()}:$thread"; Chris@0: } while (!\Drupal::lock()->acquire($lock_name)); Chris@0: $this->threadLock = $lock_name; Chris@0: } Chris@0: $this->setThread($thread); Chris@0: } Chris@0: // The entity fields for name and mail have no meaning if the user is not Chris@0: // Anonymous. Set them to NULL to make it clearer that they are not used. Chris@0: // For anonymous users see \Drupal\comment\CommentForm::form() for mail, Chris@0: // and \Drupal\comment\CommentForm::buildEntity() for name setting. Chris@0: if (!$this->getOwner()->isAnonymous()) { Chris@0: $this->set('name', NULL); Chris@0: $this->set('mail', NULL); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function postSave(EntityStorageInterface $storage, $update = TRUE) { Chris@0: parent::postSave($storage, $update); Chris@0: Chris@0: // Always invalidate the cache tag for the commented entity. Chris@0: if ($commented_entity = $this->getCommentedEntity()) { Chris@0: Cache::invalidateTags($commented_entity->getCacheTagsToInvalidate()); Chris@0: } Chris@0: Chris@0: $this->releaseThreadLock(); Chris@0: // Update the {comment_entity_statistics} table prior to executing the hook. Chris@0: \Drupal::service('comment.statistics')->update($this); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Release the lock acquired for the thread in preSave(). Chris@0: */ Chris@0: protected function releaseThreadLock() { Chris@0: if ($this->threadLock) { Chris@0: \Drupal::lock()->release($this->threadLock); Chris@0: $this->threadLock = ''; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function postDelete(EntityStorageInterface $storage, array $entities) { Chris@0: parent::postDelete($storage, $entities); Chris@0: Chris@0: $child_cids = $storage->getChildCids($entities); Chris@0: entity_delete_multiple('comment', $child_cids); Chris@0: Chris@0: foreach ($entities as $id => $entity) { Chris@0: \Drupal::service('comment.statistics')->update($entity); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function referencedEntities() { Chris@0: $referenced_entities = parent::referencedEntities(); Chris@0: if ($this->getCommentedEntityId()) { Chris@0: $referenced_entities[] = $this->getCommentedEntity(); Chris@0: } Chris@0: return $referenced_entities; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function permalink() { Chris@18: $uri = $this->toUrl(); Chris@0: $uri->setOption('fragment', 'comment-' . $this->id()); Chris@0: return $uri; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { Chris@0: /** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */ Chris@0: $fields = parent::baseFieldDefinitions($entity_type); Chris@0: $fields += static::publishedBaseFieldDefinitions($entity_type); Chris@18: $fields += static::ownerBaseFieldDefinitions($entity_type); Chris@0: Chris@0: $fields['cid']->setLabel(t('Comment ID')) Chris@0: ->setDescription(t('The comment ID.')); Chris@0: Chris@0: $fields['uuid']->setDescription(t('The comment UUID.')); Chris@0: Chris@0: $fields['comment_type']->setLabel(t('Comment Type')) Chris@0: ->setDescription(t('The comment type.')); Chris@0: Chris@0: $fields['langcode']->setDescription(t('The comment language code.')); Chris@0: Chris@0: // Set the default value callback for the status field. Chris@0: $fields['status']->setDefaultValueCallback('Drupal\comment\Entity\Comment::getDefaultStatus'); Chris@0: Chris@0: $fields['pid'] = BaseFieldDefinition::create('entity_reference') Chris@0: ->setLabel(t('Parent ID')) Chris@0: ->setDescription(t('The parent comment ID if this is a reply to a comment.')) Chris@0: ->setSetting('target_type', 'comment'); Chris@0: Chris@0: $fields['entity_id'] = BaseFieldDefinition::create('entity_reference') Chris@0: ->setLabel(t('Entity ID')) Chris@0: ->setDescription(t('The ID of the entity of which this comment is a reply.')) Chris@0: ->setRequired(TRUE); Chris@0: Chris@0: $fields['subject'] = BaseFieldDefinition::create('string') Chris@0: ->setLabel(t('Subject')) Chris@0: ->setTranslatable(TRUE) Chris@0: ->setSetting('max_length', 64) Chris@0: ->setDisplayOptions('form', [ Chris@0: 'type' => 'string_textfield', Chris@0: // Default comment body field has weight 20. Chris@0: 'weight' => 10, Chris@0: ]) Chris@0: ->setDisplayConfigurable('form', TRUE); Chris@0: Chris@18: $fields['uid'] Chris@18: ->setDescription(t('The user ID of the comment author.')); Chris@0: Chris@0: $fields['name'] = BaseFieldDefinition::create('string') Chris@0: ->setLabel(t('Name')) Chris@0: ->setDescription(t("The comment author's name.")) Chris@0: ->setTranslatable(TRUE) Chris@0: ->setSetting('max_length', 60) Chris@0: ->setDefaultValue(''); Chris@0: Chris@0: $fields['mail'] = BaseFieldDefinition::create('email') Chris@0: ->setLabel(t('Email')) Chris@0: ->setDescription(t("The comment author's email address.")) Chris@0: ->setTranslatable(TRUE); Chris@0: Chris@0: $fields['homepage'] = BaseFieldDefinition::create('uri') Chris@0: ->setLabel(t('Homepage')) Chris@0: ->setDescription(t("The comment author's home page address.")) Chris@0: ->setTranslatable(TRUE) Chris@0: // URIs are not length limited by RFC 2616, but we can only store 255 Chris@0: // characters in our comment DB schema. Chris@0: ->setSetting('max_length', 255); Chris@0: Chris@0: $fields['hostname'] = BaseFieldDefinition::create('string') Chris@0: ->setLabel(t('Hostname')) Chris@0: ->setDescription(t("The comment author's hostname.")) Chris@0: ->setTranslatable(TRUE) Chris@17: ->setSetting('max_length', 128) Chris@17: ->setDefaultValueCallback(static::class . '::getDefaultHostname'); Chris@0: Chris@0: $fields['created'] = BaseFieldDefinition::create('created') Chris@0: ->setLabel(t('Created')) Chris@0: ->setDescription(t('The time that the comment was created.')) Chris@0: ->setTranslatable(TRUE); Chris@0: Chris@0: $fields['changed'] = BaseFieldDefinition::create('changed') Chris@0: ->setLabel(t('Changed')) Chris@0: ->setDescription(t('The time that the comment was last edited.')) Chris@0: ->setTranslatable(TRUE); Chris@0: Chris@0: $fields['thread'] = BaseFieldDefinition::create('string') Chris@0: ->setLabel(t('Thread place')) Chris@0: ->setDescription(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.")) Chris@0: ->setSetting('max_length', 255); Chris@0: Chris@0: $fields['entity_type'] = BaseFieldDefinition::create('string') Chris@0: ->setLabel(t('Entity type')) Chris@18: ->setRequired(TRUE) Chris@0: ->setDescription(t('The entity type to which this comment is attached.')) Chris@0: ->setSetting('is_ascii', TRUE) Chris@0: ->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH); Chris@0: Chris@0: $fields['field_name'] = BaseFieldDefinition::create('string') Chris@0: ->setLabel(t('Comment field name')) Chris@18: ->setRequired(TRUE) Chris@0: ->setDescription(t('The field name through which this comment was added.')) Chris@0: ->setSetting('is_ascii', TRUE) Chris@0: ->setSetting('max_length', FieldStorageConfig::NAME_MAX_LENGTH); Chris@0: Chris@0: return $fields; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@18: public static function getDefaultEntityOwner() { Chris@18: return 0; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@0: public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) { Chris@0: if ($comment_type = CommentType::load($bundle)) { Chris@0: $fields['entity_id'] = clone $base_field_definitions['entity_id']; Chris@0: $fields['entity_id']->setSetting('target_type', $comment_type->getTargetEntityTypeId()); Chris@0: return $fields; Chris@0: } Chris@0: return []; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function hasParentComment() { Chris@0: return (bool) $this->get('pid')->target_id; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getParentComment() { Chris@0: return $this->get('pid')->entity; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCommentedEntity() { Chris@0: return $this->get('entity_id')->entity; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCommentedEntityId() { Chris@0: return $this->get('entity_id')->target_id; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCommentedEntityTypeId() { Chris@0: return $this->get('entity_type')->value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setFieldName($field_name) { Chris@0: $this->set('field_name', $field_name); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getFieldName() { Chris@0: return $this->get('field_name')->value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getSubject() { Chris@0: return $this->get('subject')->value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setSubject($subject) { Chris@0: $this->set('subject', $subject); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getAuthorName() { Chris@0: if ($this->get('uid')->target_id) { Chris@0: return $this->get('uid')->entity->label(); Chris@0: } Chris@0: return $this->get('name')->value ?: \Drupal::config('user.settings')->get('anonymous'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setAuthorName($name) { Chris@0: $this->set('name', $name); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getAuthorEmail() { Chris@0: $mail = $this->get('mail')->value; Chris@0: Chris@0: if ($this->get('uid')->target_id != 0) { Chris@0: $mail = $this->get('uid')->entity->getEmail(); Chris@0: } Chris@0: Chris@0: return $mail; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getHomepage() { Chris@0: return $this->get('homepage')->value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setHomepage($homepage) { Chris@0: $this->set('homepage', $homepage); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getHostname() { Chris@0: return $this->get('hostname')->value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setHostname($hostname) { Chris@0: $this->set('hostname', $hostname); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCreatedTime() { Chris@0: if (isset($this->get('created')->value)) { Chris@0: return $this->get('created')->value; Chris@0: } Chris@0: return NULL; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setCreatedTime($created) { Chris@0: $this->set('created', $created); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getStatus() { Chris@0: return $this->get('status')->value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getThread() { Chris@0: $thread = $this->get('thread'); Chris@0: if (!empty($thread->value)) { Chris@0: return $thread->value; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setThread($thread) { Chris@0: $this->set('thread', $thread); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function preCreate(EntityStorageInterface $storage, array &$values) { Chris@0: if (empty($values['comment_type']) && !empty($values['field_name']) && !empty($values['entity_type'])) { Chris@0: $field_storage = FieldStorageConfig::loadByName($values['entity_type'], $values['field_name']); Chris@0: $values['comment_type'] = $field_storage->getSetting('comment_type'); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getOwner() { Chris@0: $user = $this->get('uid')->entity; Chris@0: if (!$user || $user->isAnonymous()) { Chris@0: $user = User::getAnonymousUser(); Chris@0: $user->name = $this->getAuthorName(); Chris@0: $user->homepage = $this->getHomepage(); Chris@0: } Chris@0: return $user; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the comment type ID for this comment. Chris@0: * Chris@0: * @return string Chris@0: * The ID of the comment type. Chris@0: */ Chris@0: public function getTypeId() { Chris@0: return $this->bundle(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Default value callback for 'status' base field definition. Chris@0: * Chris@0: * @see ::baseFieldDefinitions() Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if the comment should be published, FALSE otherwise. Chris@0: */ Chris@0: public static function getDefaultStatus() { Chris@0: return \Drupal::currentUser()->hasPermission('skip comment approval') ? CommentInterface::PUBLISHED : CommentInterface::NOT_PUBLISHED; Chris@0: } Chris@0: Chris@17: /** Chris@17: * Returns the default value for entity hostname base field. Chris@17: * Chris@17: * @return string Chris@17: * The client host name. Chris@17: */ Chris@17: public static function getDefaultHostname() { Chris@18: if (\Drupal::config('comment.settings')->get('log_ip_addresses')) { Chris@18: return \Drupal::request()->getClientIP(); Chris@18: } Chris@18: return ''; Chris@17: } Chris@17: Chris@0: }