1<?php
2
3namespace Drupal\node\Entity;
4
5use Drupal\Core\Entity\EditorialContentEntityBase;
6use Drupal\Core\Entity\EntityStorageInterface;
7use Drupal\Core\Entity\EntityTypeInterface;
8use Drupal\Core\Field\BaseFieldDefinition;
9use Drupal\Core\Session\AccountInterface;
10use Drupal\node\NodeInterface;
11use Drupal\user\EntityOwnerTrait;
12
13/**
14 * Defines the node entity class.
15 *
16 * @ContentEntityType(
17 *   id = "node",
18 *   label = @Translation("Content"),
19 *   label_collection = @Translation("Content"),
20 *   label_singular = @Translation("content item"),
21 *   label_plural = @Translation("content items"),
22 *   label_count = @PluralTranslation(
23 *     singular = "@count content item",
24 *     plural = "@count content items"
25 *   ),
26 *   bundle_label = @Translation("Content type"),
27 *   handlers = {
28 *     "storage" = "Drupal\node\NodeStorage",
29 *     "storage_schema" = "Drupal\node\NodeStorageSchema",
30 *     "view_builder" = "Drupal\node\NodeViewBuilder",
31 *     "access" = "Drupal\node\NodeAccessControlHandler",
32 *     "views_data" = "Drupal\node\NodeViewsData",
33 *     "form" = {
34 *       "default" = "Drupal\node\NodeForm",
35 *       "delete" = "Drupal\node\Form\NodeDeleteForm",
36 *       "edit" = "Drupal\node\NodeForm",
37 *       "delete-multiple-confirm" = "Drupal\node\Form\DeleteMultiple"
38 *     },
39 *     "route_provider" = {
40 *       "html" = "Drupal\node\Entity\NodeRouteProvider",
41 *     },
42 *     "list_builder" = "Drupal\node\NodeListBuilder",
43 *     "translation" = "Drupal\node\NodeTranslationHandler"
44 *   },
45 *   base_table = "node",
46 *   data_table = "node_field_data",
47 *   revision_table = "node_revision",
48 *   revision_data_table = "node_field_revision",
49 *   show_revision_ui = TRUE,
50 *   translatable = TRUE,
51 *   list_cache_contexts = { "user.node_grants:view" },
52 *   entity_keys = {
53 *     "id" = "nid",
54 *     "revision" = "vid",
55 *     "bundle" = "type",
56 *     "label" = "title",
57 *     "langcode" = "langcode",
58 *     "uuid" = "uuid",
59 *     "status" = "status",
60 *     "published" = "status",
61 *     "uid" = "uid",
62 *     "owner" = "uid",
63 *   },
64 *   revision_metadata_keys = {
65 *     "revision_user" = "revision_uid",
66 *     "revision_created" = "revision_timestamp",
67 *     "revision_log_message" = "revision_log"
68 *   },
69 *   bundle_entity_type = "node_type",
70 *   field_ui_base_route = "entity.node_type.edit_form",
71 *   common_reference_target = TRUE,
72 *   permission_granularity = "bundle",
73 *   links = {
74 *     "canonical" = "/node/{node}",
75 *     "delete-form" = "/node/{node}/delete",
76 *     "delete-multiple-form" = "/admin/content/node/delete",
77 *     "edit-form" = "/node/{node}/edit",
78 *     "version-history" = "/node/{node}/revisions",
79 *     "revision" = "/node/{node}/revisions/{node_revision}/view",
80 *     "create" = "/node",
81 *   }
82 * )
83 */
84class Node extends EditorialContentEntityBase implements NodeInterface {
85
86  use EntityOwnerTrait;
87
88  /**
89   * Whether the node is being previewed or not.
90   *
91   * The variable is set to public as it will give a considerable performance
92   * improvement. See https://www.drupal.org/node/2498919.
93   *
94   * @var true|null
95   *   TRUE if the node is being previewed and NULL if it is not.
96   */
97  public $in_preview = NULL;
98
99  /**
100   * {@inheritdoc}
101   */
102  public function preSave(EntityStorageInterface $storage) {
103    parent::preSave($storage);
104
105    foreach (array_keys($this->getTranslationLanguages()) as $langcode) {
106      $translation = $this->getTranslation($langcode);
107
108      // If no owner has been set explicitly, make the anonymous user the owner.
109      if (!$translation->getOwner()) {
110        $translation->setOwnerId(0);
111      }
112    }
113
114    // If no revision author has been set explicitly, make the node owner the
115    // revision author.
116    if (!$this->getRevisionUser()) {
117      $this->setRevisionUserId($this->getOwnerId());
118    }
119  }
120
121  /**
122   * {@inheritdoc}
123   */
124  public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
125    parent::preSaveRevision($storage, $record);
126
127    if (!$this->isNewRevision() && isset($this->original) && (!isset($record->revision_log) || $record->revision_log === '')) {
128      // If we are updating an existing node without adding a new revision, we
129      // need to make sure $entity->revision_log is reset whenever it is empty.
130      // Therefore, this code allows us to avoid clobbering an existing log
131      // entry with an empty one.
132      $record->revision_log = $this->original->revision_log->value;
133    }
134  }
135
136  /**
137   * {@inheritdoc}
138   */
139  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
140    parent::postSave($storage, $update);
141
142    // Update the node access table for this node, but only if it is the
143    // default revision. There's no need to delete existing records if the node
144    // is new.
145    if ($this->isDefaultRevision()) {
146      /** @var \Drupal\node\NodeAccessControlHandlerInterface $access_control_handler */
147      $access_control_handler = \Drupal::entityTypeManager()->getAccessControlHandler('node');
148      $grants = $access_control_handler->acquireGrants($this);
149      \Drupal::service('node.grant_storage')->write($this, $grants, NULL, $update);
150    }
151
152    // Reindex the node when it is updated. The node is automatically indexed
153    // when it is added, simply by being added to the node table.
154    if ($update) {
155      node_reindex_node_search($this->id());
156    }
157  }
158
159  /**
160   * {@inheritdoc}
161   */
162  public static function preDelete(EntityStorageInterface $storage, array $entities) {
163    parent::preDelete($storage, $entities);
164
165    // Ensure that all nodes deleted are removed from the search index.
166    if (\Drupal::hasService('search.index')) {
167      /** @var \Drupal\search\SearchIndexInterface $search_index */
168      $search_index = \Drupal::service('search.index');
169      foreach ($entities as $entity) {
170        $search_index->clear('node_search', $entity->nid->value);
171      }
172    }
173  }
174
175  /**
176   * {@inheritdoc}
177   */
178  public static function postDelete(EntityStorageInterface $storage, array $nodes) {
179    parent::postDelete($storage, $nodes);
180    \Drupal::service('node.grant_storage')->deleteNodeRecords(array_keys($nodes));
181  }
182
183  /**
184   * {@inheritdoc}
185   */
186  public function getType() {
187    return $this->bundle();
188  }
189
190  /**
191   * {@inheritdoc}
192   */
193  public function access($operation = 'view', AccountInterface $account = NULL, $return_as_object = FALSE) {
194    // This override exists to set the operation to the default value "view".
195    return parent::access($operation, $account, $return_as_object);
196  }
197
198  /**
199   * {@inheritdoc}
200   */
201  public function getTitle() {
202    return $this->get('title')->value;
203  }
204
205  /**
206   * {@inheritdoc}
207   */
208  public function setTitle($title) {
209    $this->set('title', $title);
210    return $this;
211  }
212
213  /**
214   * {@inheritdoc}
215   */
216  public function getCreatedTime() {
217    return $this->get('created')->value;
218  }
219
220  /**
221   * {@inheritdoc}
222   */
223  public function setCreatedTime($timestamp) {
224    $this->set('created', $timestamp);
225    return $this;
226  }
227
228  /**
229   * {@inheritdoc}
230   */
231  public function isPromoted() {
232    return (bool) $this->get('promote')->value;
233  }
234
235  /**
236   * {@inheritdoc}
237   */
238  public function setPromoted($promoted) {
239    $this->set('promote', $promoted ? NodeInterface::PROMOTED : NodeInterface::NOT_PROMOTED);
240    return $this;
241  }
242
243  /**
244   * {@inheritdoc}
245   */
246  public function isSticky() {
247    return (bool) $this->get('sticky')->value;
248  }
249
250  /**
251   * {@inheritdoc}
252   */
253  public function setSticky($sticky) {
254    $this->set('sticky', $sticky ? NodeInterface::STICKY : NodeInterface::NOT_STICKY);
255    return $this;
256  }
257
258  /**
259   * {@inheritdoc}
260   */
261  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
262    $fields = parent::baseFieldDefinitions($entity_type);
263    $fields += static::ownerBaseFieldDefinitions($entity_type);
264
265    $fields['title'] = BaseFieldDefinition::create('string')
266      ->setLabel(t('Title'))
267      ->setRequired(TRUE)
268      ->setTranslatable(TRUE)
269      ->setRevisionable(TRUE)
270      ->setSetting('max_length', 255)
271      ->setDisplayOptions('view', [
272        'label' => 'hidden',
273        'type' => 'string',
274        'weight' => -5,
275      ])
276      ->setDisplayOptions('form', [
277        'type' => 'string_textfield',
278        'weight' => -5,
279      ])
280      ->setDisplayConfigurable('form', TRUE);
281
282    $fields['uid']
283      ->setLabel(t('Authored by'))
284      ->setDescription(t('The username of the content author.'))
285      ->setRevisionable(TRUE)
286      ->setDisplayOptions('view', [
287        'label' => 'hidden',
288        'type' => 'author',
289        'weight' => 0,
290      ])
291      ->setDisplayOptions('form', [
292        'type' => 'entity_reference_autocomplete',
293        'weight' => 5,
294        'settings' => [
295          'match_operator' => 'CONTAINS',
296          'size' => '60',
297          'placeholder' => '',
298        ],
299      ])
300      ->setDisplayConfigurable('form', TRUE);
301
302    $fields['status']
303      ->setDisplayOptions('form', [
304        'type' => 'boolean_checkbox',
305        'settings' => [
306          'display_label' => TRUE,
307        ],
308        'weight' => 120,
309      ])
310      ->setDisplayConfigurable('form', TRUE);
311
312    $fields['created'] = BaseFieldDefinition::create('created')
313      ->setLabel(t('Authored on'))
314      ->setDescription(t('The time that the node was created.'))
315      ->setRevisionable(TRUE)
316      ->setTranslatable(TRUE)
317      ->setDisplayOptions('view', [
318        'label' => 'hidden',
319        'type' => 'timestamp',
320        'weight' => 0,
321      ])
322      ->setDisplayOptions('form', [
323        'type' => 'datetime_timestamp',
324        'weight' => 10,
325      ])
326      ->setDisplayConfigurable('form', TRUE);
327
328    $fields['changed'] = BaseFieldDefinition::create('changed')
329      ->setLabel(t('Changed'))
330      ->setDescription(t('The time that the node was last edited.'))
331      ->setRevisionable(TRUE)
332      ->setTranslatable(TRUE);
333
334    $fields['promote'] = BaseFieldDefinition::create('boolean')
335      ->setLabel(t('Promoted to front page'))
336      ->setRevisionable(TRUE)
337      ->setTranslatable(TRUE)
338      ->setDefaultValue(TRUE)
339      ->setDisplayOptions('form', [
340        'type' => 'boolean_checkbox',
341        'settings' => [
342          'display_label' => TRUE,
343        ],
344        'weight' => 15,
345      ])
346      ->setDisplayConfigurable('form', TRUE);
347
348    $fields['sticky'] = BaseFieldDefinition::create('boolean')
349      ->setLabel(t('Sticky at top of lists'))
350      ->setRevisionable(TRUE)
351      ->setTranslatable(TRUE)
352      ->setDefaultValue(FALSE)
353      ->setDisplayOptions('form', [
354        'type' => 'boolean_checkbox',
355        'settings' => [
356          'display_label' => TRUE,
357        ],
358        'weight' => 16,
359      ])
360      ->setDisplayConfigurable('form', TRUE);
361
362    return $fields;
363  }
364
365}
366