Commit 2b0be1d8 authored by alexpott's avatar alexpott

Issue #2226493 by Berdir, Wim Leers, m1r1k, mr.baileys, andypost, scor, cbr,...

Issue #2226493 by Berdir, Wim Leers, m1r1k, mr.baileys, andypost, scor, cbr, joelpittet: Apply formatters and widgets to Node base fields.
parent a77de62a
......@@ -137,3 +137,23 @@ entity_form_display.field.string:
placeholder:
type: label
label: 'Placeholder'
entity_form_display.field.datetime_timestamp:
type: entity_field_form_display_base
label: 'Datetime timestamp display format settings'
mapping:
settings:
type: sequence
label: 'Settings'
sequence:
- type: string
entity_form_display.field.boolean_checkbox:
type: entity_field_form_display_base
label: 'Boolean checkbox display format settings'
mapping:
settings:
type: sequence
label: 'Settings'
sequence:
- type: string
<?php
/**
* @file
* Contains \Drupal\Core\Datetime\Plugin\Field\FieldWidget\TimestampDatetimeWidget.
*/
namespace Drupal\Core\Datetime\Plugin\Field\FieldWidget;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Datetime\Element\Datetime;
use Drupal\Core\Datetime\Entity\DateFormat;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'datetime timestamp' widget.
*
* @FieldWidget(
* id = "datetime_timestamp",
* label = @Translation("Datetime Timestamp"),
* field_types = {
* "timestamp",
* "created",
* }
* )
*/
class TimestampDatetimeWidget extends WidgetBase {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$date_format = DateFormat::load('html_date')->getPattern();
$time_format = DateFormat::load('html_time')->getPattern();
$default_value = isset($items[$delta]->value) ? DrupalDateTime::createFromTimestamp($items[$delta]->value) : '';
$element['value'] = $element + array(
'#type' => 'datetime',
'#default_value' => $default_value,
'#date_year_range' => '1902:2037',
);
$element['value']['#description'] = $this->t('Format: %format. Leave blank to use the time of form submission.', array('%format' => Datetime::formatExample($date_format . ' ' . $time_format)));
return $element;
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
foreach ($values as &$item) {
// @todo The structure is different whether access is denied or not, to
// be fixed in https://www.drupal.org/node/2326533.
if (isset($item['value']) && $item['value'] instanceof DrupalDateTime) {
$date = $item['value'];
}
else if (isset($item['value']['object']) && $item['value']['object'] instanceof DrupalDateTime) {
$date = $item['value']['object'];
}
else {
$date = new DrupalDateTime();
}
$item['value'] = $date->getTimestamp();
}
return $values;
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Field\Plugin\Field\FieldFormatter\TimestampFormatter.
*/
namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Plugin implementation of the 'timestamp' formatter.
*
* @FieldFormatter(
* id = "timestamp",
* label = @Translation("Default"),
* field_types = {
* "timestamp",
* "created",
* }
* )
*/
class TimestampFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items) {
$elements = array();
foreach ($items as $delta => $item) {
$elements[$delta] = array('#markup' => format_date($item->value));
}
return $elements;
}
}
......@@ -14,7 +14,8 @@
* id = "created",
* label = @Translation("Created"),
* description = @Translation("An entity field containing a UNIX timestamp of when the entity has been created."),
* no_ui = TRUE
* no_ui = TRUE,
* default_formatter = "timestamp",
* )
*/
class CreatedItem extends TimestampItem {
......
......@@ -18,7 +18,19 @@
* id = "timestamp",
* label = @Translation("Timestamp"),
* description = @Translation("An entity field containing a UNIX timestamp value."),
* no_ui = TRUE
* no_ui = TRUE,
* default_formatter = "timestamp",
* constraints = {
* "ComplexData" = {
* "value" = {
* "Range" = {
* "min" = "-2147483648",
* "max" = "2147483648",
* }
* }
* }
* }
* )
* )
*/
class TimestampItem extends FieldItemBase {
......@@ -45,4 +57,5 @@ public static function schema(FieldStorageDefinitionInterface $field_definition)
),
);
}
}
......@@ -9,9 +9,6 @@
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Datetime\Element\Datetime;
use Drupal\node\NodeInterface;
/**
* Defines the timezone that dates should be stored in.
......@@ -137,23 +134,3 @@ function datetime_datelist_widget_validate(&$element, FormStateInterface $form_s
function datetime_date_default_time($date) {
$date->setTime(12, 0, 0);
}
/**
* Implements hook_form_BASE_FORM_ID_alter() for node forms.
*/
function datetime_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Alter the 'Authored on' date to use datetime.
$form['created']['#type'] = 'datetime';
$date_format = entity_load('date_format', 'html_date')->getPattern();
$time_format = entity_load('date_format', 'html_time')->getPattern();
$form['created']['#description'] = t('Format: %format. Leave blank to use the time of form submission.', array('%format' => Datetime::formatExample($date_format . ' ' . $time_format)));
unset($form['created']['#maxlength']);
}
/**
* Implements hook_node_prepare_form().
*/
function datetime_node_prepare_form(NodeInterface $node, $operation, FormStateInterface $form_state) {
// Prepare the 'Authored on' date to use datetime.
$node->date = DrupalDateTime::createFromTimestamp($node->getCreatedTime());
}
......@@ -286,19 +286,23 @@ public function testRenameDeleteBundle() {
$this->assertEqual('article_rename', $new_form_display->bundle);
$this->assertEqual('node.article_rename.default', $new_form_display->id);
$expected_dependencies = array(
$expected_view_dependencies = array(
'entity' => array('field.instance.node.article_rename.body', 'node.type.article_rename'),
'module' => array('text')
'module' => array('text', 'user')
);
// Check that the display has dependencies on the bundle, fields and the
// modules that provide the formatters.
$dependencies = $new_display->calculateDependencies();
$this->assertEqual($expected_dependencies, $dependencies);
$this->assertEqual($expected_view_dependencies, $dependencies);
// Check that the form display has dependencies on the bundle, fields and
// the modules that provide the formatters.
$dependencies = $new_form_display->calculateDependencies();
$this->assertEqual($expected_dependencies, $dependencies);
$expected_form_dependencies = array(
'entity' => array('field.instance.node.article_rename.body', 'node.type.article_rename'),
'module' => array('text')
);
$this->assertEqual($expected_form_dependencies, $dependencies);
// Delete the bundle.
$type->delete();
......
......@@ -73,7 +73,7 @@ public function elementValidate($element, FormStateInterface $form_state, $form)
elseif (preg_match("/.+\(([\w.]+)\)/", $element['#value'], $matches)) {
$value = $matches[1];
}
if (!$value) {
if ($value === NULL) {
// Try to get a match from the input string when the user didn't use the
// autocomplete but filled in a value manually.
$handler = \Drupal::service('plugin.manager.entity_reference.selection')->getSelectionHandler($this->fieldDefinition);
......
......@@ -113,6 +113,9 @@ public function testAvailableFormatters() {
// Create entity reference field with taxonomy term as a target.
$taxonomy_term_field_name = $this->createEntityReferenceField('taxonomy_term', 'tags');
// Create entity reference field with user as a target.
$user_field_name = $this->createEntityReferenceField('user');
// Create entity reference field with node as a target.
$node_field_name = $this->createEntityReferenceField('node', $this->type);
......@@ -132,6 +135,17 @@ public function testAvailableFormatters() {
'hidden',
));
// Test if User Reference Field has the correct formatters.
// Author should be available for this field.
// RSS Category should not be available for this field.
$this->assertFieldSelectOptions('fields[field_' . $user_field_name . '][type]', array(
'author',
'entity_reference_entity_id',
'entity_reference_entity_view',
'entity_reference_label',
'hidden',
));
// Test if Node Entity Reference Field has the correct formatters.
// RSS Category should not be available for this field.
$this->assertFieldSelectOptions('fields[field_' . $node_field_name . '][type]', array(
......
......@@ -59,6 +59,7 @@ protected function setUp() {
* Tests the entity reference field with all its supported field widgets.
*/
public function testSupportedEntityTypesAndWidgets() {
$user_id = mt_rand(128, 256);
foreach ($this->getTestEntities() as $referenced_entities) {
$this->fieldName = 'field_test_' . $referenced_entities[0]->getEntityTypeId();
......@@ -68,10 +69,15 @@ public function testSupportedEntityTypesAndWidgets() {
// Test the default 'entity_reference_autocomplete' widget.
entity_get_form_display($this->entityType, $this->bundle, 'default')->setComponent($this->fieldName)->save();
$user_id++;
entity_create('user', array(
'uid' => $user_id,
'name' => $this->randomString(),
))->save();
$entity_name = $this->randomMachineName();
$edit = array(
'name' => $entity_name,
'user_id' => mt_rand(0, 128),
'user_id' => $user_id,
$this->fieldName . '[0][target_id]' => $referenced_entities[0]->label() . ' (' . $referenced_entities[0]->id() . ')',
// Test an input of the entity label without a ' (entity_id)' suffix.
$this->fieldName . '[1][target_id]' => $referenced_entities[1]->label(),
......@@ -94,9 +100,14 @@ public function testSupportedEntityTypesAndWidgets() {
$target_id = $referenced_entities[0]->label() . ' (' . $referenced_entities[0]->id() . ')';
// Test an input of the entity label without a ' (entity_id)' suffix.
$target_id .= ', ' . $referenced_entities[1]->label();
$user_id++;
entity_create('user', array(
'uid' => $user_id,
'name' => $this->randomString(),
))->save();
$edit = array(
'name' => $entity_name,
'user_id' => mt_rand(0, 128),
'user_id' => $user_id,
$this->fieldName . '[target_id]' => $target_id,
);
$this->drupalPostForm($this->entityType . '/add', $edit, t('Save'));
......@@ -111,9 +122,11 @@ public function testSupportedEntityTypesAndWidgets() {
// Test all the other widgets supported by the entity reference field.
// Since we don't know the form structure for these widgets, just test
// that editing and saving an already created entity works.
// Also exclude the special author reference widgets.
$exclude = array('entity_reference_autocomplete', 'entity_reference_autocomplete_tags', 'route_based_autocomplete', 'author_autocomplete');
$entity = current(entity_load_multiple_by_properties($this->entityType, array('name' => $entity_name)));
$supported_widgets = \Drupal::service('plugin.manager.field.widget')->getOptions('entity_reference');
$supported_widget_types = array_diff(array_keys($supported_widgets), array('entity_reference_autocomplete', 'entity_reference_autocomplete_tags'));
$supported_widget_types = array_diff(array_keys($supported_widgets), $exclude);
foreach ($supported_widget_types as $widget_type) {
entity_get_form_display($this->entityType, $this->bundle, 'default')->setComponent($this->fieldName, array(
......
......@@ -11,12 +11,12 @@ fieldMappings:
properties:
- 'schema:dateCreated'
datatype_callback:
callable: 'date_iso8601'
callable: 'Drupal\rdf\CommonDataConverter::dateIso8601Value'
changed:
properties:
- 'schema:dateModified'
datatype_callback:
callable: 'date_iso8601'
callable: 'Drupal\rdf\CommonDataConverter::dateIso8601Value'
body:
properties:
- 'schema:text'
......
......@@ -169,8 +169,8 @@ protected function createForumTopics($count = 5) {
'body[0][value]' => $body,
// Forum posts are ordered by timestamp, so force a unique timestamp by
// adding the index.
'created[date]' => $date->format('Y-m-d'),
'created[time]' => $date->format('H:i:s'),
'created[0][value][date]' => $date->format('Y-m-d'),
'created[0][value][time]' => $date->format('H:i:s'),
);
// Create the forum topic, preselecting the forum ID via a URL parameter.
......
......@@ -7,3 +7,4 @@ core: 8.x
configure: node.overview_types
dependencies:
- text
- entity_reference
......@@ -27,8 +27,8 @@
$context.find('.node-form-author').drupalSetSummary(function (context) {
var $context = $(context);
var name = $context.find('.form-item-name input').val() || drupalSettings.anonymous,
date = $context.find('.form-item-date input').val();
var name = $context.find('.field-name-uid input').val(),
date = $context.find('.field-name-created input').val();
return date ?
Drupal.t('By @name on @date', { '@name': name, '@date': date }) :
Drupal.t('By @name', { '@name': name });
......@@ -39,7 +39,7 @@
var vals = [];
if ($context.find('input').is(':checked')) {
$context.find('input:checked').parent().each(function () {
$context.find('input:checked').next('label').each(function () {
vals.push(Drupal.checkPlain($.trim($(this).text())));
});
return vals.join(', ');
......
......@@ -177,6 +177,14 @@ function node_theme() {
'base hook' => 'field',
'template' => 'field--node--title',
),
'field__node__uid' => array(
'base hook' => 'field',
'template' => 'field--node--uid',
),
'field__node__created' => array(
'base hook' => 'field',
'template' => 'field--node--created',
),
);
}
......@@ -197,15 +205,6 @@ function node_entity_view_display_alter(EntityViewDisplayInterface $display, $co
}
}
/**
* Implements hook_entity_form_display_alter().
*/
function node_entity_form_display_alter(EntityFormDisplayInterface $form_display, $context) {
if ($context['entity_type'] == 'node') {
$node_type = node_type_load($context['bundle']);
}
}
/**
* Gathers a listing of links to nodes.
*
......@@ -614,14 +613,10 @@ function template_preprocess_node(&$variables) {
$variables['node'] = $variables['elements']['#node'];
/** @var \Drupal\node\NodeInterface $node */
$node = $variables['node'];
$variables['date'] = format_date($node->getCreatedTime());
$username = array(
'#theme' => 'username',
'#account' => $node->getOwner(),
'#link_options' => array('attributes' => array('rel' => 'author')),
);
$variables['author_name'] = drupal_render($username);
$variables['date'] = drupal_render($variables['elements']['created'], TRUE);
unset($variables['elements']['created']);
$variables['author_name'] = drupal_render($variables['elements']['uid'], TRUE);
unset($variables['elements']['uid']);
$variables['url'] = $node->url('canonical', array(
'language' => $node->language(),
......
......@@ -91,11 +91,7 @@ public function addPage() {
* A node submission form.
*/
public function add(NodeTypeInterface $node_type) {
$account = $this->currentUser();
$node = $this->entityManager()->getStorage('node')->create(array(
'uid' => $account->id(),
'name' => $account->getUsername() ?: '',
'type' => $node_type->type,
));
......
......@@ -372,11 +372,29 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setDisplayConfigurable('form', TRUE);
$fields['uid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Author'))
->setDescription(t('The user that is the node author.'))
->setLabel(t('Authored by'))
->setDescription(t('The user ID of the node author.'))
->setRevisionable(TRUE)
->setSetting('target_type', 'user')
->setTranslatable(TRUE);
->setSetting('handler', 'default')
->setDefaultValueCallback(array('Drupal\node\Entity\Node', 'getCurrentUserId'))
->setTranslatable(TRUE)
->setDisplayOptions('view', array(
'label' => 'hidden',
'type' => 'author',
'weight' => 0,
))
->setDisplayOptions('form', array(
'type' => 'entity_reference_autocomplete',
'weight' => 5,
'settings' => array(
'match_operator' => 'CONTAINS',
'size' => '60',
'autocomplete_type' => 'tags',
'placeholder' => '',
),
))
->setDisplayConfigurable('form', TRUE);
$fields['status'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Publishing status'))
......@@ -386,10 +404,20 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setDefaultValue(TRUE);
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Created'))
->setLabel(t('Authored on'))
->setDescription(t('The time that the node was created.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE);
->setTranslatable(TRUE)
->setDisplayOptions('view', array(
'label' => 'hidden',
'type' => 'timestamp',
'weight' => 0,
))
->setDisplayOptions('form', array(
'type' => 'datetime_timestamp',
'weight' => 10,
))
->setDisplayConfigurable('form', TRUE);
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
......@@ -402,13 +430,29 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setDescription(t('A boolean indicating whether the node should be displayed on the front page.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE)
->setDefaultValue(TRUE);
->setDefaultValue(TRUE)
->setDisplayOptions('form', array(
'type' => 'boolean_checkbox',
'settings' => array(
'display_label' => TRUE,
),
'weight' => 15,
))
->setDisplayConfigurable('form', TRUE);
$fields['sticky'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Sticky'))
->setDescription(t('A boolean indicating whether the node should be displayed at the top of lists in which it appears.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE);
->setTranslatable(TRUE)
->setDisplayOptions('form', array(
'type' => 'boolean_checkbox',
'settings' => array(
'display_label' => TRUE,
),
'weight' => 16,
))
->setDisplayConfigurable('form', TRUE);
$fields['revision_timestamp'] = BaseFieldDefinition::create('created')
->setLabel(t('Revision timestamp'))
......@@ -425,11 +469,30 @@ 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.'))
->setDescription(t('Briefly describe the changes you have made.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE);
->setTranslatable(TRUE)
->setDisplayOptions('form', array(
'type' => 'string_textarea',
'weight' => 25,
'settings' => array(
'rows' => 4,
),
));
return $fields;
}
/**
* Default value callback for 'uid' base field definition.
*
* @see ::baseFieldDefinitions()
*
* @return array
* An array of default values.
*/
public static function getCurrentUserId() {
return array(\Drupal::currentUser()->id());
}
}
......@@ -129,14 +129,28 @@ protected function checkCreateAccess(AccountInterface $account, array $context,
* {@inheritdoc}
*/
protected function checkFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
$administrative_fields = array('uid', 'status', 'created', 'promote', 'sticky', 'revision_log');
$read_only_fields = array('changed', 'revision_timestamp', 'revision_uid');
// Only users with the administer nodes permission can edit administrative
// fields.
$administrative_fields = array('uid', 'status', 'created', 'promote', 'sticky');
if ($operation == 'edit' && in_array($field_definition->getName(), $administrative_fields)) {
return $account->hasPermission('administer nodes');
}
// No user can change read only fields.
$read_only_fields = array('changed', 'revision_timestamp', 'revision_uid');
if ($operation == 'edit' && in_array($field_definition->getName(), $read_only_fields)) {
return FALSE;
}
// Users have access to the revision_log field either if they have
// administrative permissions or if the new revision option is enabled.
if ($operation == 'edit' && $field_definition->getName() == 'revision_log') {
if ($account->hasPermission('administer nodes')) {
return TRUE;
}
$node_type_settings = $items->getEntity()->type->entity->getModuleSettings('node');
return !empty($node_type_settings['options']['revision']);
}
return parent::checkFieldAccess($operation, $field_definition, $account, $items);
}
......
......@@ -7,15 +7,14 @@
namespace Drupal\node;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Component\Utility\String;
use Drupal\Core\Entity\EntityManagerInterface;