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