Index: modules/display_fields/src/Plugin/DisplayFieldsField/FieldExtraField.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- modules/display_fields/src/Plugin/DisplayFieldsField/FieldExtraField.php	(revision )
+++ modules/display_fields/src/Plugin/DisplayFieldsField/FieldExtraField.php	(revision )
@@ -0,0 +1,406 @@
+<?php
+
+
+namespace Drupal\display_fields\Plugin\DisplayFieldsField;
+
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\display_fields\DisplayFields;
+use Drupal\display_fields\Plugin\DisplayFieldsField\Field;
+
+/**
+ * Plugin that renders an extra field.
+ *
+ * @DisplayFieldsField(
+ *   id = "custom_extra_field",
+ *   title = @Translation("Add custom extra field"),
+ *   entity_types = {},
+ * )
+ */
+class FieldExtraField extends Field {
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createForm($form, FormStateInterface $form_state, $parents = array()) {
+    //The current entity for the form "Manage display".
+    $entity_type = $form_state->getCompleteForm()['#entity_type'];
+    $bundle = $form_state->getCompleteForm()['#bundle'];
+    $view_mode = $this->getCurrentViewMode();
+    //Prepare the form to create the form for adding a new custom extrafield.
+    $form['bundle'] = array(
+      '#type' => 'hidden',
+      '#value' => $bundle
+    );
+    $form['view_mode'] = array(
+      '#type' => 'hidden',
+      '#value' => $view_mode
+    );
+    $form['entity_type'] = array(
+      '#type' => 'hidden',
+      '#value' => $entity_type
+    );
+    $form['class'] = array(
+      '#type' => 'textfield',
+      '#default_value' => '',
+      '#title' => t('Class')
+    );
+    $form['tag'] = array(
+      '#type' => 'select',
+      '#options' => array(
+        'span' => 'SPAN',
+        'div' => 'DIV',
+        'strong' => 'STRONG',
+        'h2' => 'H2',
+        'h3' => 'H3',
+        'h4' => 'H4',
+        'h5' => 'H5',
+        'h6' => 'H6'
+      ),
+      '#default_value' => 'div',
+      '#title' => t('Tag')
+    );
+    $form['data'] = array(
+      '#type' => 'textarea',
+      '#default_value' => '',
+      '#title' => t('Write text here'),
+      '#description' => t('Add html code with valid tokens if you want.'),
+    );
+     // Add the token browser at the bottom.
+     $form['token'] = \Drupal::service('displayfields.token')->tokenBrowser();
+    return $form;
+  }
+
+  /**
+   * This method is responsible for editing the custom extra field.
+   * @param string $field_name
+   * @param array $field
+   * @param array $field_display_settings
+   * @param string $view_mode
+   * @param FormStateInterface $form_state
+   * @param array $complete_form
+   * @return array
+   */
+  public function buildFieldFormRow($field_name, $field, $field_display_settings, $view_mode, FormStateInterface $form_state, &$complete_form) {
+
+    $row = parent::buildFieldFormRow($field_name, $field, $field_display_settings, $view_mode, $form_state, $complete_form);
+
+    // Base button element for the various plugin settings actions.
+    $base_button = array(
+      '#submit' => array(array($this, 'multistepSubmit')),
+      '#ajax' => array(
+        'callback' => array($this, 'multistepAjax'),
+        'wrapper' => 'field-display-overview-wrapper',
+        'effect' => 'fade',
+      ),
+      '#field_name' => 'display_fields_' . $field_name,
+    );
+    if (!empty($field_display_settings) && $form_state->get('plugin_settings_edit') == 'display_fields_' . $field_name) {
+
+      $row['plugin'] = array(
+        'type' => array(
+          '#type' => 'select',
+          '#title' => t('Plugin for @title', array('@title' => $field['label'])),
+          '#title_display' => 'invisible',
+          '#options' => $this->getExtraFieldVisibilityOptions(),
+          '#default_value' => !empty($field_display_settings) ? $field_display_settings['type'] : 'hidden',
+          '#parents' => array(
+            'fields',
+            'display_fields_' . $field_name,
+            'type'
+          ),
+          '#attributes' => array('class' => array('field-plugin-type')),
+        ),
+      );
+
+      // Generate the settings form and allow other modules to alter it.
+      $row['plugin']['#cell_attributes'] = array('colspan' => 3);
+      $settings_form = $this->fieldRowSettingsForm($complete_form, $form_state, $field);
+      $row['plugin']['settings_edit_form'] = array(
+        '#type' => 'container',
+        '#attributes' => array('class' => array('field-plugin-settings-edit-form')),
+        '#parents' => array(
+          'fields',
+          'display_fields_' . $field_name,
+          'settings_edit_form'
+        ),
+        'label' => array(
+          '#markup' => t('Plugin settings'),
+        ),
+        'settings' => $settings_form,
+        'actions' => array(
+          '#type' => 'actions',
+          'save_settings' => $base_button + array(
+              '#type' => 'submit',
+              '#button_type' => 'primary',
+              '#name' => 'display_fields_' . $field_name . '_plugin_settings_update',
+              '#value' => t('Update'),
+              '#op' => 'update',
+            ),
+          'cancel_settings' => $base_button + array(
+              '#type' => 'submit',
+              '#name' => 'display_fields_' . $field_name . '_plugin_settings_cancel',
+              '#value' => t('Cancel'),
+              '#op' => 'cancel',
+              // Do not check errors for the 'Cancel' button, but make sure we
+              // get the value of the 'plugin type' select.
+              '#limit_validation_errors' => array(
+                array(
+                  'fields',
+                  'display_fields_' . $field_name,
+                  'type'
+                )
+              ),
+            ),
+        ),
+      );
+      $row['#attributes']['class'][] = 'field-plugin-settings-editing';
+    }
+    elseif (!empty($field_display_settings)) {
+      // Check selected plugin settings to display edit link or not.
+      $settings_form = $this->fieldRowSettingsForm($complete_form, $form_state, $field, $field_display_settings);
+      if (!empty($settings_form)) {
+        $row['settings_edit'] += $base_button + array(
+            '#type' => 'image_button',
+            '#name' => 'display_fields_' . $field_name . '_settings_edit',
+            '#src' => 'core/misc/icons/787878/cog.svg',
+            '#attributes' => array(
+              'class' => array('field-plugin-settings-edit'),
+              'alt' => t('Edit')
+            ),
+            '#op' => 'edit',
+            // Do not check errors for the 'Edit' button, but make sure we get
+            // the value of the 'plugin type' select.
+            '#limit_validation_errors' => array(
+              array(
+                'fields',
+                'display_fields_' . $field_name,
+                'type'
+              )
+            ),
+            '#prefix' => '<div class="field-plugin-settings-edit-wrapper">',
+            '#suffix' => '</div>',
+          );
+      }
+    }
+
+    return $row;
+  }
+
+  /**
+   * Return the form for editing the custom extra field.
+   * @param $form
+   * @param FormStateInterface $form_state
+   * @param $field
+   * @return mixed
+   */
+  public function fieldRowSettingsForm($form, FormStateInterface $form_state, $field) {
+    // Add the token browser at the bottom.
+    //Get all defaults values( label, class, tag and text).
+    $field_name = $field['label'];
+    $class = $field['settings']['class'];
+    $tag = $field['settings']['tag'];
+    $data = $field['settings']['data'];
+    $elements['label'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Name'),
+      '#default_value' => $field_name,
+    );
+    $elements['class'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Class'),
+      '#default_value' => $class,
+    );
+    $elements['tag'] = array(
+      '#type' => 'select',
+      '#options' => array(
+        'span' => 'SPAN',
+        'div' => 'DIV',
+        'strong' => 'STRONG',
+        'h2' => 'H2',
+        'h3' => 'H3',
+        'h4' => 'H4',
+        'h5' => 'H5',
+        'h6' => 'H6'
+      ),
+      '#title' => t('Tag'),
+      '#default_value' => $tag,
+    );
+    $elements['data'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Text'),
+      '#default_value' => $data,
+    );
+    // Add the token browser at the top.
+    $elements['token'] = \Drupal::service('displayfields.token')->tokenBrowser();
+    return $elements;
+  }
+
+  /**
+   * Form submission handler for multistep buttons.
+   * @param $form
+   * @param FormStateInterface $form_state
+   */
+  public function multistepSubmit($form, FormStateInterface &$form_state) {
+    //Get the triggered element.
+    $trigger = $form_state->getTriggeringElement();
+    $field_name = $trigger['#field_name'];
+    //Get the entity type and the bundle associated to the custom extra field.
+    $entity_type = $form['#entity_type'];
+    $bundle = $form['#bundle'];
+    //Get all changes in values after submission.
+    $values = $form_state->getValues()['fields'][$field_name]['settings_edit_form']['settings'];
+    // get the operation (edit, update, cancel).
+    $op = $trigger['#op'];
+    switch ($op) {
+      case 'edit':
+        // Store the field whose settings are currently being edited.
+        $field_name = $trigger['#field_name'];
+        $form_state->set('plugin_settings_edit', $field_name);
+        break;
+
+      case 'update':
+        if ($plugin_settings = $form_state->getValue(array(
+            'fields',
+            $field_name,
+            'settings_edit_form',
+            'settings'
+          ))
+        ) {
+          $form_state->set([
+              'plugin_settings',
+              $field_name,
+              'settings'
+            ], $plugin_settings);
+        }
+        //Get the current configuration of the custom extra field.
+        $display_fields_config = DisplayFields::getDisplayFields($entity_type, $bundle);
+        $display_fields = $display_fields_config->get('display_fields');
+        $new_field_name = str_replace("display_fields_", "", $field_name);
+        //Replace the old values of the custom extra field by the news one after submit.
+        $display_fields[$new_field_name]['label'] = $values['label'];
+        $display_fields[$new_field_name]['settings']['class'] = $values['class'];
+        $display_fields[$new_field_name]['settings']['tag'] = $values['tag'];
+        $display_fields[$new_field_name]['settings']['data'] = $values['data'];
+        $display_fields_config->set('display_fields', $display_fields);
+        $display_fields_config->save();
+        $form_state->set('plugin_settings_edit', NULL);
+        break;
+
+      case 'cancel':
+        // Set the field back to 'non edit' mode.
+        $form_state->set('plugin_settings_edit', NULL);
+        break;
+
+      case 'refresh_table':
+        // If the currently edited field is one of the rows to be refreshed, set
+        // it back to 'non edit' mode.
+        $updated_rows = explode(' ', $form_state->getValue('refresh_rows'));
+        $plugin_settings_edit = $form_state->get('plugin_settings_edit');
+        if ($plugin_settings_edit && in_array($plugin_settings_edit, $updated_rows)) {
+
+          $form_state->set('plugin_settings_edit', NULL);
+        }
+        break;
+    }
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Ajax handler for multistep buttons.
+   * @param $form
+   * @param FormStateInterface $form_state
+   * @return mixed
+   */
+  public function multistepAjax($form, FormStateInterface &$form_state) {
+    $trigger = $form_state->getTriggeringElement();
+    $op = $trigger['#op'];
+    // Pick the elements that need to receive the ajax-new-content effect.
+    switch ($op) {
+      case 'edit':
+        $updated_rows = array($trigger['#field_name']);
+        $updated_columns = array('plugin');
+        break;
+      case 'update':
+      case 'cancel':
+        $updated_rows = array($trigger['#field_name']);
+        $updated_columns = array('plugin', 'settings_summary', 'settings_edit');
+        break;
+
+      case 'refresh_table':
+        $updated_rows = array_values(explode(' ', $form_state->getValue('refresh_rows')));
+        $updated_columns = array('settings_summary', 'settings_edit');
+        break;
+    }
+
+    foreach ($updated_rows as $name) {
+      foreach ($updated_columns as $key) {
+        $element = &$form['fields'][$name][$key];
+        $element['#prefix'] = '<div class="ajax-new-content">' . (isset($element['#prefix']) ? $element['#prefix'] : '');
+        $element['#suffix'] = (isset($element['#suffix']) ? $element['#suffix'] : '') . '</div>';
+      }
+    }
+    // Return the whole table.
+    return $form['fields'];
+  }
+
+  /**
+   * Build the display of the custom extra field.
+   * @param array $entities
+   * @param array $display_field
+   * @param array $display_settings
+   * @param string $parent_entity
+   * @param string $view_mode
+   * @param string $language
+   * @return array
+   */
+  public function getFieldBuild($entities, $display_field, $display_settings, $parent_entity, $view_mode, $language) {
+    $build = array();
+    //Get the text to display
+    $data = $display_field['settings']['data'];
+    //get the class added to the text.
+    $class = $display_field['settings']['class'];
+    //Get the tag chosen in the configuration page.
+    $tag = $display_field['settings']['tag'];
+    //Get the current entity type id .For example: node, user
+    $entity_type = $display_field['settings']['entity_type'];
+    foreach ($entities as $entity) {
+      //Get the current user.
+      $user = \Drupal::currentUser();
+      //Returns the token service.
+      $token_service = \Drupal::token();
+      // Replaces all tokens in a given string with appropriate values.
+      $data_replace_tokens = $token_service->replace(t($data), array(
+        'user' => $user,
+        $entity_type => $entity,
+      ));
+      //Format the text perfectly with the right tag and class.
+      $build['data_' . '_' . $class . '_' . $data] = array(
+        '#markup' => "<" . $tag . " class = " . "'" . $class . "'" . ">" . $data_replace_tokens . "</" . $tag . ">",
+      );
+    }
+    return $build;
+  }
+
+
+  /**
+   * Get the current view mode from the current display.
+   * @return string
+   */
+  protected function getCurrentViewMode() {
+    //Get the current url from.
+    $current_url = \Drupal::request()->query->get('q');
+    $get_view_mode_form_url = explode('/', $current_url);
+    $count = count($get_view_mode_form_url);
+    //Get the right view display.
+    if ($get_view_mode_form_url[$count - 1] == "display") {
+      $view_mode = "default";
+      return $view_mode;
+    }
+    else {
+      $view_mode = $get_view_mode_form_url[$count - 1];
+      return $view_mode;
+    }
+  }
+}
\ No newline at end of file
Index: modules/display_fields/display_fields.services.yml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- modules/display_fields/display_fields.services.yml	(revision 7fa82b89e70191be640063c35281f3cb2708a261)
+++ modules/display_fields/display_fields.services.yml	(revision )
@@ -1,4 +1,7 @@
 services:
   plugin.manager.display_fields:
     class: Drupal\display_fields\Plugin\DisplayFieldsPluginManager
-    parent: default_plugin_manager
\ No newline at end of file
+    parent: default_plugin_manager
+  displayfields.token:
+      class: Drupal\display_fields\AllTokens
+      arguments: ['@module_handler', '@token']
\ No newline at end of file
Index: modules/display_fields/display_fields.info.yml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- modules/display_fields/display_fields.info.yml	(revision 7fa82b89e70191be640063c35281f3cb2708a261)
+++ modules/display_fields/display_fields.info.yml	(revision )
@@ -2,4 +2,6 @@
 description: Provide pseudo fields (for display) that can be used to sync other fields and display it with another formatter.
 core: 8.x
 type: module
-package: Fields
\ No newline at end of file
+package: Fields
+dependencies:
+  - token
\ No newline at end of file
Index: src/AllTokens.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- modules/display_fields/src/AllTokens.php	(revision )
+++ modules/display_fields/src/AllTokens.php	(revision )
@@ -0,0 +1,129 @@
+<?php
+
+/**
+ * @file
+ * Contains the \Drupal\display_fields\MetatagToken class.
+ */
+
+namespace Drupal\display_fields;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Utility\Token;
+
+/**
+ * Token handling service. Uses core token service or contributed Token.
+ */
+class AllTokens {
+
+  /**
+   * Module handler service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Token service.
+   *
+   * @var \Drupal\Core\Utility\Token
+   */
+  protected $coreToken;
+
+  /**
+   * Constructs a new MetatagToken object.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   Module handler service.
+   * @param \Drupal\Core\Utility\Token $token
+   *   Token service.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler, Token $token) {
+    $this->coreToken = $token;
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * Gatekeeper function to direct to either the core or contributed Token.
+   *
+   * @param $string
+   * @param $data
+   * @param array $settings
+   * @return mixed|string $string
+   */
+  public function tokenReplace($string, $data, $settings = array()){
+    if ($this->moduleHandler->moduleExists('token')) {
+      return $this->contribReplace($string, $data, $settings);
+    }
+    else {
+      return $this->coreReplace($string, $data, $settings);
+    }
+  }
+
+  /**
+   * Gatekeeper function to direct to either the core or contributed Token.
+   *
+   * @return array
+   *   If token module is installed, a popup browser plus a help text. If not
+   *   only the help text.
+   */
+  public function tokenBrowser() {
+    $form = array();
+
+    $form['intro_text'] = array(
+      '#markup' => '<p>' . t('Configure the meta tags below. Use tokens to avoid redundant meta data and search engine penalization. For example, a \'keyword\' value of "example" will be shown on all content using this configuration, whereas using the [node:field_keywords] automatically inserts the "keywords" values from the current entity (node, term, etc).') . '</p>',
+    );
+
+    if ($this->moduleHandler->moduleExists('token')) {
+      $form['tokens'] = array(
+        '#theme' => 'token_tree_link',
+        '#token_types' => 'all',
+        '#global_types' => TRUE,
+        '#click_insert' => TRUE,
+        '#show_restricted' => FALSE,
+        '#recursion_limit' => 3,
+        '#text' => t('Browse available tokens'),
+      );
+    }
+
+    return $form;
+  }
+
+  /**
+   * Replace tokens with their values using the core token service.
+   *
+   * @param $string
+   * @param $data
+   * @param array $settings
+   * @return mixed|string
+   */
+  private function coreReplace($string, $data, $settings = array()) {
+    // @TODO: Remove this temp code.
+    // This is just here as a way to see all available tokens in debugger.
+    $tokens = $this->coreToken->getInfo();
+
+    $options = array('clear' => TRUE);
+
+    // Replace tokens with core Token service.
+    $replaced = $this->coreToken->replace($string, $data, $options);
+
+    // Ensure that there are no double-slash sequences due to empty token values.
+    $replaced = preg_replace('/(?<!:)\/+\//', '/', $replaced);
+
+    return $replaced;
+  }
+
+  /**
+   * Replace tokens with their values using the contributed token module.
+   *
+   * @param $string
+   * @param $data
+   * @param array $settings
+   * @return mixed|string
+   */
+  private function contribReplace($string, $data, $settings = array()) {
+    // @TODO: Add contrib Token integration when it is ready.
+    // For now, just redirect to the core replacement to avoid breaking sites
+    // where Token is installed.
+    return $this->coreReplace($string, $data, $settings);
+  }
+
+}
