3 namespace Drupal\paragraphs\Entity;
5 use Drupal\Component\Utility\NestedArray;
6 use Drupal\Component\Utility\Unicode;
7 use Drupal\Core\Entity\EntityStorageInterface;
8 use Drupal\Core\Field\BaseFieldDefinition;
9 use Drupal\Core\Entity\ContentEntityBase;
10 use Drupal\Core\Entity\EntityTypeInterface;
11 use Drupal\Core\Field\ChangedFieldItemList;
12 use Drupal\Core\Field\FieldDefinitionInterface;
13 use Drupal\Core\TypedData\TranslatableInterface;
14 use Drupal\field\Entity\FieldStorageConfig;
15 use Drupal\entity_reference_revisions\EntityNeedsSaveInterface;
16 use Drupal\entity_reference_revisions\EntityNeedsSaveTrait;
17 use Drupal\paragraphs\ParagraphInterface;
18 use Drupal\user\UserInterface;
21 * Defines the Paragraph entity.
27 * label = @Translation("Paragraph"),
28 * bundle_label = @Translation("Paragraph type"),
30 * "view_builder" = "Drupal\paragraphs\ParagraphViewBuilder",
31 * "access" = "Drupal\paragraphs\ParagraphAccessControlHandler",
32 * "storage_schema" = "Drupal\paragraphs\ParagraphStorageSchema",
34 * "default" = "Drupal\Core\Entity\ContentEntityForm",
35 * "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
36 * "edit" = "Drupal\Core\Entity\ContentEntityForm"
38 * "views_data" = "Drupal\views\EntityViewsData",
40 * base_table = "paragraphs_item",
41 * data_table = "paragraphs_item_field_data",
42 * revision_table = "paragraphs_item_revision",
43 * revision_data_table = "paragraphs_item_revision_field_data",
44 * translatable = TRUE,
45 * entity_revision_parent_type_field = "parent_type",
46 * entity_revision_parent_id_field = "parent_id",
47 * entity_revision_parent_field_name_field = "parent_field_name",
52 * "langcode" = "langcode",
53 * "revision" = "revision_id"
55 * bundle_entity_type = "paragraphs_type",
56 * field_ui_base_route = "entity.paragraphs_type.edit_form",
57 * common_reference_revisions_target = TRUE,
58 * content_translation_ui_skip = TRUE,
59 * render_cache = FALSE,
60 * default_reference_revision_settings = {
61 * "field_storage_config" = {
64 * "target_type" = "paragraph"
69 * "handler" = "default:paragraph"
72 * "entity_form_display" = {
73 * "type" = "entity_reference_paragraphs"
75 * "entity_view_display" = {
76 * "type" = "entity_reference_revisions_entity_view"
81 class Paragraph extends ContentEntityBase implements ParagraphInterface {
83 use EntityNeedsSaveTrait;
86 * The behavior plugin data for the paragraph entity.
90 protected $unserializedBehaviorSettings;
93 * Number of summaries.
97 protected $summaryCount;
102 public function getParentEntity() {
103 if (!isset($this->get('parent_type')->value) || !isset($this->get('parent_id')->value)) {
107 $parent = \Drupal::entityTypeManager()->getStorage($this->get('parent_type')->value)->load($this->get('parent_id')->value);
109 // Return current translation of parent entity, if it exists.
110 if ($parent != NULL && ($parent instanceof TranslatableInterface) && $parent->hasTranslation($this->language()->getId())) {
111 return $parent->getTranslation($this->language()->getId());
120 public function label() {
122 if ($parent = $this->getParentEntity()) {
123 $parent_field = $this->get('parent_field_name')->value;
124 $values = $parent->{$parent_field};
125 foreach ($values as $key => $value) {
126 if ($value->entity->id() == $this->id()) {
127 $label = $parent->label() . ' > ' . $value->getFieldDefinition()->getLabel();
137 public function preSave(EntityStorageInterface $storage) {
138 parent::preSave($storage);
140 // If no owner has been set explicitly, make the current user the owner.
141 if (!$this->getOwner()) {
142 $this->setOwnerId(\Drupal::currentUser()->id());
144 // If no revision author has been set explicitly, make the node owner the
146 if (!$this->getRevisionAuthor()) {
147 $this->setRevisionAuthorId($this->getOwnerId());
150 // If behavior settings are not set then get them from the entity.
151 if ($this->unserializedBehaviorSettings !== NULL) {
152 $this->set('behavior_settings', serialize($this->unserializedBehaviorSettings));
159 public function getAllBehaviorSettings() {
160 if ($this->unserializedBehaviorSettings === NULL) {
161 $this->unserializedBehaviorSettings = unserialize($this->get('behavior_settings')->value);
163 if (!is_array($this->unserializedBehaviorSettings)) {
164 $this->unserializedBehaviorSettings = [];
166 return $this->unserializedBehaviorSettings;
172 public function &getBehaviorSetting($plugin_id, $key, $default = NULL) {
173 $settings = $this->getAllBehaviorSettings();
175 $value = &NestedArray::getValue($settings, array_merge((array) $plugin_id, (array) $key), $exists);
185 public function setAllBehaviorSettings(array $settings) {
186 // Set behavior settings fields.
187 $this->unserializedBehaviorSettings = $settings;
193 public function setBehaviorSettings($plugin_id, array $settings) {
194 // Set behavior settings fields.
195 $this->unserializedBehaviorSettings[$plugin_id] = $settings;
201 public function postSave(EntityStorageInterface $storage, $update = TRUE) {
202 $this->setNeedsSave(FALSE);
203 parent::postSave($storage, $update);
209 public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
210 parent::preSaveRevision($storage, $record);
216 public function getCreatedTime() {
217 return $this->get('created')->value;
223 public function getOwner() {
224 return $this->get('uid')->entity;
230 public function getOwnerId() {
231 return $this->get('uid')->target_id;
237 public function setOwnerId($uid) {
238 $this->set('uid', $uid);
245 public function setOwner(UserInterface $account) {
246 $this->set('uid', $account->id());
253 public function getType() {
254 return $this->bundle();
260 public function getParagraphType() {
261 return $this->type->entity;
267 public function getRevisionAuthor() {
268 return $this->get('revision_uid')->entity;
274 public function setRevisionAuthorId($uid) {
275 $this->set('revision_uid', $uid);
282 public function getRevisionLog() {
289 public function setRevisionLog($revision_log) {
296 public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
297 $fields['id'] = BaseFieldDefinition::create('integer')
299 ->setDescription(t('The ID of the Paragraphs entity.'))
301 ->setSetting('unsigned', TRUE);
303 $fields['uuid'] = BaseFieldDefinition::create('uuid')
304 ->setLabel(t('UUID'))
305 ->setDescription(t('The UUID of the paragraphs entity.'))
308 $fields['revision_id'] = BaseFieldDefinition::create('integer')
309 ->setLabel(t('Revision ID'))
310 ->setDescription(t('The paragraphs entity revision ID.'))
312 ->setSetting('unsigned', TRUE);
314 $fields['type'] = BaseFieldDefinition::create('entity_reference')
315 ->setLabel(t('Type'))
316 ->setDescription(t('The Paragraphs type.'))
317 ->setSetting('target_type', 'paragraphs_type')
320 $fields['langcode'] = BaseFieldDefinition::create('language')
321 ->setLabel(t('Language code'))
322 ->setDescription(t('The paragraphs entity language code.'))
323 ->setRevisionable(TRUE);
325 $fields['uid'] = BaseFieldDefinition::create('entity_reference')
326 ->setLabel(t('Authored by'))
327 ->setDescription(t('The user ID of the paragraphs author.'))
328 ->setRevisionable(TRUE)
329 ->setSetting('target_type', 'user')
330 ->setSetting('handler', 'default')
331 ->setDefaultValueCallback('Drupal\paragraphs\Entity\Paragraph::getCurrentUserId')
332 ->setTranslatable(TRUE)
333 ->setDisplayOptions('form', array(
337 ->setDisplayConfigurable('form', TRUE);
339 $fields['status'] = BaseFieldDefinition::create('boolean')
340 ->setLabel(t('Published'))
341 ->setRevisionable(TRUE)
342 ->setTranslatable(TRUE)
343 ->setDefaultValue(TRUE)
344 ->setDisplayConfigurable('form', TRUE);
346 $fields['created'] = BaseFieldDefinition::create('created')
347 ->setLabel(t('Authored on'))
348 ->setDescription(t('The time that the Paragraph was created.'))
349 ->setRevisionable(TRUE)
350 ->setTranslatable(TRUE)
351 ->setDisplayOptions('form', array(
355 ->setDisplayConfigurable('form', TRUE);
357 $fields['revision_uid'] = BaseFieldDefinition::create('entity_reference')
358 ->setLabel(t('Revision user ID'))
359 ->setDescription(t('The user ID of the author of the current revision.'))
360 ->setSetting('target_type', 'user')
361 ->setQueryable(FALSE)
362 ->setRevisionable(TRUE);
364 $fields['parent_id'] = BaseFieldDefinition::create('string')
365 ->setLabel(t('Parent ID'))
366 ->setDescription(t('The ID of the parent entity of which this entity is referenced.'))
367 ->setSetting('is_ascii', TRUE);
369 $fields['parent_type'] = BaseFieldDefinition::create('string')
370 ->setLabel(t('Parent type'))
371 ->setDescription(t('The entity parent type to which this entity is referenced.'))
372 ->setSetting('is_ascii', TRUE)
373 ->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH);
375 $fields['parent_field_name'] = BaseFieldDefinition::create('string')
376 ->setLabel(t('Parent field name'))
377 ->setDescription(t('The entity parent field name to which this entity is referenced.'))
378 ->setSetting('is_ascii', TRUE)
379 ->setSetting('max_length', FieldStorageConfig::NAME_MAX_LENGTH);
381 $fields['behavior_settings'] = BaseFieldDefinition::create('string_long')
382 ->setLabel(t('Behavior settings'))
383 ->setDescription(t('The behavior plugin settings'))
384 ->setRevisionable(TRUE)
385 ->setDefaultValue(serialize([]));
391 * Default value callback for 'uid' base field definition.
393 * @see ::baseFieldDefinitions()
396 * An array of default values.
398 public static function getCurrentUserId() {
399 return array(\Drupal::currentUser()->id());
405 public function createDuplicate() {
406 $duplicate = parent::createDuplicate();
407 // Loop over entity fields and duplicate nested paragraphs.
408 foreach ($duplicate->getFields() as $field) {
409 if ($field->getFieldDefinition()->getType() == 'entity_reference_revisions') {
410 if ($field->getFieldDefinition()->getTargetEntityTypeId() == "paragraph") {
411 foreach ($field as $item) {
412 $item->entity = $item->entity->createDuplicate();
423 public function getSummary(array $options = []) {
424 $show_behavior_summary = isset($options['show_behavior_summary']) ? $options['show_behavior_summary'] : TRUE;
425 $depth_limit = isset($options['depth_limit']) ? $options['depth_limit'] : 1;
426 $this->summaryCount = 0;
429 foreach ($this->getFieldDefinitions() as $field_name => $field_definition) {
430 if ($field_definition->getType() == 'image' || $field_definition->getType() == 'file') {
431 $file_summary = $this->getFileSummary($field_name);
432 if ($file_summary != '') {
433 $summary[] = $file_summary;
437 $text_summary = $this->getTextSummary($field_name, $field_definition);
438 if ($text_summary != '') {
439 $summary[] = $text_summary;
442 if ($field_definition->getType() == 'entity_reference_revisions') {
443 // Decrease the depth, since we are entering a nested paragraph.
444 $nested_summary = $this->getNestedSummary($field_name, [
445 'show_behavior_summary' => $show_behavior_summary,
446 'depth_limit' => $depth_limit - 1
448 if ($nested_summary != '') {
449 $summary[] = $nested_summary;
453 if ($field_type = $field_definition->getType() == 'entity_reference') {
454 if (!in_array($field_name, ['type', 'uid', 'revision_uid'])) {
455 if ($this->get($field_name)->entity) {
456 $summary[] = $this->get($field_name)->entity->label();
461 // Add the Block admin label referenced by block_field.
462 if ($field_definition->getType() == 'block_field') {
463 if (!empty($this->get($field_name)->first())) {
464 $block_admin_label = $this->get($field_name)->first()->getBlock()->getPluginDefinition()['admin_label'];
465 $summary[] = $block_admin_label;
470 if ($show_behavior_summary) {
471 $paragraphs_type = $this->getParagraphType();
472 foreach ($paragraphs_type->getEnabledBehaviorPlugins() as $plugin_id => $plugin) {
473 if ($plugin_summary = $plugin->settingsSummary($this)) {
474 $summary = array_merge($summary, $plugin_summary);
479 if ($this->summaryCount) {
480 array_unshift($summary, (string) \Drupal::translation()->formatPlural($this->summaryCount, '1 child', '@count children'));
483 $collapsed_summary_text = implode(', ', $summary);
484 return strip_tags($collapsed_summary_text);
488 * Returns an array of field names to skip in ::isChanged.
491 * An array of field names.
493 protected function getFieldsToSkipFromChangedCheck() {
494 // A list of revision fields which should be skipped from the comparision.
496 $this->getEntityType()->getKey('revision'),
499 'revision_log_message',
508 public function isChanged() {
509 if ($this->isNew()) {
513 // $this->original only exists during save. If it exists we re-use it here
514 // for performance reasons.
515 /** @var \Drupal\paragraphs\ParagraphInterface $original */
516 $original = $this->original ?: NULL;
518 $original = $this->entityTypeManager()->getStorage($this->getEntityTypeId())->loadRevision($this->getLoadedRevisionId());
521 // If the current revision has just been added, we have a change.
522 if ($original->isNewRevision()) {
526 // The list of fields to skip from the comparision.
527 $skip_fields = $this->getFieldsToSkipFromChangedCheck();
529 // Compare field item current values with the original ones to determine
530 // whether we have changes. We skip also computed fields as comparing them
531 // with their original values might not be possible or be meaningless.
532 foreach ($this->getFieldDefinitions() as $field_name => $definition) {
533 if (in_array($field_name, $skip_fields, TRUE)) {
536 $field = $this->get($field_name);
537 // When saving entities in the user interface, the changed timestamp is
538 // automatically incremented by ContentEntityForm::submitForm() even if
539 // nothing was actually changed. Thus, the changed time needs to be
540 // ignored when determining whether there are any actual changes in the
542 if (!($field instanceof ChangedFieldItemList) && !$definition->isComputed()) {
543 $items = $field->filterEmptyItems();
544 $original_items = $original->get($field_name)->filterEmptyItems();
545 if (!$items->equals($original_items)) {
555 * Returns summary for file paragraph.
557 * @param string $field_name
558 * Field name from field definition.
563 protected function getFileSummary($field_name) {
565 if ($this->get($field_name)->entity) {
566 foreach ($this->get($field_name) as $file_key => $file_value) {
569 if ($file_value->description != '') {
570 $text = $file_value->description;
572 elseif ($file_value->title != '') {
573 $text = $file_value->title;
575 elseif ($file_value->alt != '') {
576 $text = $file_value->alt;
578 elseif ($file_value->entity->getFileName()) {
579 $text = $file_value->entity->getFileName();
582 if (strlen($text) > 150) {
583 $text = Unicode::truncate($text, 150);
590 return trim($summary);
594 * Returns summary for nested paragraphs.
596 * @param string $field_name
597 * Field definition id for paragraph.
598 * @param array $options
599 * (optional) An associative array of additional options.
600 * See \Drupal\paragraphs\ParagraphInterface::getSummary() for all of the
604 * Short summary for nested Paragraphs type
605 * or NULL if the summary is empty.
607 protected function getNestedSummary($field_name, array $options) {
609 if ($options['depth_limit'] >= 0) {
610 foreach ($this->get($field_name) as $item) {
611 $entity = $item->entity;
612 if ($entity instanceof ParagraphInterface) {
613 $summary[] = $entity->getSummary($options);
614 $this->summaryCount++;
619 $summary = array_filter($summary);
621 if (empty($summary)) {
625 $paragraph_summary = implode(', ', $summary);
626 return $paragraph_summary;
630 * Returns summary for all text type paragraph.
632 * @param string $field_name
633 * Field definition id for paragraph.
634 * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
635 * Field definition for paragraph.
638 * Short summary for text paragraph.
640 public function getTextSummary($field_name, FieldDefinitionInterface $field_definition) {
649 $excluded_text_types = [
656 if (in_array($field_definition->getType(), $text_types)) {
657 if (in_array($field_name, $excluded_text_types)) {
661 $text = $this->get($field_name)->value;
662 if (strlen($text) > 150) {
663 $text = Unicode::truncate($text, 150);
669 return trim($summary);