Commit 4a7aed82 authored by seworthi's avatar seworthi

Initial commit

parents
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
This is a module that extends the code telephone module by including
the ability to select a type of telephone number (ie Cell, Work, Fax),
and also validated the number. It uses libphonenumber-php library
(port of google's libphonenumber library).
REQUIREMENTS
------------
- telephone
- composer_manager
- field_ui
INSTALLATION
------------
Just enable module. You will might need to use drush composer extension to
install libphonenumber library. If so, follow composer_manager documentation
page https://www.drupal.org/node/2405805
Add a new field and select 'Telephone number with type'.
CONFIGURATION
-------------
Configurations of the field can be found on the "Manage form display" and
"Manage display" settings tabs for the content type you added the field to.
MAINTANINERS
------------
Current Maintainer: Scott E Worthington
{
"name": "drupal/telephone_type",
"type": "drupal-module",
"description": "Use 3rd party library to validate telephone field.",
"license": "GPL-2.0+",
},
"require": {
"drupal/core": "^8.0",
"drupal/telephone": "^8.0",
"drupal/field_ui": "^8.0",
"giggsey/libphonenumber-for-php": "~8.0"
}
}
# Schema for the configuration files of the Telephone type module.
field.formatter.settings.telephone_type_link:
type: mapping
label: 'Telephone type link format settings'
field.widget.settings.telephone_type_default:
type: mapping
label: 'Telephone type default format settings'
<?php
namespace Drupal\telephone_type\Plugin\Field\FieldFormatter;
use Drupal\Component\Utility\Html;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\telephone\Plugin\Field\FieldFormatter\TelephoneLinkFormatter;
use Drupal\Core\Url;
/**
* Plugin implementation of the 'telephone_type_link' formatter.
*
* @FieldFormatter(
* id = "telephone_type_link",
* label = @Translation("Telephone link w/type"),
* field_types = {
* "telephone_type"
* }
* )
*/
class TelephoneTypeLinkFormatter extends TelephoneLinkFormatter {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'label' => 'short',
'location' => 'prefix',
'separator' => ': ',
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$elements = parent::settingsForm($form, $form_state);
$elements['label'] = [
'#type' => 'select',
'#title' => t('Label'),
'#default_value' => $this->getSetting('label'),
'#options' => ['key' => 'Short', 'value' => 'Description'],
'#description' => t('Select to use either the key or value for the label.'),
];
$elements['location'] = [
'#type' => 'select',
'#title' => t('Label location'),
'#default_value' => $this->getSetting('location'),
'#options' => ['prefix' => 'Before', 'suffix' => 'After'],
'#description' => t('Select the location of the label.'),
];
$elements['separator'] = [
'#type' => 'textfield',
'#title' => t('Separator'),
'#default_value' => $this->getSetting('separator'),
'#size' => 15,
'#maxlength' => 10,
];
return $elements;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
$summary[] = 'Label: ' . ($this->getSetting('label') == 'key' ? 'Short' : 'Description');
$summary[] = 'Label location: ' . ($this->getSetting('location') == 'prefix' ? 'Before' : 'After');
$summary[] = 'Separator: ' . HTML::escape($this->getSetting('separator'));
return $summary;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$element = [];
$title_setting = $this->getSetting('title');
$validator = \Drupal::service('telephone_type.validator');
foreach ($items as $delta => $item) {
// If type is fax, use markup to display.
if (isset($item->type) && $item->type == 'fax') {
$element[$delta]['#markup'] = $validator->format($item->value);
}
else {
$element[$delta] = [
'#type' => 'link',
// Use custom title if available, otherwise use the telephone number.
'#title' => $title_setting ?: $validator->format($item->value),
'#url' => Url::fromUri($validator->formatUri($item->value)),
'#options' => ['external' => TRUE],
];
}
if (!empty($item->_attributes)) {
$element[$delta]['#options'] += ['attributes' => []];
$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);
}
// If label selected.
if (!empty($item->type)) {
$label = ($this->getSetting('label')== 'key') ? $item->type : telephone_types_options($item->type);
if ($this->getSetting('location') == 'prefix') {
$element[$delta]['#prefix'] = $label . HTML::escape($this->getSetting('separator'));
}
else {
$element[$delta]['#suffix'] = HTML::escape($this->getSetting('separator')). $label;
}
}
}
return $element;
}
}
<?php
namespace Drupal\telephone_type\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\telephone\Plugin\Field\FieldType\TelephoneItem;
/**
* Plugin implementation of the 'telephone_type' field type.
*
* @FieldType(
* id = "telephone_type",
* label = @Translation("Telephone number with type"),
* description = @Translation("This field stores a telephone number and type in the database."),
* category = @Translation("Number"),
* default_widget = "telephone_type_default",
* default_formatter = "telephone_type_link",
* constraints = {"TelephoneTypeValidation" = {}}
* )
*/
class TelephoneTypeItem extends TelephoneItem {
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
$schema = parent::schema($field_definition);
$schema['columns']['type'] = array(
'type' => 'varchar',
'length' => 255,
);
return $schema;
}
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties = parent::propertyDefinitions($field_definition);
$type_definition = DataDefinition::create('string')
->setLabel(t('Type'))
->setRequired(TRUE);
$properties['type'] = $type_definition;
return $properties;
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
$type = $this->get('type')->getValue();
if ($type === NULL || $type === '') {
if ($this->getSetting('type_required')) {
return TRUE;
}
}
return parent::isEmpty();
}
/**
* {@inheritdoc}
*
* Store the entered value in NATIONAL format.
*/
public function preSave() {
$validator = \Drupal::service('telephone_type.validator');
$this->value = $validator->getNationalNumber($this->value);
}
/**
* {@inheritdoc}
*/
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
$values = parent::generateSampleValue($field_definition);
$values['type'] = array_rand(telephone_types_options());
return $values;
}
}
<?php
namespace Drupal\telephone_type\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\telephone\Plugin\Field\FieldWidget\TelephoneDefaultWidget;
/**
* Plugin implementation of the 'telephone_type_default' widget.
*
* @FieldWidget(
* id = "telephone_type_default",
* label = @Translation("Telephone number w/type"),
* field_types = {
* "telephone_type"
* }
* )
*/
class TelephoneTypeDefaultWidget extends TelephoneDefaultWidget {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
$settings = parent::defaultSettings();
$settings['placeholder'] = '480 731 8000';
$settings['type_required'] = FALSE;
$settings['types'] = ['home', 'work', 'cell', 'fax'];
return $settings;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element = parent::settingsForm($form, $form_state);
$element['types'] = [
'#type' => 'select',
'#title' => t('Types'),
'#options' => telephone_types_options(),
'#default_value' => $this->getSetting('types'),
'#multiple' => TRUE,
'#size' => count(telephone_types_options()),
'#description' => t('Select the types to display in form.'),
];
$element['type_required'] = [
'#type' => 'checkbox',
'#title' => t('Type required'),
'#default_value' => $this->getSetting('type_required'),
'#description' => t('Check this to require a type be selected.'),
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
// Types.
$types = $this->getSetting('types');
if (!empty($types)) {
$list = [];
foreach ($types as $type) {
$list[] = telephone_types_options($type);
}
$summary[] = t('Types: @types', ['@types' => implode(', ', $list)]);
}
else {
$summary[] = t('No types');
}
// Required.
$required = $this->getSetting('type_required');
if ($required) {
$summary[] = t('Type value is required.');
}
return $summary;
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$options = $this->getSetting('types');
$options_list = [];
foreach ($options as $option) {
$options_list[$option] = telephone_types_options($option);
}
$widget = parent::formElement($items, $delta, $element, $form, $form_state);
$widget['type'] = [
'#type' => 'select',
'#title' => t('Type'),
'#description' => t('Select the type of this telephone number.'),
'#options' => $options_list,
'#default_value' => isset($items[$delta]) ? $items[$delta]->type : NULL,
'#required' => $this->getSetting('type_required'),
'#empty_value' => '',
'#maxlength' => 25,
'#weight' => '25',
];
// Change display of number to National format.
if (!empty($widget['value']['#default_value'])) {
$validator = \Drupal::service('telephone_type.validator');
$widget['value']['#default_value'] = $validator->format($widget['value']['#default_value']);
}
return $widget;
}
}
<?php
namespace Drupal\telephone_type\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Telephone constraint.
*
* @Constraint(
* id = "TelephoneTypeValidation",
* label = @Translation("Telephone", context = "Validation")
* )
*/
class TelephoneTypeValidationContraint extends Constraint {
public $message = "@number is not a valid US phone number.";
}
<?php
namespace Drupal\telephone_type\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\ExecutionContextInterface;
/**
* Validates the LinkExternalProtocols constraint.
*/
class TelephoneTypeValidationContraintValidator implements ConstraintValidatorInterface {
/**
* Stores the validator's state during validation.
*
* @var \Symfony\Component\Validator\ExecutionContextInterface
*/
protected $context;
/**
* Validator service.
*
* @var \Drupal\telephone_type_validation\Validator
*/
protected $validator;
/**
* {@inheritdoc}
*/
public function initialize(ExecutionContextInterface $context) {
$this->context = $context;
$this->validator = \Drupal::service('telephone_type.validator');
}
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint) {
try {
$number = $value->getValue();
}
catch (\InvalidArgumentException $e) {
return;
}
// Validate number against validation settings.
if (!$this->validator->isValid($number['value'])) {
$this->context->addViolation($constraint->message, ['@number' => $number['value']]);
}
}
}
<?php
namespace Drupal\telephone_type;
use libphonenumber\PhoneNumberFormat;
use libphonenumber\PhoneNumberUtil;
/**
* Performs telephone_type validation.
*/
class Validator {
/**
* Phone Number util.
*
* @var \libphonenumber\PhoneNumberUtil
*/
public $phoneUtils;
/**
* Validator constructor.
*/
public function __construct() {
$this->phoneUtils = PhoneNumberUtil::getInstance();
}
/**
* Check if number is valid for given settings.
*
* @param string $value
* Phone number.
*
* @return bool
* Boolean representation of validation result.
*/
public function isValid($value) {
try {
$number = $this->phoneUtils->parse($value, 'US');
}
catch (\Exception $e) {
return FALSE;
}
// Perform validation for valid US number.
if ($this->phoneUtils->isValidNumber($number) && $this->phoneUtils->isValidNumberForRegion($number, 'US')) {
return TRUE;
}
return FALSE;
}
/**
* Get telephone number in number only format.
*
* @param null $value
*
* @return bool|string
*/
public function getNationalNumber($value) {
try {
$number = $this->phoneUtils->parse($value, 'US');
}
catch (\Exception $e) {
return FALSE;
}
return $this->phoneUtils->getNationalSignificantNumber($number);
}
/**
* Format a telephone number, National.
*
* @param null $value
*
* @return bool|string
*/
public function format($value = NULL) {
try {
$number = $this->phoneUtils->parse($value, 'US');
}
catch (\Exception $e) {
return FALSE;
}
return $this->phoneUtils->format($number, PhoneNumberFormat::NATIONAL);
}
/**
* Format a telephone number for use in URI.
*
* @param null $value
*
* @return bool|string
*/
public function formatUri($value = NULL) {
try {
$number = $this->phoneUtils->parse($value, 'US');
}
catch (\Exception $e) {
return FALSE;
}
return $this->phoneUtils->format($number, PhoneNumberFormat::RFC3966);
}
}
name: 'Telephone Type'
description: 'Defines a field type for telephone numbers with optional types. Extend core telephone field.'
package: Field types
core: 8.x
version: 8.x-3.x-dev
dependencies:
- telephone
type: module
<?php
/**
* @file
* Installation hooks.
*/
/**
* Implements hook_requirements().
*/
function telephone_type_requirements($phase) {
$requirements = [];
if ($phase == 'install') {
if (!class_exists('\\libphonenumber\\PhoneNumberUtil')) {
$requirements['telephone_type'] = [
'title' => t('Composer dependencies'),
'description' => t('This module can be only installed with Composer as it uses composer dependencies. Read more on https://www.drupal.org/node/2718229'),
'severity' => REQUIREMENT_ERROR,
];
}
}
return $requirements;
}
<?php
/**
* @file
* Defines a telephone number field type with telephone types selector.
* Extend core telephone module.
*/
/**
* Implements hook_field_widget_FORM_ID_form_alter().
*/
function telephone_type_field_widget_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) {
$field_definition = $context['items']->getFieldDefinition();
if ($field_definition->getType() == 'telephone_type') {
if (isset($element['value']['#title'])) {
$element['value']['#title'] = 'Number';
}
// Add title for telephone field back for multiple values.
$cardinality = $field_definition->getFieldStorageDefinition()->getCardinality();
if ($cardinality == 1) {
$element['#type'] = 'fieldset';
}
else {
$element['value']['#title_display'] = 'before';
}
}
}
/**
* Function to return telephone type options.
*
* @param null $option
*
* @return array|mixed
*/
function telephone_types_options($option = NULL) {
$options = [
//'Voice' => t('Voice number'),
'main' => t('Main number'),
'home' => t('Home'),
'cell' => t('Cellular/Mobile'),
'work' => t('Work'),
'fax' => t('Fax'),
'pager' => t('Pager'),
];
return isset($options[$option]) ? $options[$option] : $options;
}
\ No newline at end of file
services:
telephone_type.validator:
class: Drupal\telephone_type\validator
<?php
namespace Drupal\Tests\telephone_type\Functional;
use Drupal\field\Entity\FieldConfig;
use Drupal\Tests\BrowserTestBase;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests the creation of telephone_type fields.
*
* @group telephone
*/
class TelephoneTypeFieldTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'field',
'node',
'telephone_type'
];
/**
* A user with permission to create articles.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
protected function setUp() {
parent::setUp();
$this->drupalCreateContentType(['type' => 'article']);
$this->webUser = $this->drupalCreateUser(['create article content', 'edit own article content']);
$this->drupalLogin($this->webUser);
}
// Test fields.
/**
* Helper function for testTelephoneTypeField().
*/
public function testTelephoneTypeField() {
// Add the telephone field to the article content type.
FieldStorageConfig::create([
'field_name' => 'field_telephone',
'entity_type' => 'node',
'type' => 'telephone_type',