Commit a451002a authored by alexpott's avatar alexpott

Issue #2428795 by mkalkbrenner, plach, yched, catch, hchonov, webchick,...

Issue #2428795 by mkalkbrenner, plach, yched, catch, hchonov, webchick, tstoeckler, pjonckiere, miro_dietiker, Schnitzel, klausi: Translatable entity 'changed' timestamps are not working at all
parent 3d7cd4ed
......@@ -22,11 +22,19 @@
interface EntityChangedInterface {
/**
* Gets the timestamp of the last entity change.
* Gets the timestamp of the last entity change for the current translation.
*
* @return int
* The timestamp of the last entity save operation.
*/
public function getChangedTime();
/**
* Gets the timestamp of the last entity change across all translations.
*
* @return int
* The timestamp of the last entity save operation across all
* translations.
*/
public function getChangedTimeAcrossTranslations();
}
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityChangedTrait.
*/
namespace Drupal\Core\Entity;
/**
* Provides a trait for accessing changed time.
*/
trait EntityChangedTrait {
/**
* Returns the timestamp of the last entity change across all translations.
*
* @return int
* The timestamp of the last entity save operation across all
* translations.
*/
public function getChangedTimeAcrossTranslations() {
$changed = $this->getUntranslated()->getChangedTime();
foreach ($this->getTranslationLanguages(FALSE) as $language) {
$translation_changed = $this->getTranslation($language->getId())->getChangedTime();
$changed = max($translation_changed, $changed);
}
return $changed;
}
}
......@@ -23,8 +23,9 @@ public function validate($entity, Constraint $constraint) {
/** @var \Drupal\Core\Entity\EntityInterface $entity */
if (!$entity->isNew()) {
$saved_entity = \Drupal::entityManager()->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());
if ($saved_entity && $saved_entity->getChangedTime() > $entity->getChangedTime()) {
// A change to any other translation must add a violation to the current
// translation because there might be untranslatable shared fields.
if ($saved_entity && $saved_entity->getChangedTimeAcrossTranslations() > $entity->getChangedTime()) {
$this->context->addViolation($constraint->message);
}
}
......
......@@ -30,7 +30,46 @@ class ChangedItem extends CreatedItem {
*/
public function preSave() {
parent::preSave();
$this->value = REQUEST_TIME;
// Set the timestamp to request time if it is not set.
if (!$this->value) {
$this->value = REQUEST_TIME;
}
else {
// On an existing entity the changed timestamp will only be set to request
// time automatically if at least one other field value of the entity has
// changed. This detection doesn't run on new entities and will be turned
// off if the changed timestamp is set manually before save, for example
// during migrations or using
// \Drupal\content_translation\ContentTranslationMetadataWrapperInterface::setChangedTime().
// @todo Knowing if the current translation was modified or not is
// generally useful. There's a follow-up issue to reduce the nesting
// here and to offer an accessor for this information. See
// https://www.drupal.org/node/2453153
$entity = $this->getEntity();
if (!$entity->isNew()) {
$field_name = $this->getFieldDefinition()->getName();
// Clone $entity->original to avoid modifying it when calling
// getTranslation().
$original = clone $entity->original;
$translatable = $this->getFieldDefinition()->isTranslatable();
if ($translatable) {
$original = $original->getTranslation($entity->language()->getId());
}
if ($this->value == $original->get($field_name)->value) {
foreach ($entity->getFieldDefinitions() as $other_field_name => $other_field_definition) {
if ($other_field_name != $field_name && !$other_field_definition->isComputed() && (!$translatable || $other_field_definition->isTranslatable())) {
$items = $entity->get($other_field_name)->filterEmptyItems();
$original_items = $original->get($other_field_name)->filterEmptyItems();
if (!$items->equals($original_items)) {
$this->value = REQUEST_TIME;
break;
}
}
}
}
}
}
}
}
......@@ -8,6 +8,7 @@
namespace Drupal\block_content\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
......@@ -66,6 +67,8 @@
*/
class BlockContent extends ContentEntityBase implements BlockContentInterface {
use EntityChangedTrait;
/**
* The theme the block is being created in.
*
......@@ -202,6 +205,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
->setDescription(t('The time that the custom block was last edited.'))
->setTranslatable(TRUE)
->setRevisionable(TRUE);
return $fields;
......
......@@ -196,14 +196,6 @@ public function getCreatedTime();
*/
public function setCreatedTime($created);
/**
* Returns the timestamp of when the comment was updated.
*
* @return int
* The timestamp of when the comment was updated.
*/
public function getChangedTime();
/**
* Checks if the comment is published.
*
......
......@@ -126,8 +126,10 @@ public function create(FieldableEntityInterface $entity, $fields) {
}
// Default to REQUEST_TIME when entity does not have a changed property.
$last_comment_timestamp = REQUEST_TIME;
// @todo Make comment statistics language aware and add some tests. See
// https://www.drupal.org/node/2318875
if ($entity instanceof EntityChangedInterface) {
$last_comment_timestamp = $entity->getChangedTime();
$last_comment_timestamp = $entity->getChangedTimeAcrossTranslations();
}
$query->values(array(
'entity_id' => $entity->id(),
......@@ -243,9 +245,9 @@ public function update(CommentInterface $comment) {
->fields(array(
'cid' => 0,
'comment_count' => 0,
// Use the created date of the entity if it's set, or default to
// Use the changed date of the entity if it's set, or default to
// REQUEST_TIME.
'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTime() : REQUEST_TIME,
'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTimeAcrossTranslations() : REQUEST_TIME,
'last_comment_name' => '',
'last_comment_uid' => $last_comment_uid,
))
......
......@@ -10,6 +10,7 @@
use Drupal\Component\Utility\Number;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\comment\CommentInterface;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
......@@ -61,6 +62,8 @@
*/
class Comment extends ContentEntityBase implements CommentInterface {
use EntityChangedTrait;
/**
* The thread for which a lock was acquired.
*/
......
......@@ -213,7 +213,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $type = '
'#url' => $commented_entity->urlInfo(),
),
),
'changed' => $this->dateFormatter->format($comment->getChangedTime(), 'short'),
'changed' => $this->dateFormatter->format($comment->getChangedTimeAcrossTranslations(), 'short'),
);
$comment_uri_options = $comment->urlInfo()->getOptions();
$links = array();
......
......@@ -61,7 +61,7 @@ function testCommentTokenReplacement() {
$tests['[comment:url]'] = $comment->url('canonical', $url_options + array('fragment' => 'comment-' . $comment->id()));
$tests['[comment:edit-url]'] = $comment->url('edit-form', $url_options);
$tests['[comment:created:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $comment->getCreatedTime(), 2, $language_interface->getId());
$tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $comment->getChangedTime(), 2, $language_interface->getId());
$tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $comment->getChangedTimeAcrossTranslations(), 2, $language_interface->getId());
$tests['[comment:parent:cid]'] = $comment->hasParentComment() ? $comment->getParentComment()->id() : NULL;
$tests['[comment:parent:title]'] = SafeMarkup::checkPlain($parent_comment->getSubject());
$tests['[comment:entity]'] = SafeMarkup::checkPlain($node->getTitle());
......
......@@ -7,8 +7,6 @@
namespace Drupal\content_translation;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\user\UserInterface;
/**
......@@ -17,7 +15,7 @@
* This acts as a wrapper for an entity translation object, encapsulating the
* logic needed to retrieve translation metadata.
*/
interface ContentTranslationMetadataWrapperInterface extends EntityChangedInterface {
interface ContentTranslationMetadataWrapperInterface {
/**
* Retrieves the source language for this translation.
......@@ -109,6 +107,14 @@ public function getCreatedTime();
*/
public function setCreatedTime($timestamp);
/**
* Returns the timestamp of the last entity change from current translation.
*
* @return int
* The timestamp of the last entity save operation.
*/
public function getChangedTime();
/**
* Sets the translation modification timestamp.
*
......
......@@ -26,7 +26,7 @@ class ContentTestTranslationUITest extends ContentTranslationUITest {
*/
protected function setUp() {
// Use the entity_test_mul as this has multilingual property support.
$this->entityTypeId = 'entity_test_mul';
$this->entityTypeId = 'entity_test_mul_changed';
parent::setUp();
}
......
......@@ -42,6 +42,7 @@ function testTranslationUI() {
$this->doTestPublishedStatus();
$this->doTestAuthoringInfo();
$this->doTestTranslationEdit();
$this->doTestTranslationChanged();
$this->doTestTranslationDeletion();
}
......@@ -365,6 +366,19 @@ protected function getValue(EntityInterface $translation, $property, $langcode)
return $translation->get($property)->{$key};
}
/**
* Returns the name of the field that implements the changed timestamp.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity being tested.
*
* @return string
* The the field name.
*/
protected function getChangedFieldName($entity) {
return $entity->hasField('content_translation_changed') ? 'content_translation_changed' : 'changed';
}
/**
* Tests edit content translation.
*/
......@@ -384,4 +398,71 @@ protected function doTestTranslationEdit() {
}
}
/**
* Tests the basic translation workflow.
*/
protected function doTestTranslationChanged() {
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
$changed_field_name = $this->getChangedFieldName($entity);
$definition = $entity->getFieldDefinition($changed_field_name);
$config = $definition->getConfig($entity->bundle());
foreach ([FALSE, TRUE] as $translatable_changed_field) {
if ($definition->isTranslatable()) {
// For entities defining a translatable changed field we want to test
// the correct behavior of that field even if the translatability is
// revoked. In that case the changed timestamp should be synchronized
// across all translations.
$config->setTranslatable($translatable_changed_field);
$config->save();
}
elseif ($translatable_changed_field) {
// For entities defining a non-translatable changed field we cannot
// declare the field as translatable on the fly by modifying its config
// because the schema doesn't support this.
break;
}
foreach ($entity->getTranslationLanguages() as $language) {
// Ensure different timestamps.
sleep(1);
$langcode = $language->getId();
$edit = array(
$this->fieldName . '[0][value]' => $this->randomString(),
);
$edit_path = $entity->urlInfo('edit-form', array('language' => $language));
$this->drupalPostForm($edit_path, $edit, $this->getFormSubmitAction($entity, $langcode));
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
$this->assertEqual(
$entity->getChangedTimeAcrossTranslations(), $entity->getTranslation($langcode)->getChangedTime(),
format_string('Changed time for language %language is the latest change over all languages.', array('%language' => $language->getName()))
);
}
$timestamps = array();
foreach ($entity->getTranslationLanguages() as $language) {
$next_timestamp = $entity->getTranslation($language->getId())->getChangedTime();
if (!in_array($next_timestamp, $timestamps)) {
$timestamps[] = $next_timestamp;
}
}
if ($translatable_changed_field) {
$this->assertEqual(
count($timestamps), count($entity->getTranslationLanguages()),
'All timestamps from all languages are different.'
);
}
else {
$this->assertEqual(
count($timestamps), 1,
'All timestamps from all languages are identical.'
);
}
}
}
}
......@@ -8,10 +8,10 @@
namespace Drupal\file\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Language\LanguageInterface;
use Drupal\file\FileInterface;
use Drupal\user\UserInterface;
......@@ -38,6 +38,8 @@
*/
class File extends ContentEntityBase implements FileInterface {
use EntityChangedTrait;
/**
* {@inheritdoc}
*/
......
......@@ -22,8 +22,6 @@ function testFileSave() {
'filename' => 'druplicon.txt',
'uri' => 'public://druplicon.txt',
'filemime' => 'text/plain',
'created' => 1,
'changed' => 1,
'status' => FILE_STATUS_PERMANENT,
));
file_put_contents($file->getFileUri(), 'hello world');
......@@ -64,8 +62,6 @@ function testFileSave() {
'filename' => 'DRUPLICON.txt',
'uri' => 'public://DRUPLICON.txt',
'filemime' => 'text/plain',
'created' => 1,
'changed' => 1,
'status' => FILE_STATUS_PERMANENT,
));
file_put_contents($uppercase_file->getFileUri(), 'hello world');
......
......@@ -8,6 +8,7 @@
namespace Drupal\menu_link_content\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
......@@ -52,6 +53,8 @@
*/
class MenuLinkContent extends ContentEntityBase implements MenuLinkContentInterface {
use EntityChangedTrait;
/**
* A flag for whether this entity is wrapped in a plugin instance.
*
......@@ -377,7 +380,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
->setDescription(t('The time that the menu link was last edited.'));
->setDescription(t('The time that the menu link was last edited.'))
->setTranslatable(TRUE);
return $fields;
}
......
......@@ -138,7 +138,7 @@ public function testCreateLink() {
'title' => 'Test Link',
);
$link->link->options = $options;
$link->changed->value = REQUEST_TIME - 5;
$link->changed->value = 0;
$link->save();
// Make sure the changed timestamp is updated.
$this->assertEqual($link->getChangedTime(), REQUEST_TIME, 'Changing a menu link sets "changed" timestamp.');
......
......@@ -743,8 +743,8 @@ function node_page_title(NodeInterface $node) {
* @param $nid
* The ID of a node.
* @param string $langcode
* (optional) The language the node has been last modified in. Defaults to the
* node language.
* (optional) The language to get the last changed time for. If omitted, the
* last changed time across all translations will be returned.
*
* @return string
* A unix timestamp indicating the last time the node was changed.
......@@ -753,8 +753,9 @@ function node_page_title(NodeInterface $node) {
* for validation, which will be done by EntityChangedConstraintValidator.
*/
function node_last_changed($nid, $langcode = NULL) {
$changed = \Drupal::entityManager()->getStorage('node')->loadUnchanged($nid)->getChangedTime();
return $changed ? $changed : FALSE;
$node = \Drupal::entityManager()->getStorage('node')->loadUnchanged($nid);
$changed = $langcode ? $node->getTranslation($langcode)->getChangedTime() : $node->getChangedTimeAcrossTranslations();
return $changed ?: FALSE;
}
/**
......
......@@ -8,6 +8,7 @@
namespace Drupal\node\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
......@@ -69,6 +70,8 @@
*/
class Node extends ContentEntityBase implements NodeInterface {
use EntityChangedTrait;
/**
* {@inheritdoc}
*/
......
......@@ -289,7 +289,7 @@ protected function actions(array $form, FormStateInterface $form_state) {
public function validate(array $form, FormStateInterface $form_state) {
$node = parent::validate($form, $form_state);
if ($node->id() && (node_last_changed($node->id(), $this->getFormLangcode($form_state)) > $node->getChangedTime())) {
if ($node->id() && (node_last_changed($node->id()) > $node->getChangedTimeAcrossTranslations())) {
$form_state->setErrorByName('changed', $this->t('The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved.'));
}
......
......@@ -38,7 +38,7 @@ function testNodeLastChanged() {
// Test node last changed timestamp.
$changed_timestamp = node_last_changed($node->id());
$this->assertEqual($changed_timestamp, $node->getChangedTime(), 'Expected last changed timestamp returned.');
$this->assertEqual($changed_timestamp, $node->getChangedTimeAcrossTranslations(), 'Expected last changed timestamp returned.');
$changed_timestamp = node_last_changed($node->id(), $node->language()->getId());
$this->assertEqual($changed_timestamp, $node->getChangedTime(), 'Expected last changed timestamp returned.');
......
......@@ -120,8 +120,8 @@ function testTimestamps() {
entity_create('node', $edit)->save();
$node = $this->drupalGetNodeByTitle($edit['title']);
$this->assertEqual($node->getCreatedTime(), 280299600, 'Creating a node uses user-set "created" timestamp.');
$this->assertNotEqual($node->getChangedTime(), 979534800, 'Creating a node does not use user-set "changed" timestamp.');
$this->assertEqual($node->getCreatedTime(), 280299600, 'Creating a node programmatically uses programmatically set "created" timestamp.');
$this->assertEqual($node->getChangedTime(), 979534800, 'Creating a node programmatically uses programmatically set "changed" timestamp.');
// Update the timestamps.
$node->setCreatedTime(979534800);
......@@ -130,7 +130,10 @@ function testTimestamps() {
$node->save();
$node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
$this->assertEqual($node->getCreatedTime(), 979534800, 'Updating a node uses user-set "created" timestamp.');
$this->assertNotEqual($node->getChangedTime(), 280299600, 'Updating a node does not use user-set "changed" timestamp.');
// Allowing setting changed timestamps is required, see
// Drupal\content_translation\ContentTranslationMetadataWrapper::setChangedTime($timestamp)
// for example.
$this->assertEqual($node->getChangedTime(), 280299600, 'Updating a node uses user-set "changed" timestamp.');
}
/**
......
......@@ -8,6 +8,7 @@
namespace Drupal\shortcut\Tests;
use Drupal\content_translation\Tests\ContentTranslationUITest;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Language\Language;
/**
......@@ -102,4 +103,16 @@ protected function doTestTranslationEdit() {
}
}
/**
* Tests the basic translation workflow.
*/
protected function doTestTranslationChanged() {
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
$this->assertFalse(
$entity instanceof EntityChangedInterface,
format_string('%entity is not implementing EntityChangedInterface.' , array('%entity' => $this->entityTypeId))
);
}
}
......@@ -58,6 +58,7 @@ function entity_test_entity_types($filter = NULL) {
if ($filter != ENTITY_TEST_TYPES_REVISABLE) {
$types[] = 'entity_test_mul';
$types[] = 'entity_test_mul_langcode_key';
$types[] = 'entity_test_mul_changed';
}
if ($filter != ENTITY_TEST_TYPES_MULTILINGUAL) {
$types[] = 'entity_test_rev';
......@@ -66,6 +67,7 @@ function entity_test_entity_types($filter = NULL) {
$types[] = 'entity_test_base_field_display';
}
$types[] = 'entity_test_mulrev';
$types[] = 'entity_test_mulrev_changed';
return array_combine($types, $types);
}
......@@ -459,6 +461,20 @@ function entity_test_entity_test_mul_translation_delete(EntityInterface $transla
/**
* Implements hook_ENTITY_TYPE_translation_insert() for 'entity_test_mulrev'.
*/
function entity_test_entity_test_mul_changed_translation_insert(EntityInterface $translation) {
_entity_test_record_hooks('entity_test_mul_changed_translation_insert', $translation->language()->getId());
}
/**
* Implements hook_ENTITY_TYPE_translation_delete().
*/
function entity_test_entity_test_mul_changed_translation_delete(EntityInterface $translation) {
_entity_test_record_hooks('entity_test_mul_changed_translation_delete', $translation->language()->getId());
}
/**
* Implements hook_ENTITY_TYPE_translation_insert().
*/
function entity_test_entity_test_mulrev_translation_insert(EntityInterface $translation) {
_entity_test_record_hooks('entity_test_mulrev_translation_insert', $translation->language()->getId());
}
......@@ -470,6 +486,20 @@ function entity_test_entity_test_mulrev_translation_delete(EntityInterface $tran
_entity_test_record_hooks('entity_test_mulrev_translation_delete', $translation->language()->getId());
}
/**
* Implements hook_ENTITY_TYPE_translation_insert() for 'entity_test_mulrev'.
*/
function entity_test_entity_test_mulrev_changed_translation_insert(EntityInterface $translation) {
_entity_test_record_hooks('entity_test_mulrev_changed_translation_insert', $translation->language()->getId());
}
/**
* Implements hook_ENTITY_TYPE_translation_delete().
*/
function entity_test_entity_test_mulrev_changed_translation_delete(EntityInterface $translation) {
_entity_test_record_hooks('entity_test_mulrev_changed_translation_delete', $translation->language()->getId());
}
/**
* Implements hook_ENTITY_TYPE_translation_insert() for 'entity_test_mul_langcode_key'.
*/
......
......@@ -7,6 +7,7 @@
namespace Drupal\entity_test\Entity;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Field\BaseFieldDefinition;
......@@ -32,6 +33,8 @@
*/
class EntityTestConstraints extends EntityTest implements EntityChangedInterface {
use EntityChangedTrait;
/**
* {@inheritdoc}
*/
......
<?php
/**
* @file
* Contains \Drupal\entity_test\Entity\EntityTestMulDefaultValue.
*/
namespace Drupal\entity_test\Entity;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
/**
* Defines the test entity class.
*
* @ContentEntityType(
* id = "entity_test_mul_changed",
* label = @Translation("Test entity - data table"),
* handlers = {
* "view_builder" = "Drupal\entity_test\EntityTestViewBuilder",
* "access" = "Drupal\entity_test\EntityTestAccessControlHandler",
* "form" = {
* "default" = "Drupal\entity_test\EntityTestForm",
* "delete" = "Drupal\entity_test\EntityTestDeleteForm"
* },
* "translation" = "Drupal\content_translation\ContentTranslationHandler",
* "views_data" = "Drupal\views\EntityViewsData"
* },
* base_table = "entity_test_mul_changed",
* data_table = "entity_test_mul_changed_property",
* translatable = TRUE,
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid",
* "bundle" = "type",
* "label" = "name",
* "langcode" = "langcode"
* },
* links = {
* "canonical" = "/entity_test_mul_changed/manage/{entity_test_mul_changed}",
* "edit-form" = "/entity_test_mul_changed/manage/{entity_test_mul_changed}",
* "delete-form" = "/entity_test/delete/entity_test_mul_changed/{entity_test_mul_changed}",
* },
* field_ui_base_route = "entity.entity_test_mul_changed.admin_form",
* )
*/
class EntityTestMulChanged extends EntityTestMul implements EntityChangedInterface {
use EntityChangedTrait;
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['changed'] = BaseFieldDefinition::create('changed_test')
->setLabel(t('Changed'))
->setDescription(t('The time that the entity was last edited.'))
->setTranslatable(TRUE);
return $fields;
}
/**
* {@inheritdoc}
*/
public function save() {
// Ensure a new timestamp.
sleep(1);
parent::save();
}
/**
* {@inheritdoc}
*/
public function getChangedTime() {