Newer
Older
<?php
/**
* @file
* Contains \Drupal\field_ui\FieldOverview.
*/
namespace Drupal\field_ui;
catch
committed
use Drupal\Core\Entity\EntityManager;
use Drupal\Core\Entity\Field\FieldTypePluginManager;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\field_ui\OverviewBase;
Alex Pott
committed
use Symfony\Component\DependencyInjection\ContainerInterface;
Alex Pott
committed
use Drupal\field\Plugin\Core\Entity\Field;
/**
* Field UI field overview form.
*/
class FieldOverview extends OverviewBase {
catch
committed
/**
* The field type manager.
*
* @var \Drupal\Core\Entity\Field\FieldTypePluginManager
*/
protected $fieldTypeManager;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
catch
committed
/**
* Constructs a new FieldOverview.
*
* @param \Drupal\Core\Entity\EntityManager $entity_manager
* The entity manager.
* @param \Drupal\Core\Entity\Field\FieldTypePluginManager $field_type_manager
* The field type manager
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke hooks on.
catch
committed
*/
public function __construct(EntityManager $entity_manager, FieldTypePluginManager $field_type_manager, ModuleHandlerInterface $module_handler) {
parent::__construct($entity_manager);
catch
committed
$this->fieldTypeManager = $field_type_manager;
$this->moduleHandler = $module_handler;
catch
committed
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.entity'),
$container->get('plugin.manager.entity.field.field_type'),
$container->get('module_handler')
catch
committed
);
}
Alex Pott
committed
/**
* {@inheritdoc}
*/
public function getRegions() {
return array(
'content' => array(
'title' => t('Content'),
'invisible' => TRUE,
Alex Pott
committed
// @todo Bring back this message in https://drupal.org/node/1963340.
//'message' => t('No fields are present yet.'),
),
);
}
/**
Alex Pott
committed
* {@inheritdoc}
public function getFormID() {
return 'field_ui_field_overview_form';
}
Alex Pott
committed
* {@inheritdoc}
Alex Pott
committed
public function buildForm(array $form, array &$form_state, $entity_type = NULL, $bundle = NULL) {
parent::buildForm($form, $form_state, $entity_type, $bundle);
// Gather bundle information.
$instances = field_info_instances($this->entity_type, $this->bundle);
catch
committed
$field_types = $this->fieldTypeManager->getDefinitions();
Alex Pott
committed
// Field prefix.
$field_prefix = config('field_ui.settings')->get('field_prefix');
$form += array(
'#entity_type' => $this->entity_type,
'#bundle' => $this->bundle,
'#fields' => array_keys($instances),
);
$table = array(
'#type' => 'field_ui_table',
'#tree' => TRUE,
'#header' => array(
t('Label'),
t('Machine name'),
t('Field type'),
t('Operations'),
),
'#regions' => $this->getRegions(),
'#attributes' => array(
'class' => array('field-ui-overview'),
'id' => 'field-overview',
),
);
// Fields.
foreach ($instances as $name => $instance) {
$field = field_info_field($instance['field_name']);
$admin_field_path = $this->adminPath . '/fields/' . $instance->id();
$table[$name] = array(
Alex Pott
committed
'#attributes' => array(
'id' => drupal_html_class($name),
),
'label' => array(
'#markup' => check_plain($instance['label']),
),
'field_name' => array(
'#markup' => $instance['field_name'],
),
'type' => array(
'#type' => 'link',
'#title' => $field_types[$field['type']]['label'],
Alex Pott
committed
'#href' => $admin_field_path . '/field',
'#options' => array('attributes' => array('title' => t('Edit field settings.'))),
),
);
$links = array();
$links['edit'] = array(
'title' => t('Edit'),
'href' => $admin_field_path,
'attributes' => array('title' => t('Edit instance settings.')),
);
Alex Pott
committed
$links['field-settings'] = array(
'title' => t('Field settings'),
'href' => $admin_field_path . '/field',
'attributes' => array('title' => t('Edit field settings.')),
);
$links['delete'] = array(
'title' => t('Delete'),
'href' => "$admin_field_path/delete",
'attributes' => array('title' => t('Delete instance.')),
);
// Allow altering the operations on this entity listing.
$this->moduleHandler->alter('entity_operation', $links, $instance);
$table[$name]['operations']['data'] = array(
'#type' => 'operations',
'#links' => $links,
);
if (!empty($field['locked'])) {
$table[$name]['operations'] = array('#markup' => t('Locked'));
$table[$name]['#attributes']['class'][] = 'menu-disabled';
}
}
Alex Pott
committed
// Gather valid field types.
$field_type_options = array();
foreach ($field_types as $name => $field_type) {
Alex Pott
committed
// Skip field types which should not be added via user interface.
if (empty($field_type['no_ui'])) {
Alex Pott
committed
$field_type_options[$name] = $field_type['label'];
}
}
asort($field_type_options);
Alex Pott
committed
// Additional row: add new field.
if ($field_type_options) {
$name = '_add_new_field';
$table[$name] = array(
Alex Pott
committed
'#attributes' => array('class' => array('add-new')),
'label' => array(
'#type' => 'textfield',
'#title' => t('New field label'),
'#title_display' => 'invisible',
'#size' => 15,
'#description' => t('Label'),
'#prefix' => '<div class="label-input"><div class="add-new-placeholder">' . t('Add new field') .'</div>',
'#suffix' => '</div>',
),
'field_name' => array(
'#type' => 'machine_name',
'#title' => t('New field name'),
'#title_display' => 'invisible',
// This field should stay LTR even for RTL languages.
Alex Pott
committed
'#field_prefix' => '<span dir="ltr">' . $field_prefix,
'#field_suffix' => '</span>‎',
'#size' => 15,
'#description' => t('A unique machine-readable name containing letters, numbers, and underscores.'),
Alex Pott
committed
// Calculate characters depending on the length of the field prefix
// setting. Maximum length is 32.
'#maxlength' => Field::ID_MAX_LENGTH - strlen($field_prefix),
'#prefix' => '<div class="add-new-placeholder"> </div>',
'#machine_name' => array(
'source' => array('fields', $name, 'label'),
Alex Pott
committed
'exists' => array($this, 'fieldNameExists'),
'standalone' => TRUE,
'label' => '',
),
'#required' => FALSE,
),
'type' => array(
'#type' => 'select',
'#title' => t('Type of new field'),
'#title_display' => 'invisible',
'#options' => $field_type_options,
'#empty_option' => t('- Select a field type -'),
'#description' => t('Type of data to store.'),
'#attributes' => array('class' => array('field-type-select')),
Alex Pott
committed
'#cell_attributes' => array('colspan' => 2),
'#prefix' => '<div class="add-new-placeholder"> </div>',
),
// Place the 'translatable' property as an explicit value so that
// contrib modules can form_alter() the value for newly created fields.
'translatable' => array(
'#type' => 'value',
'#value' => FALSE,
),
);
}
// Additional row: re-use existing field.
Alex Pott
committed
$existing_fields = $this->getExistingFieldOptions();
Alex Pott
committed
if ($existing_fields) {
// Build list of options.
$existing_field_options = array();
foreach ($existing_fields as $field_name => $info) {
$text = t('@type: @field (@label)', array(
'@type' => $info['type_label'],
'@label' => $info['label'],
'@field' => $info['field'],
));
$existing_field_options[$field_name] = truncate_utf8($text, 80, FALSE, TRUE);
}
asort($existing_field_options);
$name = '_add_existing_field';
$table[$name] = array(
Alex Pott
committed
'#attributes' => array('class' => array('add-new')),
'#row_type' => 'add_new_field',
Alex Pott
committed
'#region_callback' => array($this, 'getRowRegion'),
'label' => array(
'#type' => 'textfield',
'#title' => t('Existing field label'),
'#title_display' => 'invisible',
'#size' => 15,
'#description' => t('Label'),
'#attributes' => array('class' => array('label-textfield')),
'#prefix' => '<div class="label-input"><div class="add-new-placeholder">' . t('Re-use existing field') .'</div>',
'#suffix' => '</div>',
),
'field_name' => array(
'#type' => 'select',
'#title' => t('Existing field to share'),
'#title_display' => 'invisible',
'#options' => $existing_field_options,
'#empty_option' => t('- Select an existing field -'),
'#description' => t('Field to share'),
'#attributes' => array('class' => array('field-select')),
'#cell_attributes' => array('colspan' => 3),
'#prefix' => '<div class="add-new-placeholder"> </div>',
),
);
}
Alex Pott
committed
// We can set the 'rows_order' element, needed by theme_field_ui_table(),
// here instead of a #pre_render callback because this form doesn't have the
// tabledrag behavior anymore.
foreach (element_children($table) as $name) {
$table['#regions']['content']['rows_order'][] = $name;
}
Alex Pott
committed
$form['fields'] = $table;
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save'));
return $form;
}
/**
Alex Pott
committed
* {@inheritdoc}
public function validateForm(array &$form, array &$form_state) {
$this->validateAddNew($form, $form_state);
$this->validateAddExisting($form, $form_state);
}
/**
* Validates the 'add new field' row.
*
* @param array $form
* An associative array containing the structure of the form.
* @param array $form_state
* A reference to a keyed array containing the current state of the form.
*
* @see Drupal\field_ui\FieldOverview::validateForm()
*/
protected function validateAddNew(array $form, array &$form_state) {
$field = $form_state['values']['fields']['_add_new_field'];
// Validate if any information was provided in the 'add new field' row.
Alex Pott
committed
if (array_filter(array($field['label'], $field['field_name'], $field['type']))) {
// Missing label.
if (!$field['label']) {
form_set_error('fields][_add_new_field][label', t('Add new field: you need to provide a label.'));
}
// Missing field name.
if (!$field['field_name']) {
form_set_error('fields][_add_new_field][field_name', t('Add new field: you need to provide a field name.'));
}
// Field name validation.
else {
$field_name = $field['field_name'];
Alex Pott
committed
// Add the field prefix.
$field_name = config('field_ui.settings')->get('field_prefix') . $field_name;
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
form_set_value($form['fields']['_add_new_field']['field_name'], $field_name, $form_state);
}
// Missing field type.
if (!$field['type']) {
form_set_error('fields][_add_new_field][type', t('Add new field: you need to select a field type.'));
}
}
}
/**
* Validates the 're-use existing field' row.
*
* @param array $form
* An associative array containing the structure of the form.
* @param array $form_state
* A reference to a keyed array containing the current state of the form.
*
* @see Drupal\field_ui\FieldOverview::validate()
*/
protected function validateAddExisting(array $form, array &$form_state) {
// The form element might be absent if no existing fields can be added to
// this bundle.
if (isset($form_state['values']['fields']['_add_existing_field'])) {
$field = $form_state['values']['fields']['_add_existing_field'];
// Validate if any information was provided in the
// 're-use existing field' row.
Alex Pott
committed
if (array_filter(array($field['label'], $field['field_name']))) {
// Missing label.
if (!$field['label']) {
form_set_error('fields][_add_existing_field][label', t('Re-use existing field: you need to provide a label.'));
}
// Missing existing field name.
if (!$field['field_name']) {
form_set_error('fields][_add_existing_field][field_name', t('Re-use existing field: you need to select a field.'));
}
}
}
}
/**
* Overrides \Drupal\field_ui\OverviewBase::submitForm().
public function submitForm(array &$form, array &$form_state) {
$form_values = $form_state['values']['fields'];
$destinations = array();
// Create new field.
if (!empty($form_values['_add_new_field']['field_name'])) {
$values = $form_values['_add_new_field'];
$field = array(
'field_name' => $values['field_name'],
'type' => $values['type'],
'translatable' => $values['translatable'],
);
$instance = array(
'field_name' => $field['field_name'],
'entity_type' => $this->entity_type,
'bundle' => $this->bundle,
'label' => $values['label'],
);
// Create the field and instance.
try {
$this->entityManager->getStorageController('field_entity')->create($field)->save();
$new_instance = $this->entityManager->getStorageController('field_instance')->create($instance);
$new_instance->save();
Alex Pott
committed
// Make sure the field is displayed in the 'default' form mode (using
Alex Pott
committed
// default widget and settings). It stays hidden for other form modes
// until it is explicitly configured.
Alex Pott
committed
entity_get_form_display($this->entity_type, $this->bundle, 'default')
Alex Pott
committed
->setComponent($field['field_name'])
Alex Pott
committed
->save();
// Make sure the field is displayed in the 'default' view mode (using
// default formatter and settings). It stays hidden for other view
// modes until it is explicitly configured.
entity_get_display($this->entity_type, $this->bundle, 'default')
->setComponent($field['field_name'])
->save();
// Always show the field settings step, as the cardinality needs to be
// configured for new fields.
Alex Pott
committed
$destinations[] = $this->adminPath. '/fields/' . $new_instance->id() . '/field';
$destinations[] = $this->adminPath . '/fields/' . $new_instance->id();
// Store new field information for any additional submit handlers.
$form_state['fields_added']['_add_new_field'] = $field['field_name'];
}
Alex Pott
committed
catch (\Exception $e) {
drupal_set_message(t('There was a problem creating field %label: !message', array('%label' => $instance['label'], '!message' => $e->getMessage())), 'error');
}
}
// Re-use existing field.
if (!empty($form_values['_add_existing_field']['field_name'])) {
$values = $form_values['_add_existing_field'];
$field = field_info_field($values['field_name']);
if (!empty($field['locked'])) {
drupal_set_message(t('The field %label cannot be added because it is locked.', array('%label' => $values['label'])), 'error');
}
else {
$instance = array(
'field_name' => $field['field_name'],
'entity_type' => $this->entity_type,
'bundle' => $this->bundle,
'label' => $values['label'],
);
try {
$new_instance = $this->entityManager->getStorageController('field_instance')->create($instance);
$new_instance->save();
Alex Pott
committed
// Make sure the field is displayed in the 'default' form mode (using
Alex Pott
committed
// default widget and settings). It stays hidden for other form modes
// until it is explicitly configured.
Alex Pott
committed
entity_get_form_display($this->entity_type, $this->bundle, 'default')
Alex Pott
committed
->setComponent($field['field_name'])
Alex Pott
committed
->save();
// Make sure the field is displayed in the 'default' view mode (using
// default formatter and settings). It stays hidden for other view
// modes until it is explicitly configured.
entity_get_display($this->entity_type, $this->bundle, 'default')
->setComponent($field['field_name'])
->save();
$destinations[] = $this->adminPath . '/fields/' . $new_instance->id();
// Store new field information for any additional submit handlers.
$form_state['fields_added']['_add_existing_field'] = $instance['field_name'];
}
Alex Pott
committed
catch (\Exception $e) {
drupal_set_message(t('There was a problem creating field instance %label: @message.', array('%label' => $instance['label'], '@message' => $e->getMessage())), 'error');
}
}
}
if ($destinations) {
$destination = drupal_get_destination();
$destinations[] = $destination['destination'];
unset($_GET['destination']);
Alex Pott
committed
$path = array_shift($destinations);
$options = drupal_parse_url($path);
$options['query']['destinations'] = $destinations;
$form_state['redirect'] = array($options['path'], $options);
}
else {
drupal_set_message(t('Your settings have been saved.'));
}
}
Alex Pott
committed
/**
* Returns an array of existing fields to be added to a bundle.
*
* @return array
* An array of existing fields keyed by field name.
*/
protected function getExistingFieldOptions() {
$info = array();
catch
committed
$field_types = \Drupal::service('plugin.manager.entity.field.field_type')->getDefinitions();
Alex Pott
committed
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
foreach (field_info_instances() as $existing_entity_type => $bundles) {
foreach ($bundles as $existing_bundle => $instances) {
// No need to look in the current bundle.
if (!($existing_bundle == $this->bundle && $existing_entity_type == $this->entity_type)) {
foreach ($instances as $instance) {
$field = field_info_field($instance['field_name']);
// Don't show
// - locked fields,
// - fields already in the current bundle,
// - fields that cannot be added to the entity type,
// - fields that should not be added via user interface.
if (empty($field['locked'])
&& !field_info_instance($this->entity_type, $field['field_name'], $this->bundle)
&& (empty($field['entity_types']) || in_array($this->entity_type, $field['entity_types']))
&& empty($field_types[$field['type']]['no_ui'])) {
$info[$instance['field_name']] = array(
'type' => $field['type'],
'type_label' => $field_types[$field['type']]['label'],
'field' => $field['field_name'],
'label' => $instance['label'],
);
}
}
}
}
}
return $info;
}
/**
* Checks if a field machine name is taken.
*
* @param string $value
* The machine name, not prefixed with 'field_'.
*
* @return bool
* Whether or not the field machine name is taken.
*/
public function fieldNameExists($value) {
// Prefix with 'field_'.
$field_name = 'field_' . $value;
// We need to check inactive fields as well, so we can't use
// field_info_fields().
return (bool) field_read_fields(array('field_name' => $field_name), array('include_inactive' => TRUE));
}