diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml
index aa26f9a..7b2acaa 100644
--- a/core/config/schema/core.entity.schema.yml
+++ b/core/config/schema/core.entity.schema.yml
@@ -82,6 +82,60 @@ core.entity_view_display.*.*.*:
         - type: boolean
           label: 'Value'
 
+core.entity_view_display.field.telephone_link:
+  type: entity_field_view_display_base
+  label: 'Telephone link format settings'
+  mapping:
+    settings:
+      type: mapping
+      label: 'Settings'
+      mapping:
+        title:
+          type: label
+          label: 'Title to replace basic numeric telephone number display'
+
+core.entity_view_display.field.link:
+  type: entity_field_view_display_base
+  label: 'Link format settings'
+  mapping:
+    settings:
+      type: mapping
+      label: 'Settings'
+      mapping:
+        trim_length:
+          type: integer
+          label: 'Trim link text length'
+        url_only:
+          type: boolean
+          label: 'URL only'
+        url_plain:
+          type: boolean
+          label: 'Show URL as plain text'
+        rel:
+          type: string
+          label: 'Add rel="nofollow" to links'
+        target:
+          type: string
+          label: 'Open link in new window'
+
+core.entity_view_display.field.link_separate:
+  type: entity_field_view_display_base
+  label: 'Link format settings'
+  mapping:
+    settings:
+      type: mapping
+      label: 'Settings'
+      mapping:
+        trim_length:
+          type: integer
+          label: 'Trim link text length'
+        rel:
+          type: string
+          label: 'Add rel="nofollow" to links'
+        target:
+          type: string
+          label: 'Open link in new window'
+
 # Overview configuration information for form mode displays.
 core.entity_form_display.*.*.*:
   type: config_entity
@@ -130,6 +184,33 @@ core.entity_form_display.*.*.*:
         - type: boolean
           label: 'Component'
 
