You are here

block_titlelink.module in Block Title Link 8

module for adding a link to a block title

File

block_titlelink.module
View source
<?php

/**
 * @file module for adding a link to a block title
*/
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Template\Attribute;

/**
 * Implementation of hook_form_alter
*/
function block_titlelink_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if ($form_id == 'block_form' || $form_id == 'basic_custom_block_form') {
    $target_values = [
      '_blank',
      '_self',
      '_parent',
      '_top',
    ];
    $targets = array_combine($target_values, $target_values);
    $config = \Drupal::config('block_titlelink.settings');
    $block_id = $form['id']['#default_value'];
    $display = $config
      ->get($block_id . '.display_link');
    $display = isset($display) ? $display : TRUE;
    $form['block_titlelink'] = [
      '#type' => 'details',
      '#title' => t('Block Title Link Settings'),
      '#collapsible' => TRUE,
      '#weight' => 0,
      '#open' => FALSE,
    ];
    $form['block_titlelink']['title_link'] = [
      '#type' => 'textfield',
      '#title' => t('Title Path'),
      '#description' => t('URL path of Block Title. Tokens are supported.'),
      '#default_value' => $config
        ->get($block_id . '.title_link'),
    ];
    $form['block_titlelink']['title_link_title'] = [
      '#type' => 'textfield',
      '#title' => t('Title Attribute'),
      '#description' => t('Value for the &lt;a&gt; title attribute.'),
      '#default_value' => $config
        ->get($block_id . '.title'),
    ];
    $form['block_titlelink']['title_link_target'] = [
      '#type' => 'select',
      '#title' => t('Link Target'),
      '#options' => $targets,
      '#empty_value' => '',
      '#description' => t('Add a target to open the link in a new tab/window'),
      '#default_value' => $config
        ->get($block_id . '.target'),
    ];
    $form['block_titlelink']['display_link'] = [
      '#type' => 'checkbox',
      '#title' => t('Display Link'),
      '#description' => t('Select this option if title should render as a link. If deselected, the title path value is stored within the block object as $block->title_link but is not rendered. This is useful if you wish to use the link elsewhere in the block template (ex: as an icon).'),
      '#default_value' => $display,
    ];
    $form['actions']['submit']['#validate'][] = 'block_titlelink_form_validate';
    $form['actions']['submit']['#submit'][] = 'block_titlelink_form_submit';
  }
}

/**
 * Form validation callback for block_titlelink_form_alter
 */
function block_titlelink_form_validate(&$form, FormStateInterface &$form_state) {
  $title_link = trim($form_state
    ->getValue([
    'block_titlelink',
    'title_link',
  ]));
  $valid = _block_titlelink_validate_url($title_link);
  if (!empty($title_link) && !$valid) {
    $form_state
      ->setErrorByName('block_search_form_blocktitlelink][title_link', t('The title path appears to contain an invalid URL.'));
  }
}

/**
 * Form submission callback for block_titlelink_form_alter
 */
function block_titlelink_form_submit(&$form, FormStateInterface &$form_state) {
  $config = \Drupal::configFactory()
    ->getEditable('block_titlelink.settings');
  $block_id = $form['id']['#default_value'];
  $url = trim($form_state
    ->getValue([
    'block_titlelink',
    'title_link',
  ]));
  $data = [
    'url' => $url,
    'display' => $form_state
      ->getValue([
      'block_titlelink',
      'display_link',
    ]),
    'title' => $form_state
      ->getValue([
      'block_titlelink',
      'title_link_title',
    ]),
    'target' => $form_state
      ->getValue([
      'block_titlelink',
      'title_link_target',
    ]),
  ];
  if (empty($url)) {
    $config
      ->clear($block_id);
    $config
      ->save();
  }
  else {
    $config
      ->set($block_id . '.title_link', $data['url'])
      ->set($block_id . '.display_link', $data['display'])
      ->set($block_id . '.title', $data['title'])
      ->set($block_id . '.target', $data['target'])
      ->save();
  }
}

/**
 * Implements hook_preprocess_hook().
 */
function block_titlelink_preprocess_block(&$vars) {

  // Get the block id
  $block_id = $vars['elements']['#id'];
  $config = \Drupal::config('block_titlelink.settings');
  $url = $config
    ->get($block_id . '.title_link');
  $url = isset($url) ? trim($url) : NULL;
  $display = $config
    ->get($block_id . '.display_link');
  $display = isset($display) ? $display : TRUE;
  $titlelink_title = $config
    ->get($block_id . '.title');
  $titlelink_title = isset($titlelink_title) ? Html::escape($titlelink_title) : NULL;
  $target = $config
    ->get($block_id . '.target');
  $target = isset($target) ? $target : NULL;
  if (\Drupal::moduleHandler()
    ->moduleExists('php')) {
    $url = php_eval($url);
  }
  if (!empty($url) && $display) {
    $attributes = [
      'attributes' => [
        'class' => [
          'block-title-link',
        ],
      ],
      'html' => TRUE,
    ];
    if (!empty($titlelink_title)) {
      $attributes['attributes']['title'] = $titlelink_title;
    }
    if (!empty($target)) {
      $attributes['attributes']['target'] = $target;
    }
    $attributes = new Attribute($attributes['attributes']);
    $vars['label'] = Xss::filter('<a href="' . $url . '"' . $attributes . '>' . t($vars['label']) . '</a>');
  }
}

/**
 * Heisted the url validation from the link field module.
 *
 * @param string $url
 *   The url to validate.
 *
 * @return string
 *   The url type if url is valid, FALSE otherwise.
 */
