Commit f4a242f1 authored by catch's avatar catch

Issue #2669802 by chr.fritsch, Berdir, seanB, bojanz, dawehner, yoroy,...

Issue #2669802 by chr.fritsch, Berdir, seanB, bojanz, dawehner, yoroy, slashrsm, tstoeckler: Add a content entity form which allows to set revisions
parent 0f139055
......@@ -203,6 +203,13 @@ drupal.dropbutton:
- core/drupalSettings
- core/jquery.once
drupal.entity-form:
version: VERSION
js:
misc/entity-form.js: {}
dependencies:
- core/drupal.form
drupal.form:
version: VERSION
js:
......
......@@ -2,6 +2,7 @@
namespace Drupal\Core\Entity;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Form\FormStateInterface;
......@@ -21,14 +22,42 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface
*/
protected $entityManager;
/**
* The entity being used by this form.
*
* @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\RevisionLogInterface
*/
protected $entity;
/**
* The entity type bundle info service.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* The time service.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $time;
/**
* Constructs a ContentEntityForm object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle service.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
*/
public function __construct(EntityManagerInterface $entity_manager) {
public function __construct(EntityManagerInterface $entity_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
$this->entityManager = $entity_manager;
$this->entityTypeBundleInfo = $entity_type_bundle_info ?: \Drupal::service('entity_type.bundle.info');
$this->time = $time ?: \Drupal::service('datetime.time');
}
/**
......@@ -36,15 +65,54 @@ public function __construct(EntityManagerInterface $entity_manager) {
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager')
$container->get('entity.manager'),
$container->get('entity_type.bundle.info'),
$container->get('datetime.time')
);
}
/**
* {@inheritdoc}
*/
protected function prepareEntity() {
parent::prepareEntity();
// Hide the current revision log message in UI.
if ($this->showRevisionUi() && !$this->entity->isNew()) {
$this->entity->setRevisionLogMessage(NULL);
}
}
/**
* Returns the bundle entity of the entity, or NULL if there is none.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The bundle entity.
*/
protected function getBundleEntity() {
if ($bundle_entity_type = $this->entity->getEntityType()->getBundleEntityType()) {
return $this->entityTypeManager->getStorage($bundle_entity_type)->load($this->entity->bundle());
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
if ($this->showRevisionUi()) {
// Advanced tab must be the first, because other fields rely on that.
if (!isset($form['advanced'])) {
$form['advanced'] = [
'#type' => 'vertical_tabs',
'#weight' => 99,
];
}
}
$form = parent::form($form, $form_state);
// Content entity forms do not use the parent's #after_build callback
// because they only need to rebuild the entity in the validation and the
// submit handler because Field API uses its own #after_build callback for
......@@ -54,6 +122,11 @@ public function form(array $form, FormStateInterface $form_state) {
$this->getFormDisplay($form_state)->buildForm($this->entity, $form, $form_state);
// Allow modules to act before and after form language is updated.
$form['#entity_builders']['update_form_langcode'] = '::updateFormLangcode';
if ($this->showRevisionUi()) {
$this->addRevisionableFormFields($form);
}
return $form;
}
......@@ -76,6 +149,17 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
// Mark the entity as requiring validation.
$entity->setValidationRequired(!$form_state->getTemporaryValue('entity_validated'));
// Save as a new revision if requested to do so.
if ($this->showRevisionUi() && !$form_state->isValueEmpty('revision')) {
$entity->setNewRevision();
if ($entity instanceof RevisionLogInterface) {
// If a new revision is created, save the current user as
// revision author.
$entity->setRevisionUserId($this->currentUser()->id());
$entity->setRevisionCreationTime($this->time->getRequestTime());
}
}
return $entity;
}
......@@ -283,8 +367,84 @@ public function updateFormLangcode($entity_type_id, EntityInterface $entity, arr
*/
public function updateChangedTime(EntityInterface $entity) {
if ($entity->getEntityType()->isSubclassOf(EntityChangedInterface::class)) {
$entity->setChangedTime(REQUEST_TIME);
$entity->setChangedTime($this->time->getRequestTime());
}
}
/**
* Add revision form fields if the entity enabled the UI.
*
* @param array $form
* An associative array containing the structure of the form.
*/
protected function addRevisionableFormFields(array &$form) {
$entity_type = $this->entity->getEntityType();
$new_revision_default = $this->getNewRevisionDefault();
// Add a log field if the "Create new revision" option is checked, or if the
// current user has the ability to check that option.
$form['revision_information'] = [
'#type' => 'details',
'#title' => $this->t('Revision information'),
// Open by default when "Create new revision" is checked.
'#open' => $new_revision_default,
'#group' => 'advanced',
'#weight' => 20,
'#access' => $new_revision_default || $this->entity->get($entity_type->getKey('revision'))->access('update'),
'#optional' => TRUE,
'#attributes' => [
'class' => ['entity-content-form-revision-information'],
],
'#attached' => [
'library' => ['core/drupal.entity-form'],
],
];
$form['revision'] = [
'#type' => 'checkbox',
'#title' => $this->t('Create new revision'),
'#default_value' => $new_revision_default,
'#access' => !$this->entity->isNew() && $this->entity->get($entity_type->getKey('revision'))->access('update'),
'#group' => 'revision_information',
];
if (isset($form['revision_log'])) {
$form['revision_log'] += [
'#group' => 'revision_information',
'#states' => [
'visible' => [
':input[name="revision"]' => ['checked' => TRUE],
],
],
];
}
}
/**
* Should new revisions created on default.
*
* @return bool
* New revision on default.
*/
protected function getNewRevisionDefault() {
$new_revision_default = FALSE;
$bundle_entity = $this->getBundleEntity();
if ($bundle_entity instanceof RevisionableEntityBundleInterface) {
// Always use the default revision setting.
$new_revision_default = $bundle_entity->shouldCreateNewRevision();
}
return $new_revision_default;
}
/**
* Checks whether the revision form fields should be added to the form.
*
* @return bool
* TRUE if the form field should be added, FALSE otherwise.
*/
protected function showRevisionUi() {
return $this->entity->getEntityType()->showRevisionUi();
}
}
......@@ -174,6 +174,13 @@ class EntityType implements EntityTypeInterface {
*/
protected $translatable = FALSE;
/**
* Indicates whether the revision form fields should be added to the form.
*
* @var bool
*/
protected $show_revision_ui = FALSE;
/**
* The human-readable name of the type.
*
......@@ -689,6 +696,13 @@ public function getBaseTable() {
return $this->base_table;
}
/**
* {@inheritdoc}
*/
public function showRevisionUi() {
return $this->isRevisionable() && $this->show_revision_ui;
}
/**
* {@inheritdoc}
*/
......
......@@ -573,6 +573,14 @@ public function getBaseTable();
*/
public function isTranslatable();
/**
* Indicates whether the revision form fields should be added to the form.
*
* @return bool
* TRUE if the form field should be added, FALSE otherwise.
*/
public function showRevisionUi();
/**
* Indicates whether entities of this type have revision support.
*
......
<?php
namespace Drupal\Core\Entity;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Provides an interface defining a revisionable entity bundle.
*/
interface RevisionableEntityBundleInterface extends ConfigEntityInterface {
/**
* Gets whether a new revision should be created by default.
*
* @return bool
* TRUE if a new revision should be created by default.
*/
public function shouldCreateNewRevision();
}
......@@ -8,20 +8,20 @@
'use strict';
/**
* Sets summaries about revision and translation of block content.
* Sets summaries about revision and translation of entities.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches summary behaviour block content form tabs.
* Attaches summary behaviour entity form tabs.
*
* Specifically, it updates summaries to the revision information and the
* translation options.
*/
Drupal.behaviors.blockContentDetailsSummaries = {
Drupal.behaviors.entityContentDetailsSummaries = {
attach: function (context) {
var $context = $(context);
$context.find('.block-content-form-revision-information').drupalSetSummary(function (context) {
$context.find('.entity-content-form-revision-information').drupalSetSummary(function (context) {
var $revisionContext = $(context);
var revisionCheckbox = $revisionContext.find('.js-form-item-revision input');
......@@ -36,7 +36,7 @@
return Drupal.t('No revision');
});
$context.find('fieldset.block-content-translation-options').drupalSetSummary(function (context) {
$context.find('details.entity-translation-options').drupalSetSummary(function (context) {
var $translationContext = $(context);
var translate;
var $checkbox = $translationContext.find('.js-form-item-translation-translate input');
......
# Deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
# This is just for BC and not used anymore. See https://www.drupal.org/node/2669802
drupal.block_content:
version: VERSION
js:
js/block_content.js: {}
js: {}
dependencies:
- core/jquery
- core/drupal
- core/drupal.form
- core/drupal.entity-form
......@@ -4,38 +4,13 @@
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form handler for the custom block edit forms.
*/
class BlockContentForm extends ContentEntityForm {
/**
* The custom block storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $blockContentStorage;
/**
* The custom block type storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $blockContentTypeStorage;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The block content entity.
*
......@@ -43,63 +18,13 @@ class BlockContentForm extends ContentEntityForm {
*/
protected $entity;
/**
* Constructs a BlockContentForm object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Entity\EntityStorageInterface $block_content_storage
* The custom block storage.
* @param \Drupal\Core\Entity\EntityStorageInterface $block_content_type_storage
* The custom block type storage.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(EntityManagerInterface $entity_manager, EntityStorageInterface $block_content_storage, EntityStorageInterface $block_content_type_storage, LanguageManagerInterface $language_manager) {
parent::__construct($entity_manager);
$this->blockContentStorage = $block_content_storage;
$this->blockContentTypeStorage = $block_content_type_storage;
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$entity_manager = $container->get('entity.manager');
return new static(
$entity_manager,
$entity_manager->getStorage('block_content'),
$entity_manager->getStorage('block_content_type'),
$container->get('language_manager')
);
}
/**
* Overrides \Drupal\Core\Entity\EntityForm::prepareEntity().
*
* Prepares the custom block object.
*
* Fills in a few default values, and then invokes
* hook_block_content_prepare() on all modules.
*/
protected function prepareEntity() {
$block = $this->entity;
// Set up default values, if required.
$block_type = $this->blockContentTypeStorage->load($block->bundle());
if (!$block->isNew()) {
$block->setRevisionLogMessage(NULL);
}
// Always use the default revision setting.
$block->setNewRevision($block_type->shouldCreateNewRevision());
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$block = $this->entity;
$account = $this->currentUser();
$form = parent::form($form, $form_state);
if ($this->operation == 'edit') {
$form['#title'] = $this->t('Edit custom block %label', array('%label' => $block->label()));
......@@ -109,56 +34,7 @@ public function form(array $form, FormStateInterface $form_state) {
// names.
$form['#attributes']['class'][0] = 'block-' . Html::getClass($block->bundle()) . '-form';
$form['advanced'] = array(
'#type' => 'vertical_tabs',
'#weight' => 99,
);
// Add a log field if the "Create new revision" option is checked, or if the
// current user has the ability to check that option.
$form['revision_information'] = array(
'#type' => 'details',
'#title' => $this->t('Revision information'),
// Open by default when "Create new revision" is checked.
'#open' => $block->isNewRevision(),
'#group' => 'advanced',
'#attributes' => array(
'class' => array('block-content-form-revision-information'),
),
'#attached' => array(
'library' => array('block_content/drupal.block_content'),
),
'#weight' => 20,
'#access' => $block->isNewRevision() || $account->hasPermission('administer blocks'),
);
$form['revision_information']['revision'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Create new revision'),
'#default_value' => $block->isNewRevision(),
'#access' => $account->hasPermission('administer blocks'),
);
// Check the revision log checkbox when the log textarea is filled in.
// This must not happen if "Create new revision" is enabled by default,
// since the state would auto-disable the checkbox otherwise.
if (!$block->isNewRevision()) {
$form['revision_information']['revision']['#states'] = array(
'checked' => array(
'textarea[name="revision_log"]' => array('empty' => FALSE),
),
);
}
$form['revision_information']['revision_log'] = array(
'#type' => 'textarea',
'#title' => $this->t('Revision log message'),
'#rows' => 4,
'#default_value' => $block->getRevisionLog(),
'#description' => $this->t('Briefly describe the changes you have made.'),
);
return parent::form($form, $form_state, $block);
return $form;
}
/**
......@@ -167,19 +43,11 @@ public function form(array $form, FormStateInterface $form_state) {
public function save(array $form, FormStateInterface $form_state) {
$block = $this->entity;
// Save as a new revision if requested to do so.
if (!$form_state->isValueEmpty('revision')) {
$block->setNewRevision();
// If a new revision is created, save the current user as revision author.
$block->setRevisionCreationTime(REQUEST_TIME);
$block->setRevisionUserId(\Drupal::currentUser()->id());
}
$insert = $block->isNew();
$block->save();
$context = array('@type' => $block->bundle(), '%info' => $block->label());
$logger = $this->logger('block_content');
$block_type = $this->blockContentTypeStorage->load($block->bundle());
$block_type = $this->getBundleEntity();
$t_args = array('@type' => $block_type->label(), '%info' => $block->label());
if ($insert) {
......
......@@ -5,30 +5,12 @@
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Core\Entity\EntityInterface;
use Drupal\content_translation\ContentTranslationHandler;
use Drupal\Core\Form\FormStateInterface;
/**
* Defines the translation handler for custom blocks.
*/
class BlockContentTranslationHandler extends ContentTranslationHandler {
/**
* {@inheritdoc}
*/
public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
parent::entityFormAlter($form, $form_state, $entity);
// Move the translation fieldset to a vertical tab.
if (isset($form['translation'])) {
$form['translation'] += array(
'#group' => 'additional_settings',
'#weight' => 100,
'#attributes' => array(
'class' => array('block-content-translation-options'),
),
);
}
}
/**
* {@inheritdoc}
*/
......
......@@ -3,11 +3,12 @@
namespace Drupal\block_content;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\RevisionableEntityBundleInterface;
/**
* Provides an interface defining a custom block type entity.
*/
interface BlockContentTypeInterface extends ConfigEntityInterface {
interface BlockContentTypeInterface extends ConfigEntityInterface, RevisionableEntityBundleInterface {
/**
* Returns the description of the block type.
......@@ -17,12 +18,4 @@ interface BlockContentTypeInterface extends ConfigEntityInterface {
*/
public function getDescription();
/**
* Returns whether a new revision should be created by default.
*
* @return bool
* TRUE if a new revision should be created by default.
*/
public function shouldCreateNewRevision();
}
......@@ -35,6 +35,7 @@
* base_table = "block_content",
* revision_table = "block_content_revision",
* data_table = "block_content_field_data",
* show_revision_ui = TRUE,
* links = {
* "canonical" = "/block/{block_content}",
* "delete-form" = "/block/{block_content}/delete",
......@@ -182,7 +183,14 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['revision_log'] = BaseFieldDefinition::create('string_long')
->setLabel(t('Revision log message'))
->setDescription(t('The log entry explaining the changes in this revision.'))
->setRevisionable(TRUE);
->setRevisionable(TRUE)
->setDisplayOptions('form', array(
'type' => 'string_textarea',
'weight' => 25,
'settings' => array(
'rows' => 4,
),
));
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
......
......@@ -384,6 +384,16 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
'#multilingual' => TRUE,
);
if (isset($form['advanced'])) {
$form['content_translation'] += array(
'#group' => 'advanced',
'#weight' => 100,
'#attributes' => array(
'class' => array('entity-translation-options'),
),
);
}
// A new translation is enabled by default.
$metadata = $this->manager->getTranslationMetadata($entity);
$status = $new_translation || $metadata->isPublished();
......
......@@ -49,7 +49,7 @@ class MenuLinkContentForm extends ContentEntityForm {
* The path validator.
*/
public function __construct(EntityManagerInterface $entity_manager, MenuParentFormSelectorInterface $menu_parent_selector, LanguageManagerInterface $language_manager, PathValidatorInterface $path_validator) {
parent::__construct($entity_manager, $language_manager);
parent::__construct($entity_manager);
$this->menuParentSelector = $menu_parent_selector;
$this->pathValidator = $path_validator;
}
......
......@@ -18,21 +18,6 @@
Drupal.behaviors.nodeDetailsSummaries = {
attach: function (context) {
var $context = $(context);
$context.find('.node-form-revision-information').drupalSetSummary(function (context) {
var $revisionContext = $(context);
var revisionCheckbox = $revisionContext.find('.js-form-item-revision input');
// Return 'New revision' if the 'Create new revision' checkbox is
// checked, or if the checkbox doesn't exist, but the revision log does.
// For users without the "Administer content" permission the checkbox
// won't appear, but the revision log will if the content type is set to
// auto-revision.
if (revisionCheckbox.is(':checked') || (!revisionCheckbox.length && $revisionContext.find('.js-form-item-revision-log textarea').length)) {
return Drupal.t('New revision');
}
return Drupal.t('No revision');
});
$context.find('.node-form-author').drupalSetSummary(function (context) {
var $authorContext = $(context);
......@@ -64,22 +49,6 @@
return Drupal.t('Not promoted');
}
});
$context.find('fieldset.node-translation-options').drupalSetSummary(function (context) {
var $translationContext = $(context);
var translate;
var $checkbox = $translationContext.find('.js-form-item-translation-translate input');
if ($checkbox.size()) {
translate = $checkbox.is(':checked') ? Drupal.t('Needs to be updated') : Drupal.t('Does not need to be updated');
}
else {
$checkbox = $translationContext.find('.js-form-item-translation-retranslate input');
translate = $checkbox.is(':checked') ? Drupal.t('Flag other translations as outdated') : Drupal.t('Do not flag other translations as outdated');
}
return translate;
});
}
};
......
......@@ -6,10 +6,8 @@ drupal.node:
js:
node.js: {}
dependencies:
- core/jquery
- core/drupal
- core/drupal.entity-form
- core/drupalSettings
- core/drupal.form
drupal.node.preview:
version: VERSION
......
......@@ -46,6 +46,7 @@
* data_table = "node_field_data",
* revision_table = "node_revision",
* revision_data_table = "node_field_revision",
* show_revision_ui = TRUE,
* translatable = TRUE,
* list_cache_contexts = { "user.node_grants:view" },
* entity_keys = {
......
......@@ -205,4 +205,11 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti
$storage->resetCache(array_keys($entities));
}
/**
* {@inheritdoc}