+core.entity_form_display.field.telephone_default:
+  type: entity_field_form_display_base
+  label: 'Telephone default format settings'
+  mapping:
+    settings:
+      type: mapping
+      label: 'Settings'
+      mapping:
+        placeholder:
+          type: label
+          label: 'Placeholder'
+
+core.entity_form_display.field.link_default:
+  type: entity_field_form_display_base
+  label: 'Link format settings'
+  mapping:
+    settings:
+      type: mapping
+      label: 'Settings'
+      mapping:
+        placeholder_url:
+          type: string
+          label: 'Placeholder for URL'
+        placeholder_title:
+          type: label
+          label: 'Placeholder for link text'
+
 # Default schema for entity display field with undefined type.
 field.formatter.settings.*:
   type: mapping
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index c7dff02..45f68c5 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1715,6 +1715,33 @@ function template_preprocess_field_multiple_value_form(&$variables) {
 }
 
 /**
+ * Prepares variables for separated link field templates.
+ *
+ * This template outputs a separate title and link.
+ *
+ * Default template: link-formatter-link-separate.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - title: (optional) A descriptive or alternate title for the link, which
+ *     may be different than the actual link text.
+ *   - url_title: The anchor text for the link.
+ *   - url: A \Drupal\Core\Url object.
+ */
+function template_preprocess_link_formatter_link_separate(&$variables) {
+  if (!empty($variables['title'])) {
+    $variables['title'] = String::checkPlain($variables['title']);
+  }
+
+  if (!$variables['url']->isExternal()) {
+    $variables['link'] = \Drupal::linkGenerator()->generateFromUrl($variables['url_title'], $variables['url']);
+  }
+  else {
+    $variables['link'] = l($variables['url_title'], $variables['url']->getPath(), $variables['url']->getOptions());
+  }
+}
+
+/**
  * Prepares variables for breadcrumb templates.
  *
  * Default template: breadcrumb.html.twig.
@@ -1898,5 +1925,9 @@ function drupal_common_theme() {
     'field_multiple_value_form' => array(
       'render element' => 'element',
     ),
+    'link_formatter_link_separate' => array(
+      'variables' => array('title' => NULL, 'url_title' => NULL, 'url' => NULL),
+      'template' => 'link-formatter-link-separate',
+    ),
   );
 }
diff --git a/core/lib/Drupal/Core/Field/LinkItemInterface.php b/core/lib/Drupal/Core/Field/LinkItemInterface.php
new file mode 100644
index 0000000..af7fc5e
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/LinkItemInterface.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\LinkItemInterface.
+ */
+
+namespace Drupal\Core\Field;
+
+/**
+ * Defines an interface for the link field item.
+ */
+interface LinkItemInterface extends FieldItemInterface {
+
+  /**
+   * Specifies whether the field supports only internal URLs.
+   */
+  const LINK_INTERNAL = 0x01;
+
+  /**
+   * Specifies whether the field supports only external URLs.
+   */
+  const LINK_EXTERNAL = 0x10;
+
+  /**
+   * Specifies whether the field supports both internal and external URLs.
+   */
+  const LINK_GENERIC = 0x11;
+
+  /**
+   * Determines if a link is external.
+   *
+   * @return bool
+   *   TRUE if the link is external, FALSE otherwise.
+   */
+  public function isExternal();
+
+  /**
+   * Gets the URL object.
+   *
+   * @return \Drupal\Core\Url
+   *   Returns an Url object.
+   */
+  public function getUrl();
+
+}
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/LinkFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/LinkFormatter.php
new file mode 100644
index 0000000..2a5cb64
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/LinkFormatter.php
@@ -0,0 +1,266 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\Plugin\field\formatter\LinkFormatter.
+ */
+
+namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
+
+use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\FormatterBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Path\PathValidatorInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Field\LinkItemInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Plugin implementation of the 'link' formatter.
+ *
+ * @FieldFormatter(
+ *   id = "link",
+ *   label = @Translation("Link"),
+ *   field_types = {
+ *     "link"
+ *   }
+ * )
+ */
+class LinkFormatter extends FormatterBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The path validator service.
+   *
+   * @var \Drupal\Core\Path\PathValidatorInterface
+   */
+  protected $pathValidator;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $plugin_id,
+      $plugin_definition,
+      $configuration['field_definition'],
+      $configuration['settings'],
+      $configuration['label'],
+      $configuration['view_mode'],
+      $configuration['third_party_settings'],
+      $container->get('path.validator')
+    );
+  }
+
+  /**
+   * Constructs a new LinkFormatter.
+   *
+   * @param string $plugin_id
+   *   The plugin_id for the formatter.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+   *   The definition of the field to which the formatter is associated.
+   * @param array $settings
+   *   The formatter settings.
+   * @param string $label
+   *   The formatter label display setting.
+   * @param string $view_mode
+   *   The view mode.
+   * @param array $third_party_settings
+   *   Third party settings.
+   * @param \Drupal\Core\Path\PathValidatorInterface $path_validator
+   *   The path validator service.
+   */
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, PathValidatorInterface $path_validator) {
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
+    $this->pathValidator = $path_validator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return array(
+      'trim_length' => '80',
+      'url_only' => '',
+      'url_plain' => '',
+      'rel' => '',
+      'target' => '',
+    ) + parent::defaultSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    $elements = parent::settingsForm($form, $form_state);
+
+    $elements['trim_length'] = array(
+      '#type' => 'number',
+      '#title' => t('Trim link text length'),
+      '#field_suffix' => t('characters'),
+      '#default_value' => $this->getSetting('trim_length'),
+      '#min' => 1,
+      '#description' => t('Leave blank to allow unlimited link text lengths.'),
+    );
+    $elements['url_only'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('URL only'),
+      '#default_value' => $this->getSetting('url_only'),
+      '#access' => $this->getPluginId() == 'link',
+    );
+    $elements['url_plain'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Show URL as plain text'),
+      '#default_value' => $this->getSetting('url_plain'),
+      '#access' => $this->getPluginId() == 'link',
+      '#states' => array(
+        'visible' => array(
+          ':input[name*="url_only"]' => array('checked' => TRUE),
+        ),
+      ),
+    );
+    $elements['rel'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Add rel="nofollow" to links'),
+      '#return_value' => 'nofollow',
+      '#default_value' => $this->getSetting('rel'),
+    );
+    $elements['target'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Open link in new window'),
+      '#return_value' => '_blank',
+      '#default_value' => $this->getSetting('target'),
+    );
+
+    return $elements;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsSummary() {
+    $summary = array();
+
+    $settings = $this->getSettings();
+
+    if (!empty($settings['trim_length'])) {
+      $summary[] = t('Link text trimmed to @limit characters', array('@limit' => $settings['trim_length']));
+    }
+    else {
+      $summary[] = t('Link text not trimmed');
+    }
+    if ($this->getPluginId() == 'link' && !empty($settings['url_only'])) {
+      if (!empty($settings['url_plain'])) {
+        $summary[] = t('Show URL only as plain-text');
+      }
+      else {
+        $summary[] = t('Show URL only');
+      }
+    }
+    if (!empty($settings['rel'])) {
+      $summary[] = t('Add rel="@rel"', array('@rel' => $settings['rel']));
+    }
+    if (!empty($settings['target'])) {
+      $summary[] = t('Open link in new window');
+    }
+
+    return $summary;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function viewElements(FieldItemListInterface $items) {
+    $element = array();
+    $entity = $items->getEntity();
+    $settings = $this->getSettings();
+
+    foreach ($items as $delta => $item) {
+      // By default use the full URL as the link text.
+      $url = $this->buildUrl($item);
+      $link_title = $url->toString();
+
+      // If the title field value is available, use it for the link text.
+      if (empty($settings['url_only']) && !empty($item->title)) {
+        // Unsanitized token replacement here because $options['html'] is FALSE
+        // by default in _l().
+        $link_title = \Drupal::token()->replace($item->title, array($entity->getEntityTypeId() => $entity), array('sanitize' => FALSE, 'clear' => TRUE));
+      }
+
+      // Trim the link text to the desired length.
+      if (!empty($settings['trim_length'])) {
+        $link_title = Unicode::truncate($link_title, $settings['trim_length'], FALSE, TRUE);
+      }
+
+      if (!empty($settings['url_only']) && !empty($settings['url_plain'])) {
+        $element[$delta] = array(
+          '#markup' => String::checkPlain($link_title),
+        );
+
+        if (!empty($item->_attributes)) {
+          // Piggyback on the metadata attributes, which will be placed in the
+          // field template wrapper, and set the URL value in a content
+          // attribute.
+          // @todo Does RDF need a URL rather than an internal URI here?
+          // @see \Drupal\rdf\Tests\Field\LinkFieldRdfaTest.
+          $content = str_replace('internal:/', '', $item->uri);
+          $item->_attributes += array('content' => $content);
+        }
+      }
+      else {
+        $element[$delta] = array(
+          '#type' => 'link',
+          '#title' => $link_title,
+          '#options' => $url->getOptions(),
+        );
+        $element[$delta]['#url'] = $url;
+
+        if (!empty($item->_attributes)) {
+          $element[$delta]['#options'] += array ('attributes' => array());
+          $element[$delta]['#options']['attributes'] += $item->_attributes;
+          // Unset field item attributes since they have been included in the
+          // formatter output and should not be rendered in the field template.
+          unset($item->_attributes);
+        }
+      }
+    }
+
+    return $element;
+  }
+
+  /**
+   * Builds the \Drupal\Core\Url object for a link field item.
+   *
+   * @param \Drupal\Core\Field\LinkItemInterface $item
+   *   The link field item being rendered.
+   *
+   * @return \Drupal\Core\Url
+   *   An Url object.
+   */
+  protected function buildUrl(LinkItemInterface $item) {
+    $url = $item->getUrl() ?: Url::fromRoute('<none>');
+
+    $settings = $this->getSettings();
+    $options = $item->options;
+
+    // Add optional 'rel' attribute to link options.
+    if (!empty($settings['rel'])) {
+      $options['attributes']['rel'] = $settings['rel'];
+    }
+    // Add optional 'target' attribute to link options.
+    if (!empty($settings['target'])) {
+      $options['attributes']['target'] = $settings['target'];
+    }
+    $url->setOptions($options);
+
+    return $url;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/LinkSeparateFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/LinkSeparateFormatter.php
new file mode 100644
index 0000000..286b363
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/LinkSeparateFormatter.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\Plugin\field\formatter\LinkSeparateFormatter.
+ *
+ * @todo
+ * Merge into 'link' formatter once there is a #type like 'item' that
+ * can render a compound label and content outside of a form context.
+ * http://drupal.org/node/1829202
+ */
+
+namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Field\FieldItemListInterface;
+
+/**
+ * Plugin implementation of the 'link_separate' formatter.
+ *
+ * @FieldFormatter(
+ *   id = "link_separate",
+ *   label = @Translation("Separate link text and URL"),
+ *   field_types = {
+ *     "link"
+ *   }
+ * )
+ */
+class LinkSeparateFormatter extends LinkFormatter {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return array(
+      'trim_length' => 80,
+      'rel' => '',
+      'target' => '',
+    ) + parent::defaultSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function viewElements(FieldItemListInterface $items) {
+    $element = array();
+    $entity = $items->getEntity();
+    $settings = $this->getSettings();
+
+    foreach ($items as $delta => $item) {
+      // By default use the full URL as the link text.
+      $url = $this->buildUrl($item);
+      $link_title = $url->toString();
+
+      // If the link text field value is available, use it for the text.
+      if (empty($settings['url_only']) && !empty($item->title)) {
+        // Unsanitized token replacement here because $options['html'] is FALSE
+        // by default in _l().
+        $link_title = \Drupal::token()->replace($item->title, array($entity->getEntityTypeId() => $entity), array('sanitize' => FALSE, 'clear' => TRUE));
+      }
+
+      // The link_separate formatter has two titles; the link text (as in the
+      // field values) and the URL itself. If there is no link text value,
+      // $link_title defaults to the URL, so it needs to be unset.
+      // The URL version may need to be trimmed as well.
+      if (empty($item->title)) {
+        $link_title = NULL;
+      }
+      $url_title = $url->toString();
+      if (!empty($settings['trim_length'])) {
+        $link_title = Unicode::truncate($link_title, $settings['trim_length'], FALSE, TRUE);
+        $url_title = Unicode::truncate($url_title, $settings['trim_length'], FALSE, TRUE);
+      }
+
+      $element[$delta] = array(
+        '#theme' => 'link_formatter_link_separate',
+        '#title' => $link_title,
+        '#url_title' => $url_title,
+        '#url' => $url,
+      );
+
+      if (!empty($item->_attributes)) {
+        // Set our RDFa attributes on the <a> element that is being built.
+        $url->setOption('attributes', $item->_attributes);
+
+        // Unset field item attributes since they have been included in the
+        // formatter output and should not be rendered in the field template.
+        unset($item->_attributes);
+      }
+    }
+    return $element;
+  }
+}
+
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php
index ec9b79d..eb8c6e4 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php
@@ -24,6 +24,7 @@
  *   label = @Translation("Plain text"),
  *   field_types = {
  *     "string",
+ *     "telephone",
  *   },
  *   quickedit = {
  *     "editor" = "plain_text"
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TelephoneLinkFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TelephoneLinkFormatter.php
new file mode 100644
index 0000000..def1525
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/TelephoneLinkFormatter.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\Plugin\field\formatter\TelephoneLinkFormatter.
+ */
+
+namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Field\FormatterBase;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+
+/**
+ * Plugin implementation of the 'telephone_link' formatter.
+ *
+ * @FieldFormatter(
+ *   id = "telephone_link",
+ *   label = @Translation("Telephone link"),
+ *   field_types = {
+ *     "telephone"
+ *   }
+ * )
+ */
+class TelephoneLinkFormatter extends FormatterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return array(
+      'title' => '',
+    ) + parent::defaultSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    $elements['title'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Title to replace basic numeric telephone number display'),
+      '#default_value' => $this->getSetting('title'),
+    );
+
+    return $elements;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsSummary() {
+    $summary = array();
+    $settings = $this->getSettings();
+
+    if (!empty($settings['title'])) {
+      $summary[] = t('Link using text: @title', array('@title' => $settings['title']));
+    }
+    else {
+      $summary[] = t('Link using provided telephone number.');
+    }
+
+    return $summary;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function viewElements(FieldItemListInterface $items) {
+    $element = array();
+    $title_setting = $this->getSetting('title');
+
+    foreach ($items as $delta => $item) {
+      // Render each element as link.
+      $element[$delta] = array(
+        '#type' => 'link',
+        // Use custom title if available, otherwise use the telephone number
+        // itself as title.
+        '#title' => $title_setting ?: $item->value,
+        // Prepend 'tel:' to the telephone number.
+        '#url' => Url::fromUri('tel:' . rawurlencode(preg_replace('/\s+/', '', $item->value))),
+        '#options' => array('external' => TRUE),
+      );
+
+      if (!empty($item->_attributes)) {
+        $element[$delta]['#options'] += array('attributes' => array());
+        $element[$delta]['#options']['attributes'] += $item->_attributes;
+        // Unset field item attributes since they have been included in the
+        // formatter output and should not be rendered in the field template.
+        unset($item->_attributes);
+      }
+    }
+
+    return $element;
+  }
+}
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LinkItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LinkItem.php
new file mode 100644
index 0000000..6f7465a
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LinkItem.php
@@ -0,0 +1,190 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\Plugin\Field\FieldType\LinkItem.
+ */
+
+namespace Drupal\Core\Field\Plugin\Field\FieldType;
+
+use Drupal\Component\Utility\Random;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemBase;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\TypedData\DataDefinition;
+use Drupal\Core\TypedData\MapDataDefinition;
+use Drupal\Core\Url;
+use Drupal\Core\Field\LinkItemInterface;
+
+/**
+ * Plugin implementation of the 'link' field type.
+ *
+ * @FieldType(
+ *   id = "link",
+ *   label = @Translation("Link"),
+ *   description = @Translation("Stores a URL string, optional varchar link text, and optional blob of attributes to assemble a link."),
+ *   default_widget = "link_default",
+ *   default_formatter = "link",
+ *   constraints = {"LinkType" = {}, "LinkAccess" = {}, "LinkExternalProtocols" = {}, "LinkNotExistingInternal" = {}}
+ * )
+ */
+class LinkItem extends FieldItemBase implements LinkItemInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultFieldSettings() {
+    return array(
+      'title' => DRUPAL_OPTIONAL,
+      'link_type' => LinkItemInterface::LINK_GENERIC
+    ) + parent::defaultFieldSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
+    $properties['uri'] = DataDefinition::create('uri')
+      ->setLabel(t('URI'));
+
+    $properties['title'] = DataDefinition::create('string')
+      ->setLabel(t('Link text'));
+
+    $properties['options'] = MapDataDefinition::create()
+      ->setLabel(t('Options'));
+
+    return $properties;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function schema(FieldStorageDefinitionInterface $field_definition) {
+    return array(
+      'columns' => array(
+        'uri' => array(
+          'description' => 'The URI of the link.',
+          'type' => 'varchar',
+          'length' => 2048,
+        ),
+        'title' => array(
+          'description' => 'The link text.',
+          'type' => 'varchar',
+          'length' => 255,
+        ),
+        'options' => array(
+          'description' => 'Serialized array of options for the link.',
+          'type' => 'blob',
+          'size' => 'big',
+          'serialize' => TRUE,
+        ),
+      ),
+      'indexes' => array(
+        'uri' => array(array('uri', 30)),
+      ),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
+    $element = array();
+
+    $element['link_type'] = array(
+      '#type' => 'radios',
+      '#title' => t('Allowed link type'),
+      '#default_value' => $this->getSetting('link_type'),
+      '#options' => array(
+        static::LINK_INTERNAL => t('Internal links only'),
+        static::LINK_EXTERNAL => t('External links only'),
+        static::LINK_GENERIC => t('Both internal and external links'),
+      ),
+    );
+
+    $element['title'] = array(
+      '#type' => 'radios',
+      '#title' => t('Allow link text'),
+      '#default_value' => $this->getSetting('title'),
+      '#options' => array(
+        DRUPAL_DISABLED => t('Disabled'),
+        DRUPAL_OPTIONAL => t('Optional'),
+        DRUPAL_REQUIRED => t('Required'),
+      ),
+    );
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
+    // Set of possible top-level domains.
+    $tlds = array('com', 'net', 'gov', 'org', 'edu', 'biz', 'info');
+    // Set random length for the domain name.
+    $domain_length = mt_rand(7, 15);
+    $random = new Random();
+
+    switch ($field_definition->getSetting('title')) {
+      case DRUPAL_DISABLED:
+        $values['title'] = '';
+        break;
+      case DRUPAL_REQUIRED:
+        $values['title'] = $random->sentences(4);
+        break;
+      case DRUPAL_OPTIONAL:
+        // In case of optional title, randomize its generation.
+        $values['title'] = mt_rand(0,1) ? $random->sentences(4) : '';
+        break;
+    }
+    $values['uri'] = 'http://www.' . $random->word($domain_length) . '.' . $tlds[mt_rand(0, (sizeof($tlds)-1))];
+    return $values;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEmpty() {
+    $value = $this->get('uri')->getValue();
+    return $value === NULL || $value === '';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isExternal() {
+    return $this->getUrl()->isExternal();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function mainPropertyName() {
+    return 'uri';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getUrl() {
+    return Url::fromUri($this->uri);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setValue($values, $notify = TRUE) {
+    // Unserialize the values.
+    // @todo The storage controller should take care of this, see
+    //   SqlContentEntityStorage::loadFieldItems, see
+    //   https://www.drupal.org/node/2414835
+    if (isset($values['options']) && is_string($values['options'])) {
+      $values['options'] = unserialize($values['options']);
+    }
+    parent::setValue($values, $notify);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/TelephoneItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/TelephoneItem.php
new file mode 100644
index 0000000..22e33f2
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/TelephoneItem.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\Plugin\Field\FieldType\TelephoneItem.
+ */
+
+namespace Drupal\Core\Field\Plugin\Field\FieldType;
+
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemBase;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\TypedData\DataDefinition;
+
+/**
+ * Plugin implementation of the 'telephone' field type.
+ *
+ * @FieldType(
+ *   id = "telephone",
+ *   label = @Translation("Telephone number"),
+ *   description = @Translation("This field stores a telephone number in the database."),
+ *   category = @Translation("Number"),
+ *   default_widget = "telephone_default",
+ *   default_formatter = "basic_string"
+ * )
+ */
+class TelephoneItem extends FieldItemBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function schema(FieldStorageDefinitionInterface $field_definition) {
+    return array(
+      'columns' => array(
+        'value' => array(
+          'type' => 'varchar',
+          'length' => 256,
+        ),
+      ),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
+    $properties['value'] = DataDefinition::create('string')
+      ->setLabel(t('Telephone number'))
+      ->setRequired(TRUE);
+
+    return $properties;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEmpty() {
+    $value = $this->get('value')->getValue();
+    return $value === NULL || $value === '';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConstraints() {
+    $constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager();
+    $constraints = parent::getConstraints();
+
+    $max_length = 256;
+    $constraints[] = $constraint_manager->create('ComplexData', array(
+      'value' => array(
+        'Length' => array(
+          'max' => $max_length,
+          'maxMessage' => t('%name: the telephone number may not be longer than @max characters.', array('%name' => $this->getFieldDefinition()->getLabel(), '@max' => $max_length)),
+        )
+      ),
+    ));
+
+    return $constraints;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
+    $values['value'] = rand(pow(10, 8), pow(10, 9)-1);
+    return $values;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/LinkWidget.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/LinkWidget.php
new file mode 100644
index 0000000..9701ed4
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/LinkWidget.php
@@ -0,0 +1,365 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\Plugin\Field\FieldWidget\LinkWidget.
+ */
+
+namespace Drupal\Core\Field\Plugin\Field\FieldWidget;
+
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Entity\Element\EntityAutocomplete;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\WidgetBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Field\LinkItemInterface;
+use Symfony\Component\Routing\Exception\RouteNotFoundException;
+use Symfony\Component\Validator\ConstraintViolation;
+use Symfony\Component\Validator\ConstraintViolationListInterface;
+
+/**
+ * Plugin implementation of the 'link' widget.
+ *
+ * @FieldWidget(
+ *   id = "link_default",
+ *   label = @Translation("Link"),
+ *   field_types = {
+ *     "link"
+ *   }
+ * )
+ */
+class LinkWidget extends WidgetBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return array(
+      'placeholder_url' => '',
+      'placeholder_title' => '',
+    ) + parent::defaultSettings();
+  }
+
+  /**
+   * Gets the URI without the 'internal:' or 'entity:' scheme.
+   *
+   * The following two forms of URIs are transformed:
+   * - 'entity:' URIs: to entity autocomplete ("label (entity id)") strings;
+   * - 'internal:' URIs: the scheme is stripped.
+   *
+   * This method is the inverse of ::getUserEnteredStringAsUri().
+   *
+   * @param string $uri
+   *   The URI to get the displayable string for.
+   *
+   * @return string
+   *
+   * @see static::getUserEnteredStringAsUri()
+   */
+  protected static function getUriAsDisplayableString($uri) {
+    $scheme = parse_url($uri, PHP_URL_SCHEME);
+
+    // By default, the displayable string is the URI.
+    $displayable_string = $uri;
+
+    // A different displayable string may be chosen in case of the 'internal:'
+    // or 'entity:' built-in schemes.
+    if ($scheme === 'internal') {
+      $uri_reference = explode(':', $uri, 2)[1];
+
+      // @todo '<front>' is valid input for BC reasons, may be removed by
+      //   https://www.drupal.org/node/2421941
+      $path = parse_url($uri, PHP_URL_PATH);
+      if ($path === '/') {
+        $uri_reference = '<front>' . substr($uri_reference, 1);
+      }
+
+      $displayable_string = $uri_reference;
+    }
+    elseif ($scheme === 'entity') {
+      list($entity_type, $entity_id) = explode('/', substr($uri, 7), 2);
+      // Show the 'entity:' URI as the entity autocomplete would.
+      $entity_manager = \Drupal::entityManager();
+      if ($entity_manager->getDefinition($entity_type, FALSE) && $entity = \Drupal::entityManager()->getStorage($entity_type)->load($entity_id)) {
+        $displayable_string = EntityAutocomplete::getEntityLabels(array($entity));
+      }
+    }
+
+    return $displayable_string;
+  }
+
+  /**
+   * Gets the user-entered string as a URI.
+   *
+   * The following two forms of input are mapped to URIs:
+   * - entity autocomplete ("label (entity id)") strings: to 'entity:' URIs;
+   * - strings without a detectable scheme: to 'internal:' URIs.
+   *
+   * This method is the inverse of ::getUriAsDisplayableString().
+   *
+   * @param string $string
+   *   The user-entered string.
+   *
+   * @return string
+   *   The URI, if a non-empty $uri was passed.
+   *
+   * @see static::getUriAsDisplayableString()
+   */
+  protected static function getUserEnteredStringAsUri($string) {
+    // By default, assume the entered string is an URI.
+    $uri = $string;
+
+    // Detect entity autocomplete string, map to 'entity:' URI.
+    $entity_id = EntityAutocomplete::extractEntityIdFromAutocompleteInput($string);
+    if ($entity_id !== NULL) {
+      // @todo Support entity types other than 'node'. Will be fixed in
+      //    https://www.drupal.org/node/2423093.
+      $uri = 'entity:node/' . $entity_id;
+    }
+    // Detect a schemeless string, map to 'internal:' URI.
+    elseif (!empty($string) && parse_url($string, PHP_URL_SCHEME) === NULL) {
+      // @todo '<front>' is valid input for BC reasons, may be removed by
+      //   https://www.drupal.org/node/2421941
+      // - '<front>' -> '/'
+      // - '<front>#foo' -> '/#foo'
+      if (strpos($string, '<front>') === 0) {
+        $string = '/' . substr($string, strlen('<front>'));
+      }
+      $uri = 'internal:' . $string;
+    }
+
+    return $uri;
+  }
+
+  /**
+   * Disallows saving inaccessible or untrusted URLs.
+   */
+  public static function validateUriElement($element, FormStateInterface $form_state, $form) {
+    $uri = static::getUserEnteredStringAsUri($element['#value']);
+    $form_state->setValueForElement($element, $uri);
+
+    // If getUserEnteredStringAsUri() mapped the entered value to a 'internal:'
+    // URI , ensure the raw value begins with '/', '?' or '#'.
+    // @todo '<front>' is valid input for BC reasons, may be removed by
+    //   https://www.drupal.org/node/2421941
+    if (parse_url($uri, PHP_URL_SCHEME) === 'internal' && !in_array($element['#value'][0], ['/', '?', '#'], TRUE) && substr($element['#value'], 0, 7) !== '<front>') {
+      $form_state->setError($element, t('Manually entered paths should start with /, ? or #.'));
+      return;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+    /** @var \Drupal\link\LinkItemInterface $item */
+    $item = $items[$delta];
+
+    $element['uri'] = array(
+      '#type' => 'url',
+      '#title' => $this->t('URL'),
+      '#placeholder' => $this->getSetting('placeholder_url'),
+      // The current field value could have been entered by a different user.
+      // However, if it is inaccessible to the current user, do not display it
+      // to them.
+      '#default_value' => (!$item->isEmpty() && (\Drupal::currentUser()->hasPermission('link to any page') || $item->getUrl()->access())) ? static::getUriAsDisplayableString($item->uri) : NULL,
+      '#element_validate' => array(array(get_called_class(), 'validateUriElement')),
+      '#maxlength' => 2048,
+      '#required' => $element['#required'],
+    );
+
+    // If the field is configured to support internal links, it cannot use the
+    // 'url' form element and we have to do the validation ourselves.
+    if ($this->supportsInternalLinks()) {
+      $element['uri']['#type'] = 'entity_autocomplete';
+      // @todo The user should be able to select an entity type. Will be fixed
+      //    in https://www.drupal.org/node/2423093.
+      $element['uri']['#target_type'] = 'node';
+      // Disable autocompletion when the first character is '/', '#' or '?'.
+      $element['uri']['#attributes']['data-autocomplete-first-character-blacklist'] = '/#?';
+
+      // The link widget is doing its own processing in
+      // static::getUriAsDisplayableString().
+      $element['uri']['#process_default_value'] = FALSE;
+    }
+
+    // If the field is configured to allow only internal links, add a useful
+    // element prefix.
+    if (!$this->supportsExternalLinks()) {
+      $element['uri']['#field_prefix'] = rtrim(\Drupal::url('<front>', array(), array('absolute' => TRUE)), '/');
+    }
+    // If the field is configured to allow both internal and external links,
+    // show a useful description.
+    elseif ($this->supportsExternalLinks() && $this->supportsInternalLinks()) {
+      $element['uri']['#description'] = $this->t('Start typing the title of a piece of content to select it. You can also enter an internal path such as %add-node or an external URL such as %url. Enter %front to link to the front page.', array('%front' => '<front>', '%add-node' => '/node/add', '%url' => 'http://example.com'));
+    }
+
+    $element['title'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Link text'),
+      '#placeholder' => $this->getSetting('placeholder_title'),
+      '#default_value' => isset($items[$delta]->title) ? $items[$delta]->title : NULL,
+      '#maxlength' => 255,
+      '#access' => $this->getFieldSetting('title') != DRUPAL_DISABLED,
+    );
+    // Post-process the title field to make it conditionally required if URL is
+    // non-empty. Omit the validation on the field edit form, since the field
+    // settings cannot be saved otherwise.
+    if (!$this->isDefaultValueWidget($form_state) && $this->getFieldSetting('title') == DRUPAL_REQUIRED) {
+      $element['#element_validate'][] = array($this, 'validateTitle');
+    }
+
+    // Exposing the attributes array in the widget is left for alternate and more
+    // advanced field widgets.
+    $element['attributes'] = array(
+      '#type' => 'value',
+      '#tree' => TRUE,
+      '#value' => !empty($items[$delta]->options['attributes']) ? $items[$delta]->options['attributes'] : array(),
+      '#attributes' => array('class' => array('link-field-widget-attributes')),
+    );
+
+    // If cardinality is 1, ensure a proper label is output for the field.
+    if ($this->fieldDefinition->getFieldStorageDefinition()->getCardinality() == 1) {
+      // If the link title is disabled, use the field definition label as the
+      // title of the 'uri' element.
+      if ($this->getFieldSetting('title') == DRUPAL_DISABLED) {
+        $element['uri']['#title'] = $element['#title'];
+      }
+      // Otherwise wrap everything in a details element.
+      else {
+        $element += array(
+          '#type' => 'fieldset',
+        );
+      }
+    }
+
+    return $element;
+  }
+
+  /**
+   * Indicates enabled support for link to routes.
+   *
+   * @return bool
+   *   Returns TRUE if the LinkItem field is configured to support links to
+   *   routes, otherwise FALSE.
+   */
+  protected function supportsInternalLinks() {
+    $link_type = $this->getFieldSetting('link_type');
+    return (bool) ($link_type & LinkItemInterface::LINK_INTERNAL);
+  }
+
+  /**
+   * Indicates enabled support for link to external URLs.
+   *
+   * @return bool
+   *   Returns TRUE if the LinkItem field is configured to support links to
+   *   external URLs, otherwise FALSE.
+   */
+  protected function supportsExternalLinks() {
+    $link_type = $this->getFieldSetting('link_type');
+    return (bool) ($link_type & LinkItemInterface::LINK_EXTERNAL);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    $elements = parent::settingsForm($form, $form_state);
+
+    $elements['placeholder_url'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Placeholder for URL'),
+      '#default_value' => $this->getSetting('placeholder_url'),
+      '#description' => $this->t('Text that will be shown inside the field until a value is entered. This hint is usually a sample value or a brief description of the expected format.'),
+    );
+    $elements['placeholder_title'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Placeholder for link text'),
+      '#default_value' => $this->getSetting('placeholder_title'),
+      '#description' => $this->t('Text that will be shown inside the field until a value is entered. This hint is usually a sample value or a brief description of the expected format.'),
+      '#states' => array(
+        'invisible' => array(
+          ':input[name="instance[settings][title]"]' => array('value' => DRUPAL_DISABLED),
+        ),
+      ),
+    );
+
+    return $elements;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsSummary() {
+    $summary = array();
+
+    $placeholder_title = $this->getSetting('placeholder_title');
+    $placeholder_url = $this->getSetting('placeholder_url');
+    if (empty($placeholder_title) && empty($placeholder_url)) {
+      $summary[] = $this->t('No placeholders');
+    }
+    else {
+      if (!empty($placeholder_title)) {
+        $summary[] = $this->t('Title placeholder: @placeholder_title', array('@placeholder_title' => $placeholder_title));
+      }
+      if (!empty($placeholder_url)) {
+        $summary[] = $this->t('URL placeholder: @placeholder_url', array('@placeholder_url' => $placeholder_url));
+      }
+    }
+
+    return $summary;
+  }
+
+  /**
+   * Form element validation handler; Validates the title property.
+   *
+   * Conditionally requires the link title if a URL value was filled in.
+   */
+  public function validateTitle(&$element, FormStateInterface $form_state, $form) {
+    if ($element['uri']['#value'] !== '' && $element['title']['#value'] === '') {
+      $element['title']['#required'] = TRUE;
+      $form_state->setError($element['title'], $this->t('!name field is required.', array('!name' => $element['title']['#title'])));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
+    foreach ($values as &$value) {
+      $value['uri'] = static::getUserEnteredStringAsUri($value['uri']);
+      $value += ['options' => []];
+    }
+    return $values;
+  }
+
+
+  /**
+   * {@inheritdoc}
+   *
+   * Override the '%uri' message parameter, to ensure that 'internal:' URIs
+   * show a validation error message that doesn't mention that scheme.
+   */
+  public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
+    /** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */
+    foreach ($violations as $offset => $violation) {
+      $parameters = $violation->getMessageParameters();
+      if (isset($parameters['@uri'])) {
+        $parameters['@uri'] = static::getUriAsDisplayableString($parameters['@uri']);
+        $violations->set($offset, new ConstraintViolation(
+          $this->t($violation->getMessageTemplate(), $parameters),
+          $violation->getMessageTemplate(),
+          $parameters,
+          $violation->getRoot(),
+          $violation->getPropertyPath(),
+          $violation->getInvalidValue(),
+          $violation->getMessagePluralization(),
+          $violation->getCode()
+        ));
+      }
+    }
+    parent::flagErrors($items, $violations, $form, $form_state);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/TelephoneDefaultWidget.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/TelephoneDefaultWidget.php
new file mode 100644
index 0000000..555ece4
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/TelephoneDefaultWidget.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Field\Plugin\Field\FieldWidget\TelephoneDefaultWidget.
+ */
+
+namespace Drupal\Core\Field\Plugin\Field\FieldWidget;
+
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\WidgetBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Plugin implementation of the 'telephone_default' widget.
+ *
+ * @FieldWidget(
+ *   id = "telephone_default",
+ *   label = @Translation("Telephone number"),
+ *   field_types = {
+ *     "telephone"
+ *   }
+ * )
+ */
+class TelephoneDefaultWidget extends WidgetBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return array(
+      'placeholder' => '',
+    ) + parent::defaultSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    $element['placeholder'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Placeholder'),
+      '#default_value' => $this->getSetting('placeholder'),
+      '#description' => t('Text that will be shown inside the field until a value is entered. This hint is usually a sample value or a brief description of the expected format.'),
+    );
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsSummary() {
+    $summary = array();
+
+    $placeholder = $this->getSetting('placeholder');
+    if (!empty($placeholder)) {
+      $summary[] = t('Placeholder: @placeholder', array('@placeholder' => $placeholder));
+    }
+    else {
+      $summary[] = t('No placeholder');
+    }
+
+    return $summary;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+    $element['value'] = $element + array(
+      '#type' => 'tel',
+      '#default_value' => isset($items[$delta]->value) ? $items[$delta]->value : NULL,
+      '#placeholder' => $this->getSetting('placeholder'),
+    );
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/LinkTypeConstraint.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/LinkTypeConstraint.php
new file mode 100644
index 0000000..721fa55
--- /dev/null
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/LinkTypeConstraint.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Validation\Plugin\Validation\Constraint\LinkTypeConstraint.
+ */
+
+namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
+
+use Drupal\Core\Field\LinkItemInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidatorInterface;
+use Symfony\Component\Validator\ExecutionContextInterface;
+
+/**
+ * Validation constraint for links receiving data allowed by its settings.
+ *
+ * @Plugin(
+ *   id = "LinkType",
+ *   label = @Translation("Link data valid for link type.", context = "Validation"),
+ * )
+ */
+class LinkTypeConstraint extends Constraint implements ConstraintValidatorInterface {
+
+  public $message = "The path '@uri' is invalid.";
+
+  /**
+   * @var \Symfony\Component\Validator\ExecutionContextInterface
+   */
+  protected $context;
+
+  /**
+   * {@inheritDoc}
+   */
+  public function initialize(ExecutionContextInterface $context) {
+    $this->context = $context;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validatedBy() {
+    return get_class($this);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($value, Constraint $constraint) {
+    if (isset($value)) {
+      $uri_is_valid = TRUE;
+
+      /** @var $link_item \Drupal\Core\Field\LinkItemInterface */
+      $link_item = $value;
+      $link_type = $link_item->getFieldDefinition()->getSetting('link_type');
+
+      // Try to resolve the given URI to a URL. It may fail if it's schemeless.
+      try {
+        $url = $link_item->getUrl();
+      }
+      catch (\InvalidArgumentException $e) {
+        $uri_is_valid = FALSE;
+      }
+
+      // If the link field doesn't support both internal and external links,
+      // check whether the URL (a resolved URI) is in fact violating either
+      // restriction.
+      if ($uri_is_valid && $link_type !== LinkItemInterface::LINK_GENERIC) {
+        if (!($link_type & LinkItemInterface::LINK_EXTERNAL) && $url->isExternal()) {
+          $uri_is_valid = FALSE;
+        }
+        if (!($link_type & LinkItemInterface::LINK_INTERNAL) && !$url->isExternal()) {
+          $uri_is_valid = FALSE;
+        }
+      }
+
+      if (!$uri_is_valid) {
+        $this->context->addViolation($this->message, array('@uri' => $link_item->uri));
+      }
+    }
+  }
+}
+
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index d8be776..ba6d72d 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -84,8 +84,8 @@ function field_help($route_name, RouteMatchInterface $route_match) {
       $output .= '<h3>' . t('Uses') . '</h3>';
       $output .= '<dl>';
       $output .= '<dt>' . t('Enabling field types, widgets, and formatters') . '</dt>';
-      $output .= '<dd>' . t('The Field module provides the infrastructure for fields; the field types, formatters, and widgets are provided by Drupal core or additional modules. Some of the modules are required; the optional modules can be enabled from the <a href="!modules">Extend administration page</a>. Additional fields, formatters, and widgets may be provided by contributed modules, which you can find in the <a href="!contrib">contributed module section of Drupal.org</a>.', array('!modules' => \Drupal::url('system.modules_list'), '!contrib' => 'https://drupal.org/project/modules')) . '</dd>';
-
+      $output .= '<dd>' . t('The Field module provides the infrastructure for fields and field attachment; the field types and input widgets themselves are provided by additional modules and core. Some of the modules are required; the optional modules can be enabled from the <a href="!modules">Extend administration page</a>. Additional fields and widgets may be provided by contributed modules, which you can find in the <a href="!contrib">contributed module section of Drupal.org</a>.', array('!modules' => \Drupal::url('system.modules_list'), '!contrib' => 'https://drupal.org/project/modules')) . '</dd>';
+      $output .= '</dl>';
       $output .= '<h3>' . t('Field, widget, and formatter information') . '</h3>';
 
       // Make a list of all widget, formatter, and field modules currently
@@ -96,6 +96,27 @@ function field_help($route_name, RouteMatchInterface $route_match) {
       $widgets = \Drupal::service('plugin.manager.field.widget')->getDefinitions();
       $field_types = \Drupal::service('plugin.manager.field.field_type')->getUiDefinitions();
       $formatters = \Drupal::service('plugin.manager.field.formatter')->getDefinitions();
+
+      // Build a list of core field types.
+      $core_field_types = array();
+      foreach ($field_types as $field_type_name => $field_type) {
+        if ($field_type['provider'] == 'core') {
+          if (!empty($field_type['description'])) {
+            $core_field_types[$field_type_name] = (string) $field_type['label'] . ' - ' . (string) $field_type['description'];
+          }
+          else {
+            $core_field_types[$field_type_name] = (string) $field_type['label'];
+          }
+        }
+      }
+      $output .= t('Field types provided by core:');
+      $item_list = array(
+        '#theme' => 'item_list',
+        '#items' => $core_field_types,
+      );
+      $output .= drupal_render($item_list);
+
+
       $providers = array();
       foreach (array_merge($field_types, $widgets, $formatters) as $plugin) {
         $providers[] = $plugin['provider'];
diff --git a/core/modules/field/src/Tests/FieldImportDeleteUninstallTest.php b/core/modules/field/src/Tests/FieldImportDeleteUninstallTest.php
index 3e83c4d..9277736 100644
--- a/core/modules/field/src/Tests/FieldImportDeleteUninstallTest.php
+++ b/core/modules/field/src/Tests/FieldImportDeleteUninstallTest.php
@@ -22,7 +22,7 @@ class FieldImportDeleteUninstallTest extends FieldUnitTestBase {
    *
    * @var array
    */
-  public static $modules = array('telephone');
+  public static $modules = array('filter');
 
   protected function setUp() {
     parent::setUp();
@@ -55,7 +55,7 @@ public function testImportDeleteUninstall() {
     $field_storage = entity_create('field_storage_config', array(
       'field_name' => 'field_test',
       'entity_type' => 'entity_test',
-      'type' => 'telephone',
+      'type' => 'text',
     ));
     $field_storage->save();
     entity_create('field_config', array(
@@ -87,7 +87,7 @@ public function testImportDeleteUninstall() {
 
     // Stage uninstall of the Telephone module.
     $core_extension = $this->config('core.extension')->get();
-    unset($core_extension['module']['telephone']);
+    unset($core_extension['module']['text']);
     $staging->write('core.extension', $core_extension);
 
     // Stage the field deletion
@@ -101,7 +101,7 @@ public function testImportDeleteUninstall() {
     // Telephone module.
     $this->configImporter()->import();
 
-    $this->assertFalse(\Drupal::moduleHandler()->moduleExists('telephone'));
+    $this->assertFalse(\Drupal::moduleHandler()->moduleExists('text'));
     $this->assertFalse(entity_load_by_uuid('field_storage_config', $field_storage->uuid()), 'The test field has been deleted by the configuration synchronization');
     $deleted_storages = \Drupal::state()->get('field.storage.deleted') ?: array();
     $this->assertFalse(isset($deleted_storages[$field_storage->uuid()]), 'Telephone field has been completed removed from the system.');
@@ -117,7 +117,7 @@ public function testImportAlreadyDeletedUninstall() {
     $field_storage = entity_create('field_storage_config', array(
       'field_name' => 'field_test',
       'entity_type' => 'entity_test',
-      'type' => 'telephone',
+      'type' => 'text',
     ));
     $field_storage->save();
     $field_storage_uuid = $field_storage->uuid();
@@ -149,7 +149,7 @@ public function testImportAlreadyDeletedUninstall() {
 
     // Stage uninstall of the Telephone module.
     $core_extension = $this->config('core.extension')->get();
-    unset($core_extension['module']['telephone']);
+    unset($core_extension['module']['text']);
     $staging->write('core.extension', $core_extension);
 
     $deleted_storages = \Drupal::state()->get('field.storage.deleted') ?: array();
@@ -162,7 +162,7 @@ public function testImportAlreadyDeletedUninstall() {
     // Telephone module.
     $this->configImporter()->import();
 
-    $this->assertFalse(\Drupal::moduleHandler()->moduleExists('telephone'));
+    $this->assertFalse(\Drupal::moduleHandler()->moduleExists('text'));
     $deleted_storages = \Drupal::state()->get('field.storage.deleted') ?: array();
     $this->assertFalse(isset($deleted_storages[$field_storage_uuid]), 'Field has been completed removed from the system.');
   }
diff --git a/core/modules/field/src/Tests/FieldImportDeleteUninstallUiTest.php b/core/modules/field/src/Tests/FieldImportDeleteUninstallUiTest.php
index d10b146..4ccd88c 100644
--- a/core/modules/field/src/Tests/FieldImportDeleteUninstallUiTest.php
+++ b/core/modules/field/src/Tests/FieldImportDeleteUninstallUiTest.php
@@ -23,7 +23,7 @@ class FieldImportDeleteUninstallUiTest extends FieldTestBase {
    *
    * @var array
    */
-  public static $modules = array('entity_test', 'telephone', 'config', 'filter', 'text');
+  public static $modules = array('entity_test', 'options', 'config', 'filter', 'text');
 
   protected function setUp() {
     parent::setUp();
@@ -40,7 +40,10 @@ public function testImportDeleteUninstall() {
     $field_storage = entity_create('field_storage_config', array(
       'field_name' => 'field_tel',
       'entity_type' => 'entity_test',
-      'type' => 'telephone',
+      'type' => 'list_integer',
+      'settings' => array(
+        'allowed_values' => array(1 => 'One', 2 => 'Two', 3 => 'Three'),
+      ),
     ));
     $field_storage->save();
     entity_create('field_config', array(
@@ -62,7 +65,7 @@ public function testImportDeleteUninstall() {
 
     // Create an entity which has values for the telephone and text field.
     $entity = entity_create('entity_test');
-    $value = '+0123456789';
+    $value = 1;
     $entity->field_tel = $value;
     $entity->field_text = $this->randomMachineName(20);
     $entity->name->value = $this->randomMachineName();
@@ -85,7 +88,7 @@ public function testImportDeleteUninstall() {
 
     // Stage uninstall of the Telephone module.
     $core_extension = $this->config('core.extension')->get();
-    unset($core_extension['module']['telephone']);
+    unset($core_extension['module']['options']);
     $staging->write('core.extension', $core_extension);
 
     // Stage the field deletion
diff --git a/core/modules/field/src/Tests/Link/LinkFieldTest.php b/core/modules/field/src/Tests/Link/LinkFieldTest.php
new file mode 100644
index 0000000..9d34acc
--- /dev/null
+++ b/core/modules/field/src/Tests/Link/LinkFieldTest.php
@@ -0,0 +1,596 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field\Tests\Link\LinkFieldTest.
+ */
+
+namespace Drupal\field\Tests\Link;
+
+use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Url;
+use Drupal\Core\Field\LinkItemInterface;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests link field widgets and formatters.
+ *
+ * @group field
+ */
+class LinkFieldTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['entity_test', 'node'];
+
+  /**
+   * A field to use in this test class.
+   *
+   * @var \Drupal\field\Entity\FieldStorageConfig
+   */
+  protected $fieldStorage;
+
+  /**
+   * The instance used in this test class.
+   *
+   * @var \Drupal\field\Entity\FieldConfig
+   */
+  protected $field;
+
+  protected function setUp() {
+    parent::setUp();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'view test entity',
+      'administer entity_test content',
+      'link to any page',
+    ]));
+  }
+
+  /**
+   * Tests link field URL validation.
+   */
+  function testURLValidation() {
+    $field_name = Unicode::strtolower($this->randomMachineName());
+    // Create a field with settings to validate.
+    $this->fieldStorage = entity_create('field_storage_config', array(
+      'field_name' => $field_name,
+      'entity_type' => 'entity_test',
+      'type' => 'link',
+    ));
+    $this->fieldStorage->save();
+    $this->field = entity_create('field_config', array(
+      'field_storage' => $this->fieldStorage,
+      'bundle' => 'entity_test',
+      'settings' => array(
+        'title' => DRUPAL_DISABLED,
+        'link_type' => LinkItemInterface::LINK_GENERIC,
+      ),
+    ));
+    $this->field->save();
+    entity_get_form_display('entity_test', 'entity_test', 'default')
+      ->setComponent($field_name, array(
+        'type' => 'link_default',
+        'settings' => array(
+          'placeholder_url' => 'http://example.com',
+        ),
+      ))
+      ->save();
+    entity_get_display('entity_test', 'entity_test', 'full')
+      ->setComponent($field_name, array(
+        'type' => 'link',
+      ))
+      ->save();
+
+    // Display creation form.
+    $this->drupalGet('entity_test/add');
+    $this->assertFieldByName("{$field_name}[0][uri]", '', 'Link URL field is displayed');
+    $this->assertRaw('placeholder="http://example.com"');
+
+    // Create a path alias.
+    \Drupal::service('path.alias_storage')->save('admin', 'a/path/alias');
+
+    // Create a node to test the link widget.
+    $node = $this->drupalCreateNode();
+
+    // Define some valid URLs (keys are the entered values, values are the
+    // strings displayed to the user).
+    $valid_external_entries = array(
+      'http://www.example.com/' => 'http://www.example.com/',
+    );
+    $valid_internal_entries = array(
+      '/entity_test/add' => '/entity_test/add',
+      '/a/path/alias' => '/a/path/alias',
+
+      // Front page, with query string and fragment.
+      '/' => '&lt;front&gt;',
+      '/?example=llama' => '&lt;front&gt;?example=llama',
+      '/#example' => '&lt;front&gt;#example',
+
+      // @todo '<front>' is valid input for BC reasons, may be removed by
+      //   https://www.drupal.org/node/2421941
+      '<front>' => '&lt;front&gt;',
+      '<front>#example' => '&lt;front&gt;#example',
+      '<front>?example=llama' =>'&lt;front&gt;?example=llama',
+
+      // Query string and fragment.
+      '?example=llama' => '?example=llama',
+      '#example' => '#example',
+
+      // Entity reference autocomplete value.
+      $node->label() . ' (1)' => $node->label() . ' (1)',
+      // Entity URI displayed as ER autocomplete value when displayed in a form.
+      'entity:node/1' => $node->label() . ' (1)',
+      // URI for an entity that exists, but is not accessible by the user.
+      'entity:user/1' => '- Restricted access - (1)',
+      // URI for an entity that doesn't exist, but with a valid ID.
+      'entity:user/999999' => 'entity:user/999999',
+      // URI for an entity that doesn't exist, with an invalid ID.
+      'entity:user/invalid-parameter' => 'entity:user/invalid-parameter',
+    );
+
+    // Define some invalid URLs.
+    $validation_error_1 = "The path '@link_path' is invalid.";
+    $validation_error_2 = 'Manually entered paths should start with /, ? or #.';
+    $validation_error_3 = "The path '@link_path' is inaccessible.";
+    $invalid_external_entries = array(
+      // Invalid protocol
+      'invalid://not-a-valid-protocol' => $validation_error_1,
+      // Missing host name
+      'http://' => $validation_error_1,
+    );
+    $invalid_internal_entries = array(
+      'no-leading-slash' => $validation_error_2,
+      'entity:non_existing_entity_type/yar' => $validation_error_1,
+    );
+
+    // Test external and internal URLs for 'link_type' = LinkItemInterface::LINK_GENERIC.
+    $this->assertValidEntries($field_name, $valid_external_entries + $valid_internal_entries);
+    $this->assertInvalidEntries($field_name, $invalid_external_entries + $invalid_internal_entries);
+
+    // Test external URLs for 'link_type' = LinkItemInterface::LINK_EXTERNAL.
+    $this->field->settings['link_type'] = LinkItemInterface::LINK_EXTERNAL;
+    $this->field->save();
+    $this->assertValidEntries($field_name, $valid_external_entries);
+    $this->assertInvalidEntries($field_name, $valid_internal_entries + $invalid_external_entries);
+
+    // Test external URLs for 'link_type' = LinkItemInterface::LINK_INTERNAL.
+    $this->field->settings['link_type'] = LinkItemInterface::LINK_INTERNAL;
+    $this->field->save();
+    $this->assertValidEntries($field_name, $valid_internal_entries);
+    $this->assertInvalidEntries($field_name, $valid_external_entries + $invalid_internal_entries);
+
+    // Ensure that users with 'link to any page', don't apply access checking.
+    $this->drupalLogin($this->drupalCreateUser([
+      'view test entity',
+      'administer entity_test content',
+    ]));
+    $this->assertValidEntries($field_name, ['/entity_test/add' => '/entity_test/add']);
+    $this->assertInValidEntries($field_name, ['/admin' => $validation_error_3]);
+  }
+
+  /**
+   * Asserts that valid URLs can be submitted.
+   *
+   * @param string $field_name
+   *   The field name.
+   * @param array $valid_entries
+   *   An array of valid URL entries.
+   */
+  protected function assertValidEntries($field_name, array $valid_entries) {
+    foreach ($valid_entries as $uri => $string) {
+      $edit = array(
+        "{$field_name}[0][uri]" => $uri,
+      );
+      $this->drupalPostForm('entity_test/add', $edit, t('Save'));
+      preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
+      $id = $match[1];
+      $this->assertText(t('entity_test @id has been created.', array('@id' => $id)));
+      $this->assertRaw($string);
+    }
+  }
+
+  /**
+   * Asserts that invalid URLs cannot be submitted.
+   *
+   * @param string $field_name
+   *   The field name.
+   * @param array $invalid_entries
+   *   An array of invalid URL entries.
+   */
+  protected function assertInvalidEntries($field_name, array $invalid_entries) {
+    foreach ($invalid_entries as $invalid_value => $error_message) {
+      $edit = array(
+        "{$field_name}[0][uri]" => $invalid_value,
+      );
+      $this->drupalPostForm('entity_test/add', $edit, t('Save'));
+      $this->assertText(t($error_message, array('@link_path' => $invalid_value)));
+    }
+  }
+
+  /**
+   * Tests the link title settings of a link field.
+   */
+  function testLinkTitle() {
+    $field_name = Unicode::strtolower($this->randomMachineName());
+    // Create a field with settings to validate.
+    $this->fieldStorage = entity_create('field_storage_config', array(
+      'field_name' => $field_name,
+      'entity_type' => 'entity_test',
+      'type' => 'link',
+    ));
+    $this->fieldStorage->save();
+    $this->field = entity_create('field_config', array(
+      'field_storage' => $this->fieldStorage,
+      'bundle' => 'entity_test',
+      'label' => 'Read more about this entity',
+      'settings' => array(
+        'title' => DRUPAL_OPTIONAL,
+        'link_type' => LinkItemInterface::LINK_GENERIC,
+      ),
+    ));
+    $this->field->save();
+    entity_get_form_display('entity_test', 'entity_test', 'default')
+      ->setComponent($field_name, array(
+        'type' => 'link_default',
+        'settings' => array(
+          'placeholder_url' => 'http://example.com',
+          'placeholder_title' => 'Enter the text for this link',
+        ),
+      ))
+      ->save();
+    entity_get_display('entity_test', 'entity_test', 'full')
+      ->setComponent($field_name, array(
+        'type' => 'link',
+        'label' => 'hidden',
+      ))
+      ->save();
+
+    // Verify that the link text field works according to the field setting.
+    foreach (array(DRUPAL_DISABLED, DRUPAL_REQUIRED, DRUPAL_OPTIONAL) as $title_setting) {
+      // Update the link title field setting.
+      $this->field->settings['title'] = $title_setting;
+      $this->field->save();
+
+      // Display creation form.
+      $this->drupalGet('entity_test/add');
+      // Assert label is shown.
+      $this->assertText('Read more about this entity');
+      $this->assertFieldByName("{$field_name}[0][uri]", '', 'URL field found.');
+      $this->assertRaw('placeholder="http://example.com"');
+
+      if ($title_setting === DRUPAL_DISABLED) {
+        $this->assertNoFieldByName("{$field_name}[0][title]", '', 'Link text field not found.');
+        $this->assertNoRaw('placeholder="Enter the text for this link"');
+      }
+      else {
+        $this->assertRaw('placeholder="Enter the text for this link"');
+
+        $this->assertFieldByName("{$field_name}[0][title]", '', 'Link text field found.');
+        if ($title_setting === DRUPAL_REQUIRED) {
+          // Verify that the link text is required, if the URL is non-empty.
+          $edit = array(
+            "{$field_name}[0][uri]" => 'http://www.example.com',
+          );
+          $this->drupalPostForm(NULL, $edit, t('Save'));
+          $this->assertText(t('!name field is required.', array('!name' => t('Link text'))));
+
+          // Verify that the link text is not required, if the URL is empty.
+          $edit = array(
+            "{$field_name}[0][uri]" => '',
+          );
+          $this->drupalPostForm(NULL, $edit, t('Save'));
+          $this->assertNoText(t('!name field is required.', array('!name' => t('Link text'))));
+
+          // Verify that a URL and link text meets requirements.
+          $this->drupalGet('entity_test/add');
+          $edit = array(
+            "{$field_name}[0][uri]" => 'http://www.example.com',
+            "{$field_name}[0][title]" => 'Example',
+          );
+          $this->drupalPostForm(NULL, $edit, t('Save'));
+          $this->assertNoText(t('!name field is required.', array('!name' => t('Link text'))));
+        }
+      }
+    }
+
+    // Verify that a link without link text is rendered using the URL as text.
+    $value = 'http://www.example.com/';
+    $edit = array(
+      "{$field_name}[0][uri]" => $value,
+      "{$field_name}[0][title]" => '',
+    );
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
+    $id = $match[1];
+    $this->assertText(t('entity_test @id has been created.', array('@id' => $id)));
+
+    $this->renderTestEntity($id);
+    $expected_link = \Drupal::l($value, Url::fromUri($value));
+    $this->assertRaw($expected_link);
+
+    // Verify that a link with text is rendered using the link text.
+    $title = $this->randomMachineName();
+    $edit = array(
+      "{$field_name}[0][title]" => $title,
+    );
+    $this->drupalPostForm("entity_test/manage/$id", $edit, t('Save'));
+    $this->assertText(t('entity_test @id has been updated.', array('@id' => $id)));
+
+    $this->renderTestEntity($id);
+    $expected_link = \Drupal::l($title, Url::fromUri($value));
+    $this->assertRaw($expected_link);
+  }
+
+  /**
+   * Tests the default 'link' formatter.
+   */
+  function testLinkFormatter() {
+    $field_name = Unicode::strtolower($this->randomMachineName());
+    // Create a field with settings to validate.
+    $this->fieldStorage = entity_create('field_storage_config', array(
+      'field_name' => $field_name,
+      'entity_type' => 'entity_test',
+      'type' => 'link',
+      'cardinality' => 2,
+    ));
+    $this->fieldStorage->save();
+    entity_create('field_config', array(
+      'field_storage' => $this->fieldStorage,
+      'label' => 'Read more about this entity',
+      'bundle' => 'entity_test',
+      'settings' => array(
+        'title' => DRUPAL_OPTIONAL,
+        'link_type' => LinkItemInterface::LINK_GENERIC,
+      ),
+    ))->save();
+    entity_get_form_display('entity_test', 'entity_test', 'default')
+      ->setComponent($field_name, array(
+        'type' => 'link_default',
+      ))
+      ->save();
+    $display_options = array(
+      'type' => 'link',
+      'label' => 'hidden',
+    );
+    entity_get_display('entity_test', 'entity_test', 'full')
+      ->setComponent($field_name, $display_options)
+      ->save();
+
+    // Create an entity with two link field values:
+    // - The first field item uses a URL only.
+    // - The second field item uses a URL and link text.
+    // For consistency in assertion code below, the URL is assigned to the title
+    // variable for the first field.
+    $this->drupalGet('entity_test/add');
+    $url1 = 'http://www.example.com/content/articles/archive?author=John&year=2012#com';
+    $url2 = 'http://www.example.org/content/articles/archive?author=John&year=2012#org';
+    $title1 = $url1;
+    // Intentionally contains an ampersand that needs sanitization on output.
+    $title2 = 'A very long & strange example title that could break the nice layout of the site';
+    $edit = array(
+      "{$field_name}[0][uri]" => $url1,
+      // Note that $title1 is not submitted.
+      "{$field_name}[0][title]" => '',
+      "{$field_name}[1][uri]" => $url2,
+      "{$field_name}[1][title]" => $title2,
+    );
+    // Assert label is shown.
+    $this->assertText('Read more about this entity');
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
+    $id = $match[1];
+    $this->assertText(t('entity_test @id has been created.', array('@id' => $id)));
+
+    // Verify that the link is output according to the formatter settings.
+    // Not using generatePermutations(), since that leads to 32 cases, which
+    // would not test actual link field formatter functionality but rather
+    // _l() and options/attributes. Only 'url_plain' has a dependency on
+    // 'url_only', so we have a total of ~10 cases.
+    $options = array(
+      'trim_length' => array(NULL, 6),
+      'rel' => array(NULL, 'nofollow'),
+      'target' => array(NULL, '_blank'),
+      'url_only' => array(
+        array('url_only' => FALSE),
+        array('url_only' => FALSE, 'url_plain' => TRUE),
+        array('url_only' => TRUE),
+        array('url_only' => TRUE, 'url_plain' => TRUE),
+      ),
+    );
+    foreach ($options as $setting => $values) {
+      foreach ($values as $new_value) {
+        // Update the field formatter settings.
+        if (!is_array($new_value)) {
+          $display_options['settings'] = array($setting => $new_value);
+        }
+        else {
+          $display_options['settings'] = $new_value;
+        }
+        entity_get_display('entity_test', 'entity_test', 'full')
+          ->setComponent($field_name, $display_options)
+          ->save();
+
+        $this->renderTestEntity($id);
+        switch ($setting) {
+          case 'trim_length':
+            $url = $url1;
+            $title = isset($new_value) ? Unicode::truncate($title1, $new_value, FALSE, TRUE) : $title1;
+            $this->assertRaw('<a href="' . String::checkPlain($url) . '">' . String::checkPlain($title) . '</a>');
+
+            $url = $url2;
+            $title = isset($new_value) ? Unicode::truncate($title2, $new_value, FALSE, TRUE) : $title2;
+            $this->assertRaw('<a href="' . String::checkPlain($url) . '">' . String::checkPlain($title) . '</a>');
+            break;
+
+          case 'rel':
+            $rel = isset($new_value) ? ' rel="' . $new_value . '"' : '';
+            $this->assertRaw('<a href="' . String::checkPlain($url1) . '"' . $rel . '>' . String::checkPlain($title1) . '</a>');
+            $this->assertRaw('<a href="' . String::checkPlain($url2) . '"' . $rel . '>' . String::checkPlain($title2) . '</a>');
+            break;
+
+          case 'target':
+            $target = isset($new_value) ? ' target="' . $new_value . '"' : '';
+            $this->assertRaw('<a href="' . String::checkPlain($url1) . '"' . $target . '>' . String::checkPlain($title1) . '</a>');
+            $this->assertRaw('<a href="' . String::checkPlain($url2) . '"' . $target . '>' . String::checkPlain($title2) . '</a>');
+            break;
+
+          case 'url_only':
+            // In this case, $new_value is an array.
+            if (!$new_value['url_only']) {
+              $this->assertRaw('<a href="' . String::checkPlain($url1) . '">' . String::checkPlain($title1) . '</a>');
+              $this->assertRaw('<a href="' . String::checkPlain($url2) . '">' . String::checkPlain($title2) . '</a>');
+            }
+            else {
+              if (empty($new_value['url_plain'])) {
+                $this->assertRaw('<a href="' . String::checkPlain($url1) . '">' . String::checkPlain($url1) . '</a>');
+                $this->assertRaw('<a href="' . String::checkPlain($url2) . '">' . String::checkPlain($url2) . '</a>');
+              }
+              else {
+                $this->assertNoRaw('<a href="' . String::checkPlain($url1) . '">' . String::checkPlain($url1) . '</a>');
+                $this->assertNoRaw('<a href="' . String::checkPlain($url2) . '">' . String::checkPlain($url2) . '</a>');
+                $this->assertEscaped($url1);
+                $this->assertEscaped($url2);
+              }
+            }
+            break;
+        }
+      }
+    }
+  }
+
+  /**
+   * Tests the 'link_separate' formatter.
+   *
+   * This test is mostly the same as testLinkFormatter(), but they cannot be
+   * merged, since they involve different configuration and output.
+   */
+  function testLinkSeparateFormatter() {
+    $field_name = Unicode::strtolower($this->randomMachineName());
+    // Create a field with settings to validate.
+    $this->fieldStorage = entity_create('field_storage_config', array(
+      'field_name' => $field_name,
+      'entity_type' => 'entity_test',
+      'type' => 'link',
+      'cardinality' => 2,
+    ));
+    $this->fieldStorage->save();
+    entity_create('field_config', array(
+      'field_storage' => $this->fieldStorage,
+      'bundle' => 'entity_test',
+      'settings' => array(
+        'title' => DRUPAL_OPTIONAL,
+        'link_type' => LinkItemInterface::LINK_GENERIC,
+      ),
+    ))->save();
+    $display_options = array(
+      'type' => 'link_separate',
+      'label' => 'hidden',
+    );
+    entity_get_form_display('entity_test', 'entity_test', 'default')
+      ->setComponent($field_name, array(
+        'type' => 'link_default',
+      ))
+      ->save();
+    entity_get_display('entity_test', 'entity_test', 'full')
+      ->setComponent($field_name, $display_options)
+      ->save();
+
+    // Create an entity with two link field values:
+    // - The first field item uses a URL only.
+    // - The second field item uses a URL and link text.
+    // For consistency in assertion code below, the URL is assigned to the title
+    // variable for the first field.
+    $this->drupalGet('entity_test/add');
+    $url1 = 'http://www.example.com/content/articles/archive?author=John&year=2012#com';
+    $url2 = 'http://www.example.org/content/articles/archive?author=John&year=2012#org';
+    // Intentionally contains an ampersand that needs sanitization on output.
+    $title2 = 'A very long & strange example title that could break the nice layout of the site';
+    $edit = array(
+      "{$field_name}[0][uri]" => $url1,
+      "{$field_name}[1][uri]" => $url2,
+      "{$field_name}[1][title]" => $title2,
+    );
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
+    $id = $match[1];
+    $this->assertText(t('entity_test @id has been created.', array('@id' => $id)));
+
+    // Verify that the link is output according to the formatter settings.
+    $options = array(
+      'trim_length' => array(NULL, 6),
+      'rel' => array(NULL, 'nofollow'),
+      'target' => array(NULL, '_blank'),
+    );
+    foreach ($options as $setting => $values) {
+      foreach ($values as $new_value) {
+        // Update the field formatter settings.
+        $display_options['settings'] = array($setting => $new_value);
+        entity_get_display('entity_test', 'entity_test', 'full')
+          ->setComponent($field_name, $display_options)
+          ->save();
+
+        $this->renderTestEntity($id);
+        switch ($setting) {
+          case 'trim_length':
+            $url = $url1;
+            $url_title = isset($new_value) ? Unicode::truncate($url, $new_value, FALSE, TRUE) : $url;
+            $expected = '<div class="link-item">';
+            $expected .= '<div class="link-url"><a href="' . String::checkPlain($url) . '">' . String::checkPlain($url_title) . '</a></div>';
+            $expected .= '</div>';
+            $this->assertRaw($expected);
+
+            $url = $url2;
+            $url_title = isset($new_value) ? Unicode::truncate($url, $new_value, FALSE, TRUE) : $url;
+            $title = isset($new_value) ? Unicode::truncate($title2, $new_value, FALSE, TRUE) : $title2;
+            $expected = '<div class="link-item">';
+            $expected .= '<div class="link-title">' . String::checkPlain($title) . '</div>';
+            $expected .= '<div class="link-url"><a href="' . String::checkPlain($url) . '">' . String::checkPlain($url_title) . '</a></div>';
+            $expected .= '</div>';
+            $this->assertRaw($expected);
+            break;
+
+          case 'rel':
+            $rel = isset($new_value) ? ' rel="' . $new_value . '"' : '';
+            $this->assertRaw('<div class="link-url"><a href="' . String::checkPlain($url1) . '"' . $rel . '>' . String::checkPlain($url1) . '</a></div>');
+            $this->assertRaw('<div class="link-url"><a href="' . String::checkPlain($url2) . '"' . $rel . '>' . String::checkPlain($url2) . '</a></div>');
+            break;
+
+          case 'target':
+            $target = isset($new_value) ? ' target="' . $new_value . '"' : '';
+            $this->assertRaw('<div class="link-url"><a href="' . String::checkPlain($url1) . '"' . $target . '>' . String::checkPlain($url1) . '</a></div>');
+            $this->assertRaw('<div class="link-url"><a href="' . String::checkPlain($url2) . '"' . $target . '>' . String::checkPlain($url2) . '</a></div>');
+            break;
+        }
+      }
+    }
+  }
+
+  /**
+   * Renders a test_entity and sets the output in the internal browser.
+   *
+   * @param int $id
+   *   The test_entity ID to render.
+   * @param string $view_mode
+   *   (optional) The view mode to use for rendering.
+   * @param bool $reset
+   *   (optional) Whether to reset the entity_test storage cache. Defaults to
+   *   TRUE to simplify testing.
+   */
+  protected function renderTestEntity($id, $view_mode = 'full', $reset = TRUE) {
+    if ($reset) {
+      $this->container->get('entity.manager')->getStorage('entity_test')->resetCache(array($id));
+    }
+    $entity = entity_load('entity_test', $id);
+    $display = entity_get_display($entity->getEntityTypeId(), $entity->bundle(), $view_mode);
+    $content = $display->build($entity);
+    $output = drupal_render($content);
+    $this->setRawContent($output);
+    $this->verbose($output);
+  }
+
+}
diff --git a/core/modules/field/src/Tests/Link/LinkFieldUITest.php b/core/modules/field/src/Tests/Link/LinkFieldUITest.php
new file mode 100644
index 0000000..8c8e53a
--- /dev/null
+++ b/core/modules/field/src/Tests/Link/LinkFieldUITest.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field\Tests\Link\LinkFieldUITest.
+ */
+
+namespace Drupal\field\Tests\Link;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\field_ui\Tests\FieldUiTestTrait;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests link field UI functionality.
+ *
+ * @group field
+ */
+class LinkFieldUITest extends WebTestBase {
+
+  use FieldUiTestTrait;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['node', 'field_ui', 'block'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->drupalLogin($this->drupalCreateUser(['administer content types', 'administer node fields', 'administer node display']));
+    $this->drupalPlaceBlock('system_breadcrumb_block');
+  }
+
+  /**
+   * Tests that link field UI functionality does not generate warnings.
+   */
+  function testFieldUI() {
+    // Add a content type.
+    $type = $this->drupalCreateContentType();
+    $type_path = 'admin/structure/types/manage/' . $type->id();
+
+    // Add a link field to the newly-created type.
+    $label = $this->randomMachineName();
+    $field_name = Unicode::strtolower($label);
+    $this->fieldUIAddNewField($type_path, $field_name, $label, 'link');
+
+    // Load the formatter page to check that the settings summary does not
+    // generate warnings.
+    // @todo Mess with the formatter settings a bit here.
+    $this->drupalGet("$type_path/display");
+    $this->assertText(t('Link text trimmed to @limit characters', array('@limit' => 80)));
+  }
+
+}
diff --git a/core/modules/field/src/Tests/Link/LinkItemTest.php b/core/modules/field/src/Tests/Link/LinkItemTest.php
new file mode 100644
index 0000000..b916f3d
--- /dev/null
+++ b/core/modules/field/src/Tests/Link/LinkItemTest.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field\Tests\Link\LinkItemTest.
+ */
+
+namespace Drupal\field\Tests\Link;
+
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\FieldItemInterface;
+use Drupal\field\Tests\FieldUnitTestBase;
+
+/**
+ * Tests the new entity API for the link field type.
+ *
+ * @group field
+ */
+class LinkItemTest extends FieldUnitTestBase {
+
+  protected function setUp() {
+    parent::setUp();
+    $this->installSchema('system', ['router']);
+
+    // Create a link field for validation.
+    entity_create('field_storage_config', array(
+      'field_name' => 'field_test',
+      'entity_type' => 'entity_test',
+      'type' => 'link',
+    ))->save();
+    entity_create('field_config', array(
+      'entity_type' => 'entity_test',
+      'field_name' => 'field_test',
+      'bundle' => 'entity_test',
+    ))->save();
+  }
+
+  /**
+   * Tests using entity fields of the link field type.
+   */
+  public function testLinkItem() {
+    // Create entity.
+    $entity = entity_create('entity_test');
+    $url = 'http://www.drupal.org?test_param=test_value';
+    $parsed_url = UrlHelper::parse($url);
+    $title = $this->randomMachineName();
+    $class = $this->randomMachineName();
+    $entity->field_test->uri = $parsed_url['path'];
+    $entity->field_test->title = $title;
+    $entity->field_test->first()->get('options')->set('query', $parsed_url['query']);
+    $entity->field_test->first()->get('options')->set('attributes', array('class' => $class));
+    $entity->name->value = $this->randomMachineName();
+    $entity->save();
+
+    // Verify that the field value is changed.
+    $id = $entity->id();
+    $entity = entity_load('entity_test', $id);
+    $this->assertTrue($entity->field_test instanceof FieldItemListInterface, 'Field implements interface.');
+    $this->assertTrue($entity->field_test[0] instanceof FieldItemInterface, 'Field item implements interface.');
+    $this->assertEqual($entity->field_test->uri, $parsed_url['path']);
+    $this->assertEqual($entity->field_test[0]->uri, $parsed_url['path']);
+    $this->assertEqual($entity->field_test->title, $title);
+    $this->assertEqual($entity->field_test[0]->title, $title);
+    $this->assertEqual($entity->field_test->options['attributes']['class'], $class);
+    $this->assertEqual($entity->field_test->options['query'], $parsed_url['query']);
+
+    // Update only the entity name property to check if the link field data will
+    // remain intact.
+    $entity->name->value = $this->randomMachineName();
+    $entity->save();
+    $id = $entity->id();
+    $entity = entity_load('entity_test', $id);
+    $this->assertEqual($entity->field_test->uri, $parsed_url['path']);
+    $this->assertEqual($entity->field_test->options['attributes']['class'], $class);
+    $this->assertEqual($entity->field_test->options['query'], $parsed_url['query']);
+
+    // Verify changing the field value.
+    $new_url = 'http://drupal.org';
+    $new_title = $this->randomMachineName();
+    $new_class = $this->randomMachineName();
+    $entity->field_test->uri = $new_url;
+    $entity->field_test->title = $new_title;
+    $entity->field_test->first()->get('options')->set('query', NULL);
+    $entity->field_test->first()->get('options')->set('attributes', array('class' => $new_class));
+    $this->assertEqual($entity->field_test->uri, $new_url);
+    $this->assertEqual($entity->field_test->title, $new_title);
+    $this->assertEqual($entity->field_test->options['attributes']['class'], $new_class);
+    $this->assertNull($entity->field_test->options['query']);
+
+    // Read changed entity and assert changed values.
+    $entity->save();
+    $entity = entity_load('entity_test', $id);
+    $this->assertEqual($entity->field_test->uri, $new_url);
+    $this->assertEqual($entity->field_test->title, $new_title);
+    $this->assertEqual($entity->field_test->options['attributes']['class'], $new_class);
+
+    // Test the generateSampleValue() method.
+    $entity = entity_create('entity_test');
+    $entity->field_test->generateSampleItems();
+    $this->entityValidateAndSave($entity);
+  }
+
+}
diff --git a/core/modules/field/src/Tests/Telephone/TelephoneFieldTest.php b/core/modules/field/src/Tests/Telephone/TelephoneFieldTest.php
new file mode 100644
index 0000000..2ac6498
--- /dev/null
+++ b/core/modules/field/src/Tests/Telephone/TelephoneFieldTest.php
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field\Tests\Telephone\TelephoneFieldTest.
+ */
+
+namespace Drupal\field\Tests\Telephone;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests the creation of telephone fields.
+ *
+ * @group field
+ */
+class TelephoneFieldTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array(
+    'field',
+    'node',
+  );
+
+  /**
+   * A user with permission to create articles.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $webUser;
+
+  protected function setUp() {
+    parent::setUp();
+
+    $this->drupalCreateContentType(array('type' => 'article'));
+    $this->webUser = $this->drupalCreateUser(array('create article content', 'edit own article content'));
+    $this->drupalLogin($this->webUser);
+  }
+
+  // Test fields.
+
+  /**
+   * Helper function for testTelephoneField().
+   */
+  function testTelephoneField() {
+
+    // Add the telephone field to the article content type.
+    entity_create('field_storage_config', array(
+      'field_name' => 'field_telephone',
+      'entity_type' => 'node',
+      'type' => 'telephone',
+    ))->save();
+    entity_create('field_config', array(
+      'field_name' => 'field_telephone',
+      'label' => 'Telephone Number',
+      'entity_type' => 'node',
+      'bundle' => 'article',
+    ))->save();
+
+    entity_get_form_display('node', 'article', 'default')
+      ->setComponent('field_telephone', array(
+        'type' => 'telephone_default',
+        'settings' => array(
+          'placeholder' => '123-456-7890',
+        ),
+      ))
+      ->save();
+
+    entity_get_display('node', 'article', 'default')
+      ->setComponent('field_telephone', array(
+        'type' => 'telephone_link',
+        'weight' => 1,
+      ))
+      ->save();
+
+    // Display creation form.
+    $this->drupalGet('node/add/article');
+    $this->assertFieldByName("field_telephone[0][value]", '', 'Widget found.');
+    $this->assertRaw('placeholder="123-456-7890"');
+
+    // Test basic entery of telephone field.
+    $edit = array(
+      'title[0][value]' => $this->randomMachineName(),
+      'field_telephone[0][value]' => "123456789",
+    );
+
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    $this->assertRaw('<a href="tel:123456789">', 'A telephone link is provided on the article node page.');
+
+    // Add number with a space in it. Need to ensure it is stripped on output.
+    $edit = array(
+      'title[0][value]' => $this->randomMachineName(),
+      'field_telephone[0][value]' => "1234 56789",
+    );
+
+    $this->drupalPostForm('node/add/article', $edit, t('Save'));
+    $this->assertRaw('<a href="tel:123456789">', 'Telephone link is output with whitespace removed.');
+  }
+}
diff --git a/core/modules/field/src/Tests/Telephone/TelephoneItemTest.php b/core/modules/field/src/Tests/Telephone/TelephoneItemTest.php
new file mode 100644
index 0000000..df0ec77
--- /dev/null
+++ b/core/modules/field/src/Tests/Telephone/TelephoneItemTest.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field\Tests\Telephone\TelephoneItemTest.
+ */
+
+namespace Drupal\field\Tests\Telephone;
+
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\FieldItemInterface;
+use Drupal\field\Tests\FieldUnitTestBase;
+
+/**
+ * Tests the new entity API for the telephone field type.
+ *
+ * @group field
+ */
+class TelephoneItemTest extends FieldUnitTestBase {
+
+  protected function setUp() {
+    parent::setUp();
+
+    // Create a telephone field storage and field for validation.
+    entity_create('field_storage_config', array(
+      'field_name' => 'field_test',
+      'entity_type' => 'entity_test',
+      'type' => 'telephone',
+    ))->save();
+    entity_create('field_config', array(
+      'entity_type' => 'entity_test',
+      'field_name' => 'field_test',
+      'bundle' => 'entity_test',
+    ))->save();
+  }
+
+  /**
+   * Tests using entity fields of the telephone field type.
+   */
+  public function testTestItem() {
+    // Verify entity creation.
+    $entity = entity_create('entity_test');
+    $value = '+0123456789';
+    $entity->field_test = $value;
+    $entity->name->value = $this->randomMachineName();
+    $entity->save();
+
+    // Verify entity has been created properly.
+    $id = $entity->id();
+    $entity = entity_load('entity_test', $id);
+    $this->assertTrue($entity->field_test instanceof FieldItemListInterface, 'Field implements interface.');
+    $this->assertTrue($entity->field_test[0] instanceof FieldItemInterface, 'Field item implements interface.');
+    $this->assertEqual($entity->field_test->value, $value);
+    $this->assertEqual($entity->field_test[0]->value, $value);
+
+    // Verify changing the field value.
+    $new_value = '+41' . rand(1000000, 9999999);
+    $entity->field_test->value = $new_value;
+    $this->assertEqual($entity->field_test->value, $new_value);
+
+    // Read changed entity and assert changed values.
+    $entity->save();
+    $entity = entity_load('entity_test', $id);
+    $this->assertEqual($entity->field_test->value, $new_value);
+
+    // Test sample item generation.
+    $entity = entity_create('entity_test');
+    $entity->field_test->generateSampleItems();
+    $this->entityValidateAndSave($entity);
+  }
+
+}
diff --git a/core/modules/field/src/Tests/reEnableModuleFieldTest.php b/core/modules/field/src/Tests/reEnableModuleFieldTest.php
index 52ce2a3..1b779d5 100644
--- a/core/modules/field/src/Tests/reEnableModuleFieldTest.php
+++ b/core/modules/field/src/Tests/reEnableModuleFieldTest.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Contains \Drupal\field\reEnableModuleFieldTest.
+ * Contains \Drupal\field\Tests\reEnableModuleFieldTest.
  */
 
 namespace Drupal\field\Tests;
@@ -24,9 +24,8 @@ class reEnableModuleFieldTest extends WebTestBase {
   public static $modules = array(
     'field',
     'node',
-    // We use telephone module instead of test_field because test_field is
-    // hidden and does not display on the admin/modules page.
-    'telephone'
+    // Using field_test module while it displays on the admin/modules page.
+    'field_test',
   );
 
   protected function setUp() {
@@ -44,11 +43,11 @@ protected function setUp() {
    */
   function testReEnabledField() {
 
-    // Add a telephone field to the article content type.
+    // Add a test field to the article content type.
     $field_storage = entity_create('field_storage_config', array(
-      'field_name' => 'field_telephone',
+      'field_name' => 'field_test',
       'entity_type' => 'node',
-      'type' => 'telephone',
+      'type' => 'test_field',
     ));
     $field_storage->save();
     entity_create('field_config', array(
@@ -58,33 +57,29 @@ function testReEnabledField() {
     ))->save();
 
     entity_get_form_display('node', 'article', 'default')
-      ->setComponent('field_telephone', array(
-        'type' => 'telephone_default',
-        'settings' => array(
-          'placeholder' => '123-456-7890',
-        ),
+      ->setComponent('field_test', array(
+        'type' => 'test_field_widget',
       ))
       ->save();
 
     entity_get_display('node', 'article', 'default')
-      ->setComponent('field_telephone', array(
-        'type' => 'telephone_link',
+      ->setComponent('field_test', array(
+        'type' => 'field_test_default',
         'weight' => 1,
       ))
       ->save();
 
-    // Display the article node form and verify the telephone widget is present.
+    // Display the article node form and verify the field widget is present.
     $this->drupalGet('node/add/article');
-    $this->assertFieldByName("field_telephone[0][value]", '', 'Widget found.');
+    $this->assertFieldByName('field_test[0][value]', '', 'Widget found.');
 
-    // Submit an article node with a telephone field so data exist for the
-    // field.
+    // Submit an article node with the field so data exist for the field.
     $edit = array(
       'title[0][value]' => $this->randomMachineName(),
-      'field_telephone[0][value]' => "123456789",
+      'field_test[0][value]' => '1234567',
     );
     $this->drupalPostForm(NULL, $edit, t('Save'));
-    $this->assertRaw('<a href="tel:123456789">');
+    $this->assertRaw('1234567');
 
     // Test that the module can't be uninstalled from the UI while there is data
     // for it's fields.
@@ -98,7 +93,6 @@ function testReEnabledField() {
     $this->cronRun();
     $this->assertNoText('Fields type(s) in use');
     $this->assertNoText('Fields pending deletion');
-
   }
 
 }
diff --git a/core/modules/link/link.info.yml b/core/modules/link/link.info.yml
deleted file mode 100644
index 509d2ba..0000000
--- a/core/modules/link/link.info.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-name: Link
-type: module
-description: 'Provides a simple link field type.'
-core: 8.x
-package: Field types
-version: VERSION
-dependencies:
-  - field
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6Test.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6Test.php
index 7bdc93b..c8c2785 100644
--- a/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6Test.php
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6Test.php
@@ -34,7 +34,6 @@ class MigrateDrupal6Test extends MigrateFullDrupalTestBase {
     'file',
     'forum',
     'image',
-    'link',
     'locale',
     'menu_ui',
     'node',
@@ -44,7 +43,6 @@ class MigrateDrupal6Test extends MigrateFullDrupalTestBase {
     'statistics',
     'syslog',
     'taxonomy',
-    'telephone',
     'text',
     'update',
     'views',
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldFormatterSettingsTest.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldFormatterSettingsTest.php
index 60b1cc9..5e10076 100644
--- a/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldFormatterSettingsTest.php
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldFormatterSettingsTest.php
@@ -23,7 +23,7 @@ class MigrateFieldFormatterSettingsTest extends MigrateDrupal6TestBase {
    *
    * @var array
    */
-  public static $modules = array('node', 'field', 'datetime', 'image', 'text', 'link', 'file', 'telephone');
+  public static $modules = array('node', 'field', 'datetime', 'image', 'text', 'file');
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldInstanceTest.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldInstanceTest.php
index 05cc467..43cdd68 100644
--- a/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldInstanceTest.php
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldInstanceTest.php
@@ -7,10 +7,10 @@
 
 namespace Drupal\migrate_drupal\Tests\d6;
 
+use Drupal\Core\Field\LinkItemInterface;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\migrate\MigrateExecutable;
 use Drupal\migrate_drupal\Tests\d6\MigrateDrupal6TestBase;
-use Drupal\link\LinkItemInterface;
 
 /**
  * Migrate field instances.
@@ -25,8 +25,6 @@ class MigrateFieldInstanceTest extends MigrateDrupal6TestBase {
    * @var array
    */
   public static $modules = array(
-    'telephone',
-    'link',
     'file',
     'image',
     'datetime',
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldTest.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldTest.php
index 7ff2a68..9f726f9 100644
--- a/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldTest.php
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldTest.php
@@ -23,7 +23,7 @@ class MigrateFieldTest extends MigrateDrupal6TestBase {
    *
    * @var array
    */
-  public static $modules = array('field', 'telephone', 'link', 'file', 'image', 'datetime', 'node', 'options');
+  public static $modules = array('field', 'file', 'image', 'datetime', 'node', 'options');
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldWidgetSettingsTest.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldWidgetSettingsTest.php
index bf2e51a..c0c6cdc 100644
--- a/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldWidgetSettingsTest.php
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateFieldWidgetSettingsTest.php
@@ -24,8 +24,6 @@ class MigrateFieldWidgetSettingsTest extends MigrateDrupal6TestBase {
    */
   public static $modules = array(
     'field',
-    'telephone',
-    'link',
     'file',
     'image',
     'datetime',
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileEntityDisplayTest.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileEntityDisplayTest.php
index 324b522..7b58ff8 100644
--- a/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileEntityDisplayTest.php
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileEntityDisplayTest.php
@@ -23,7 +23,7 @@ class MigrateUserProfileEntityDisplayTest extends MigrateDrupal6TestBase {
    *
    * @var array
    */
-  static $modules = array('link', 'options', 'datetime');
+  static $modules = array('options', 'datetime');
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileEntityFormDisplayTest.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileEntityFormDisplayTest.php
index 6c4c898..9884905 100644
--- a/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileEntityFormDisplayTest.php
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileEntityFormDisplayTest.php
@@ -18,7 +18,7 @@
  */
 class MigrateUserProfileEntityFormDisplayTest extends MigrateDrupal6TestBase {
 
-  static $modules = array('link', 'options', 'datetime');
+  static $modules = array('options', 'datetime');
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileFieldInstanceTest.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileFieldInstanceTest.php
index dd469e8..3ae77c7 100644
--- a/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileFieldInstanceTest.php
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileFieldInstanceTest.php
@@ -18,7 +18,7 @@
  */
 class MigrateUserProfileFieldInstanceTest extends MigrateDrupal6TestBase {
 
-  static $modules = array('field', 'link', 'options', 'datetime', 'text');
+  static $modules = array('field', 'options', 'datetime', 'text');
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileFieldTest.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileFieldTest.php
index 91286df..f04c073 100644
--- a/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileFieldTest.php
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateUserProfileFieldTest.php
@@ -18,7 +18,7 @@
  */
 class MigrateUserProfileFieldTest extends MigrateDrupal6TestBase {
 
-  static $modules = array('link', 'options', 'datetime');
+  static $modules = array('options', 'datetime');
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateUserTest.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateUserTest.php
index 8a15d4e..43a024b 100644
--- a/core/modules/migrate_drupal/src/Tests/d6/MigrateUserTest.php
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateUserTest.php
@@ -26,7 +26,6 @@ class MigrateUserTest extends MigrateDrupal6TestBase {
    * @var array
    */
   static $modules = array(
-    'link',
     'options',
     'datetime',
     'text',
diff --git a/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php b/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php
index 4d643d0..fd3fafa 100644
--- a/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php
+++ b/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php
@@ -24,7 +24,7 @@ class LinkFieldRdfaTest extends FieldRdfaTestBase {
   /**
    * {@inheritdoc}
    */
-  public static $modules = array('link', 'text');
+  public static $modules = array('text');
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/rdf/src/Tests/Field/TelephoneFieldRdfaTest.php b/core/modules/rdf/src/Tests/Field/TelephoneFieldRdfaTest.php
index 002db43..7bcb620 100644
--- a/core/modules/rdf/src/Tests/Field/TelephoneFieldRdfaTest.php
+++ b/core/modules/rdf/src/Tests/Field/TelephoneFieldRdfaTest.php
@@ -28,7 +28,7 @@ class TelephoneFieldRdfaTest extends FieldRdfaTestBase {
   /**
    * {@inheritdoc}
    */
-  public static $modules = array('telephone', 'text');
+  public static $modules = array('text');
 
   protected function setUp() {
     parent::setUp();
diff --git a/core/modules/system/templates/link-formatter-link-separate.html.twig b/core/modules/system/templates/link-formatter-link-separate.html.twig
new file mode 100644
index 0000000..4b58326
--- /dev/null
+++ b/core/modules/system/templates/link-formatter-link-separate.html.twig
@@ -0,0 +1,24 @@
+{#
+/**
+ * @file
+ * Default theme implementation of a link with separate title and URL elements.
+ *
+ * Available variables:
+ * - link: The link that has already been formatted by l().
+ * - title: (optional) A descriptive or alternate title for the link, which may
+ *   be different than the actual link text.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_link_formatter_link_separate()
+ *
+ * @ingroup themeable
+ */
+#}
+{% spaceless %}
+  <div>
+    {% if title %}
+      <div>{{ title }}</div>
+    {% endif %}
+    <div>{{ link }}</div>
+  </div>
+{% endspaceless %}
diff --git a/core/modules/telephone/telephone.info.yml b/core/modules/telephone/telephone.info.yml
deleted file mode 100644
index 3e27ccb..0000000
--- a/core/modules/telephone/telephone.info.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-name: Telephone
-type: module
-description: 'Defines a field type for telephone numbers.'
-package: Field types
-version: VERSION
-core: 8.x
-dependencies:
-  - field
diff --git a/core/modules/telephone/telephone.module b/core/modules/telephone/telephone.module
deleted file mode 100644
index 7637a98..0000000
--- a/core/modules/telephone/telephone.module
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-/**
- * @file
- * Defines a simple telephone number field type.
- */
-
-use Drupal\Core\Routing\RouteMatchInterface;
-
-/**
- * Implements hook_help().
- */
-function telephone_help($route_name, RouteMatchInterface $route_match) {
-  switch ($route_name) {
-    case 'help.page.telephone':
-      $output = '';
-      $output .= '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('The Telephone module allows you to create fields that contain telephone numbers. See the <a href="!field">Field module help</a> and the <a href="!field_ui">Field UI help</a> pages for general information on fields and how to create and manage them. For more information, see the <a href="!telephone_documentation">online documentation for the Telephone module</a>.', array('!field' => \Drupal::url('help.page', array('name' => 'field')), '!field_ui' => \Drupal::url('help.page', array('name' => 'field_ui')), '!telephone_documentation' => 'https://drupal.org/documentation/modules/telephone')) . '</p>';
-      $output .= '<h3>' . t('Uses') . '</h3>';
-      $output .= '<dl>';
-      $output .= '<dt>' . t('Managing and displaying telephone fields') . '</dt>';
-      $output .= '<dd>' . t('The <em>settings</em> and the <em>display</em> of the telephone field can be configured separately. See the <a href="!field_ui">Field UI help</a> for more information on how to manage fields and their display.', array('!field_ui' => \Drupal::url('help.page', array('name' => 'field_ui')))) . '</dd>';
-      $output .= '<dt>' . t('Displaying telephone numbers as links') . '</dt>';
-      $output .= '<dd>' . t('Telephone numbers can be displayed as links with the scheme name <em>tel:</em> by choosing the <em>Telephone</em> display format on the <em>Manage display</em> page. Any spaces will be stripped out of the link text. This semantic markup improves the user experience on mobile and assistive technology devices.') . '</dd>';
-      $output .= '</dl>';
-      return $output;
-  }
-}
-
-/**
- * Implements hook_field_formatter_info_alter().
- */
-function telephone_field_formatter_info_alter(&$info) {
-  $info['string']['field_types'][] = 'telephone';
-}
