Commit 246c870d authored by Jakub Piasecki's avatar Jakub Piasecki Committed by Jakub Piasecki
Browse files

Issue #2863946 by zaporylie: Add custom field formatter

parent fb15836f
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
field.formatter.settings.telephone_formatter:
  type: mapping
  label: 'Telephone link format settings'
  mapping:
    format:
      type: integer
      label: 'Telephone format'
    link:
      type: boolean
      label: 'Number as link?'
    default_country:
      type: string
      label: 'Default formatter country'
+2 −2
Original line number Diff line number Diff line
@@ -16,12 +16,12 @@ interface FormatterInterface {
   *   Input phone number.
   * @param string $format
   *   Format option.
   * @param null|string $country
   * @param null|string $region
   *   Country code.
   *
   * @return string
   *   Formatted string.
   */
  public function format($input, $format, $country = NULL);
  public function format($input, $format, $region = NULL);

}
+202 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\telephone_formatter\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use libphonenumber\PhoneNumberFormat;

/**
 * Plugin implementation of the 'telephone_formatter' formatter.
 *
 * @FieldFormatter(
 *   id = "telephone_formatter",
 *   label = @Translation("Formatted telephone"),
 *   field_types = {
 *     "telephone"
 *   }
 * )
 */
class TelephoneFormatter extends FormatterBase {

  /**
   * @var \Drupal\telephone_formatter\FormatterInterface
   */
  protected $formatter;

  /**
   * {@inheritdoc}
   */
  public static function defaultSettings() {
    return array(
        'format' => PhoneNumberFormat::INTERNATIONAL,
        'link' => TRUE,
        'default_country' => NULL,
      ) + parent::defaultSettings();
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $elements['format'] = array(
      '#type' => 'select',
      '#title' => $this->t('Format'),
      '#description' => $this->t('List of available formats'),
      '#default_value' => $this->getSetting('format'),
      '#options' => self::availableFormats(),
    );
    $elements['link'] = array(
      '#type' => 'checkbox',
      '#title' => $this->t('Link'),
      '#description' => $this->t('Format as link'),
      '#default_value' => $this->getSetting('link'),
    );
    $elements['default_country'] = array(
      '#type' => 'select',
      '#title' => $this->t('Default country'),
      '#description' => $this->t('If field allows internal telephone numbers you can choose which country this number belongs to by default. It is highly advised to enable telephone validation for this field to ensure that telephone number is valid and can be parsed and reformatted.'),
      '#default_value' => $this->getSetting('default_country'),
      '#options' => [NULL => $this->t('- Do not use default country -')] + \Drupal::service('country_manager')->getList(),
    );

    return $elements;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary() {
    $summary = array();

    $formats = self::availableFormats();
    $summary[] = $this->t('Format: @format', ['@format' => $formats[$this->getSetting('format')]]);
    $summary[] = $this->t('Link: @link', ['@link' => $this->getSetting('link') ? $this->t('Yes') : $this->t('No')]);
    if ($default_country = $this->getSetting('default_country')) {
      $summary[] = $this->t('Default country: @default_country',
        ['@default_country' => $default_country]);
    }

    return $summary;
  }

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $element = array();
    $this->formatter = \Drupal::service('telephone_formatter.formatter');
    foreach ($items as $delta => $item) {
      try {
        if ($this->getSetting('link')) {
          $element[$delta] = $this->viewLinkValue($item);
        }
        else {
          $element[$delta] = $this->viewFormattedValue($item);
        }
      }
      catch (\Exception $e) {
        $element[$delta] = $this->viewPlainValue($item);
      }
    }

    return $element;
  }

  /**
   * Generate the output appropriate for one field item.
   *
   * @param string $item
   *   One field value.
   *
   * @return array
   *   The textual output generated as a render array.
   */
  protected function viewPlainValue(FieldItemInterface $item) {
    // The text value has no text format assigned to it, so the user input
    // should equal the output, including newlines.
    return [
      '#type' => 'inline_template',
      '#template' => '{{ value }}',
      '#context' => ['value' => $item->value],
    ];
  }