function _block_titlelink_validate_url($url) {
  $text = $url;

  // These constants were heisted from the field_module.
  // Check if they exist
  if (!defined('LINK_EXTERNAL')) {
    define('LINK_EXTERNAL', 'external');
  }
  if (!defined('LINK_INTERNAL')) {
    define('LINK_INTERNAL', 'internal');
  }
  if (!defined('LINK_FRONT')) {
    define('LINK_FRONT', 'front');
  }
  if (!defined('LINK_EMAIL')) {
    define('LINK_EMAIL', 'email');
  }
  if (!defined('LINK_NEWS')) {
    define('LINK_NEWS', 'news');
  }
  if (!defined('LINK_DOMAINS')) {
    define('LINK_DOMAINS', 'aero|arpa|asia|biz|com|cat|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|mobi|local');
  }
  $LINK_ICHARS_DOMAIN = (string) html_entity_decode(implode("", [
    "&#x00E6;",
    // æ
    "&#x00C6;",
    // Æ
    "&#x00F8;",
    // ø
    "&#x00D8;",
    // Ø
    "&#x00E5;",
    // å
    "&#x00C5;",
    // Å
    "&#x00E4;",
    // ä
    "&#x00C4;",
    // Ä
    "&#x00F6;",
    // ö
    "&#x00D6;",
    // Ö
    "&#x00FC;",
    // ü
    "&#x00DC;",
    // Ü
    "&#x00D1;",
    // Ñ
    "&#x00F1;",
  ]), ENT_QUOTES, 'UTF-8');
  $LINK_ICHARS = $LINK_ICHARS_DOMAIN . (string) html_entity_decode(implode("", [
    "&#x00DF;",
  ]), ENT_QUOTES, 'UTF-8');
  $allowed_protocols = [
    'http',
    'https',
    'ftp',
    'news',
    'nntp',
    'telnet',
    'mailto',
    'irc',
    'ssh',
    'sftp',
    'webcal',
  ];
  $protocol = '((' . implode("|", $allowed_protocols) . '):\\/\\/)';
  $authentication = '(([a-z0-9%' . $LINK_ICHARS . ']+(:[a-z0-9%' . $LINK_ICHARS . '!]*)?)?@)';
  $domain = '(([a-z0-9' . $LINK_ICHARS_DOMAIN . ']([a-z0-9' . $LINK_ICHARS_DOMAIN . '\\-_\\[\\]])*)(\\.(([a-z0-9' . $LINK_ICHARS_DOMAIN . '\\-_\\[\\]])+\\.)*(' . LINK_DOMAINS . '|[a-z]{2}))?)';
  $ipv4 = '([0-9]{1,3}(\\.[0-9]{1,3}){3})';
  $ipv6 = '([0-9a-fA-F]{1,4}(\\:[0-9a-fA-F]{1,4}){7})';
  $port = '(:([0-9]{1,5}))';

  // Pattern specific to external links.
  $external_pattern = '/^' . $protocol . '?' . $authentication . '?(' . $domain . '|' . $ipv4 . '|' . $ipv6 . ' |localhost)' . $port . '?';

  // Pattern specific to internal file link.
  $internal_pattern_file = "/^([a-z0-9" . $LINK_ICHARS . "_\\-+\\[\\]\\.]+)\$/i";
  $directories = "(\\/[a-z0-9" . $LINK_ICHARS . "_\\-\\.~+%=&,\$'!():;*@\\[\\]]*)*";

  // Yes, four backslashes == a single backslash.
  $query = "(\\/?\\?([?a-z0-9" . $LINK_ICHARS . "+_|\\-\\.\\/\\\\%=&,\$'():;*@\\[\\]{} ]*))";
  $anchor = "(#[a-z0-9" . $LINK_ICHARS . "_\\-\\.~+%=&,\$'():;*@\\[\\]\\/\\?]*)";

  // The rest of the path for a standard URL.
  $end = $directories . '?' . $query . '?' . $anchor . '?' . '$/i';
  $message_id = '[^@].*@' . $domain;
  $newsgroup_name = '([0-9a-z+-]*\\.)*[0-9a-z+-]*';
  $news_pattern = '/^news:(' . $newsgroup_name . '|' . $message_id . ')$/i';
  $user = '[a-zA-Z0-9' . $LINK_ICHARS . '_\\-\\.\\+\\^!#\\$%&*+\\/\\=\\?\\`\\|\\{\\}~\'\\[\\]]+';
  $email_pattern = '/^mailto:' . $user . '@' . '(' . $domain . '|' . $ipv4 . '|' . $ipv6 . '|localhost)' . $query . '?$/';
  if (strpos($text, '<front>') === 0) {
    return LINK_FRONT;
  }
  if (in_array('mailto', $allowed_protocols) && preg_match($email_pattern, $text)) {
    return LINK_EMAIL;
  }
  if (in_array('news', $allowed_protocols) && preg_match($news_pattern, $text)) {
    return LINK_NEWS;
  }
  if (\Drupal::service('path.validator')
    ->isValid($text)) {
    return LINK_INTERNAL;
  }
  if (preg_match($external_pattern . $end, $text)) {
    return LINK_EXTERNAL;
  }
  if (preg_match($internal_pattern_file, $text)) {
    return LINK_INTERNAL;
  }
  return FALSE;
}

Functions

Namesort descending Description
block_titlelink_form_alter Implementation of hook_form_alter
block_titlelink_form_submit Form submission callback for block_titlelink_form_alter
block_titlelink_form_validate Form validation callback for block_titlelink_form_alter
block_titlelink_preprocess_block Implements hook_preprocess_hook().
_block_titlelink_validate_url Heisted the url validation from the link field module.