  /**
   * Generate the output appropriate for one field item.
   *
   * @param string $item
   *   One field value.
   *
   * @return array
   *   The textual output generated as a render array.
   */
  protected function viewFormattedValue(FieldItemInterface $item) {
    // The text value has no text format assigned to it, so the user input
    // should equal the output, including newlines.
    return [
      '#type' => 'inline_template',
      '#template' => '{{ value }}',
      '#context' => ['value' => $this->getFormattedValue($item)],
    ];
  }

  /**
   * Generate the output appropriate for one field item.
   *
   * @param \Drupal\Core\Field\FieldItemInterface $item
   *   One field value.
   *
   * @return array
   *   The textual output generated as a render array.
   */
  protected function viewLinkValue(FieldItemInterface $item) {
      // Render each element as link.
      $element = array(
        '#type' => 'link',
        // Use custom title if available, otherwise use the telephone number
        // itself as title.
        '#title' => $this->getFormattedValue($item),
        // Prepend 'tel:' to the telephone number.
        '#url' => Url::fromUri($this->formatter->format($item->value, PhoneNumberFormat::RFC3966, $this->getSetting('default_country'))),
        '#options' => array('external' => TRUE),
      );

      if (!empty($item->_attributes)) {
        $element['#options'] += array('attributes' => array());
        $element['#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;
  }

  /**
   * @param \Drupal\Core\Field\FieldItemInterface $item
   * @return string
   */
  protected function getFormattedValue(FieldItemInterface $item) {
    return $this->formatter->format(
      $item->value,
      $this->getSetting('format'),
      $this->getSetting('default_country')
    );
  }

  /**
   * List of available formats.
   */
  public static function availableFormats() {
    return [
      PhoneNumberFormat::INTERNATIONAL => t('International'),
      PhoneNumberFormat::E164 => t('E164'),
      PhoneNumberFormat::NATIONAL => t('National'),
      PhoneNumberFormat::RFC3966 => t('RFC3966'),
    ];
  }

}
+87 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Tests\telephone_formatter\Functional;

use Drupal\field\Entity\FieldConfig;
use Drupal\Tests\BrowserTestBase;
use Drupal\field\Entity\FieldStorageConfig;
use libphonenumber\PhoneNumberFormat;

/**
 * Tests the creation of telephone fields.
 *
 * @group telephone
 */
class FieldFormatterTest extends BrowserTestBase {

  /**
   * Modules to enable.
   *
   * @var array
   */
  public static $modules = [
    'field',
    'node',
    'telephone',
    'telephone_formatter'
  ];

  /**
   * A user with permission to create articles.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $webUser;

  /**
   * {@inheritdoc}
   */
  protected function setUp() {
    parent::setUp();

    $this->drupalCreateContentType(['type' => 'page']);
    $this->webUser = $this->drupalCreateUser(['create page content', 'edit own page content']);
    $this->drupalLogin($this->webUser);
  }

  /**
   * Helper function for testTelephoneField().
   */
  public function testTelephoneFieldFallback() {
    $this->generateTelephoneField();
    $node = $this->drupalCreateNode(['field_telephone' => ['98765432']]);
    $this->drupalGet($node->toUrl());
    $this->assertSession()->responseContains('98765432');
  }

  /**
   * Helper method for telephone field generation.
   */
  protected function generateTelephoneField($settings = []) {
    // Add the telephone field to the article content type.
    FieldStorageConfig::create([
      'field_name' => 'field_telephone',
      'entity_type' => 'node',
      'type' => 'telephone',
    ])->save();
    FieldConfig::create([
      'field_name' => 'field_telephone',
      'label' => 'Telephone Number',
      'entity_type' => 'node',
      'bundle' => 'page',
    ])->save();

    entity_get_display('node', 'page', 'default')
      ->setComponent('field_telephone', [
        'type' => 'telephone_formatter',
        'weight' => 1,
        'settings' => $settings + [
          'format' => PhoneNumberFormat::INTERNATIONAL,
          'link' => TRUE,
          'default_country' => NULL,
        ],
      ])
      ->save();
  }

}