commit bad4e14892f42e7a06e9ac8b9922cfb50a57c3ff
Author: Assos Assos <assos+forge@centrale-marseille.fr>
Date:   Wed Apr 21 23:12:09 2021 +0200

    Weekly update of contrib modules

diff --git a/drupal7/sites/all/modules/dhtml_menu/dhtml_menu.info b/drupal7/sites/all/modules/dhtml_menu/dhtml_menu.info
index c7b922f5..0c1c1528 100644
--- a/drupal7/sites/all/modules/dhtml_menu/dhtml_menu.info
+++ b/drupal7/sites/all/modules/dhtml_menu/dhtml_menu.info
@@ -16,8 +16,8 @@ project = "dhtml_menu"
 datestamp = "1380569449"
 
 
-; Information added by Drupal.org packaging script on 2019-10-07
-version = "7.x-1.0-beta2"
+; Information added by Drupal.org packaging script on 2021-04-15
+version = "7.x-1.0-beta3"
 core = "7.x"
 project = "dhtml_menu"
-datestamp = "1570442588"
+datestamp = "1618470530"
diff --git a/drupal7/sites/all/modules/dhtml_menu/dhtml_menu.js b/drupal7/sites/all/modules/dhtml_menu/dhtml_menu.js
index 20b80fa6..1e58f407 100644
--- a/drupal7/sites/all/modules/dhtml_menu/dhtml_menu.js
+++ b/drupal7/sites/all/modules/dhtml_menu/dhtml_menu.js
@@ -105,6 +105,20 @@ Drupal.behaviors.dhtmlMenu = {
           });
         }
 
+        /**
+         * If link is a placeholder (ie from special_menu_items module) make sure that
+         * when you click it, the children show
+         */
+        if (link.attr('href') == '#') {
+          link.click(function(e) {
+            Drupal.dhtmlMenu.toggleMenu(li, link, ul);
+            if (settings.effects.remember) {
+              Drupal.dhtmlMenu.cookieSet();
+            }
+            return false;
+          });
+        }
+
         /* When using bullet expansion:
          * - Change the icon to a folder image
          * - Add the clickable overlay and its handler
@@ -198,7 +212,7 @@ Drupal.behaviors.dhtmlMenu = {
     });
 
     // When using LTR, all icons can be shifted as one, as the text width is not relevant.
-    if (settings.nav == 'bullet' && !rtl && $('.menu li.dhtml-folder').length) {
+    if (settings.nav == 'bullet' && $('.menu li').length && !rtl && $('.menu li.dhtml-folder').length) {
       // Shift overlay to the left by the width of the icon and the distance between icon and text.
       if ($('.menu li').hasClass('margin-left')) {
         // Saved for a later backport if needs.
diff --git a/drupal7/sites/all/modules/dhtml_menu/dhtml_menu.theme.inc b/drupal7/sites/all/modules/dhtml_menu/dhtml_menu.theme.inc
index 697c0667..915479e2 100644
--- a/drupal7/sites/all/modules/dhtml_menu/dhtml_menu.theme.inc
+++ b/drupal7/sites/all/modules/dhtml_menu/dhtml_menu.theme.inc
@@ -26,6 +26,9 @@ function dhtml_menu_preprocess_menu_link(&$variables) {
 
   // Saves a lot of code.
   $l = &$variables['element']['#original_link'];
+  if (empty($l)) {
+    return;
+  }
 
   // Determine if the menu is blacklisted or not whitelisted.
   // First check menu blocks.
diff --git a/drupal7/sites/all/modules/link/CHANGELOG.txt b/drupal7/sites/all/modules/link/CHANGELOG.txt
index d9fdeba6..c796d32a 100644
--- a/drupal7/sites/all/modules/link/CHANGELOG.txt
+++ b/drupal7/sites/all/modules/link/CHANGELOG.txt
@@ -1,3 +1,42 @@
+Link 7.x-1.9, 2021-04-19
+------------------------
+#3209434 by DamienMcKenna, tiziano.sartori: Syntax error, unexpected '(' after
+  upgrade to 7.x-1.8.
+
+
+Link 7.x-1.8, 2021-04-16
+------------------------
+#2537248 by osopolar, Ronino, DamienMcKenna: Allow title without url on
+  migration.
+#836710 by DamienMcKenna, ciss, Johan den Hollander, joachim, mstrelan, sos4nt,
+  Paul Lomax, rcross, rooby, timofey, seanB, Adirael, esbite, kay_v,
+  joep.hendrix: Convert links to their internal link representation if they
+  exist.
+#3110061 by DamienMcKenna: Tidy up test files.
+#3120382 by dsnopek, RenatoG, DamienMcKenna: Notice: Undefined index: title in
+  link_field_load() (line 310 of link.module).
+By DamienMcKenna: Unnecessary syntax change broke PHP 5.3 compatibility.
+#3156021 by roman.haluska, klausi, DamienMcKenna, RenatoG: According to RFC4343
+  uppercase letters in the domain name should be allowed.
+#3208147 by DamienMcKenna: Simplify nested ternary logic in _link_sanitize().
+#3121196 by torotil, DamienMcKenna, calebtr: Support relative fragment or query
+  -only links.
+#3208151 by DamienMcKenna: Coding standards cleanup.
+#3178419 by DamienMcKenna: Simplify the theme functions.
+#3208738 by jedihe, DamienMcKenna: Missing proper test_dependencies, testbot
+  ignoring some tests.
+#3208869 by DamienMcKenna: Simplify tests by extending LinkBaseTestClass.
+#3208869 by DamienMcKenna: Forgot to update LinkSanitizeTest.
+#3208957 by DamienMcKenna: Add unit tests for _link_parse_url().
+#3095484 by DamienMcKenna, jedihe, silverham, circuscowboy, dungahk,
+  codebymikey, bondjimbond, tcnolan7, La558, timwright_, Ron Collins, Summit,
+  BrianLewisDesign, chegor, danbarron, SKrossa, wylbur, zorya: Query string,
+  fragment stripped from URL.
+#3158296 by marcosdr, opdavies, DamienMcKenna, paulocs: Enable to switch default
+  link protocol between HTTP and HTTPS.
+#3201437 by xlin, DamienMcKenna: Optional title for Selected Title.
+
+
 Link 7.x-1.7, 2019-11-14
 ------------------------
 #3094381 by DamienMcKenna: Add a CHANGELOG.txt file.
@@ -327,7 +366,7 @@ Link 7.x-1.0, 2011-10-23
 #948658 by brenk28, Dave Reid, becw, jcfiala, iMiksu, BTMash, Digidog: Fixed
   noticeof Undefined index: title in _link_sanitize() - hopefully, finally.
 #1079782 by fago, klausi, drunken monkey, jcfiala, Digidog: Fixed support for
-  hook_entity_property_info() including search API and Rules. @TODO: needs
+  hook_entity_property_info() including search API and Rules. @todo: needs
   simpletests to be submitted for rc1.
 
 Link 7.x-1.0-beta1, 2011-09-26
diff --git a/drupal7/sites/all/modules/link/link.admin.inc b/drupal7/sites/all/modules/link/link.admin.inc
new file mode 100644
index 00000000..d5527ef4
--- /dev/null
+++ b/drupal7/sites/all/modules/link/link.admin.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Link admin settings.
+ */
+
+/**
+ * Form constructor for the settings form.
+ *
+ * @ingroup forms
+ */
+function link_admin_settings() {
+  $form['link_default_protocol'] = array(
+    '#type' => 'select',
+    '#title' => t('Protocol'),
+    '#options' => array(
+      LINK_HTTP_PROTOCOL => strtoupper(LINK_HTTP_PROTOCOL),
+      LINK_HTTPS_PROTOCOL => strtoupper(LINK_HTTPS_PROTOCOL),
+    ),
+    '#default_value' => variable_get('link_default_protocol', LINK_HTTP_PROTOCOL),
+    '#description' => t('Select the default protocol to use for each link.'),
+  );
+
+  return system_settings_form($form);
+}
diff --git a/drupal7/sites/all/modules/link/link.info b/drupal7/sites/all/modules/link/link.info
index 609379a3..76b14109 100644
--- a/drupal7/sites/all/modules/link/link.info
+++ b/drupal7/sites/all/modules/link/link.info
@@ -2,25 +2,40 @@ name = Link
 description = Defines simple link field types.
 core = 7.x
 package = Fields
+configure = admin/config/content/link
 
+; Migrate integration.
 files[] = link.migrate.inc
 
-; Tests
-files[] = tests/link.test
-files[] = tests/link.attribute.test
-files[] = tests/link.crud.test
-files[] = tests/link.crud_browser.test
-files[] = tests/link.token.test
-files[] = tests/link.entity_token.test
-files[] = tests/link.validate.test
-files[] = tests/link.multilingual.test
-
-; Views Handlers
+; Views integration.
 files[] = views/link_views_handler_argument_target.inc
 files[] = views/link_views_handler_filter_protocol.inc
 
-; Information added by Drupal.org packaging script on 2019-11-14
-version = "7.x-1.7"
+; Tests.
+; Most tests extend from this class.
+files[] = tests/LinkBaseTestClass.test
+
+; All of the actual tests.
+files[] = tests/LinkConvertInternalPathsTest.test
+files[] = tests/LinkDefaultProtocolTest.test
+files[] = tests/LinkEntityTokenTest.test
+files[] = tests/LinkFieldAttributesTest.test
+files[] = tests/LinkFieldCrudTest.test
+files[] = tests/LinkFieldValidateTest.test
+files[] = tests/LinkPathPrefixesTest.test
+files[] = tests/LinkSanitizeTest.test
+files[] = tests/LinkTokenTest.test
+files[] = tests/LinkValidationApiTest.test
+
+; Testing dependencies.
+test_dependencies[] = entity:entity
+test_dependencies[] = token:token
+
+; Standalone unit tests.
+files[] = tests/LinkUnitTestCase.test
+
+; Information added by Drupal.org packaging script on 2021-04-19
+version = "7.x-1.9"
 core = "7.x"
 project = "link"
-datestamp = "1573731676"
+datestamp = "1618831239"
diff --git a/drupal7/sites/all/modules/link/link.install b/drupal7/sites/all/modules/link/link.install
index 75a5d72f..e72d04f6 100644
--- a/drupal7/sites/all/modules/link/link.install
+++ b/drupal7/sites/all/modules/link/link.install
@@ -36,6 +36,7 @@ function link_update_7002() {
  */
 function link_uninstall() {
   variable_del('link_allowed_domains');
+  variable_del('link_default_protocol');
 }
 
 /**
diff --git a/drupal7/sites/all/modules/link/link.migrate.inc b/drupal7/sites/all/modules/link/link.migrate.inc
index 49000510..689f4e43 100644
--- a/drupal7/sites/all/modules/link/link.migrate.inc
+++ b/drupal7/sites/all/modules/link/link.migrate.inc
@@ -102,7 +102,28 @@ class MigrateLinkFieldHandler extends MigrateFieldHandler {
     }
 
     $language = $this->getFieldLanguage($entity, $field_info, $arguments);
-    $values = array_filter($values);
+
+    // URLs are numeric array elements, keyed by delta. As those elements don't
+    // exist for empty URLs they are allowed and it's up to the developer what
+    // data to migrate, add elements for those URLs up to the maximum delta
+    // found.
+    //
+    // First of all, determine the maximum delta from non-empty URLs.
+    $max_delta = max(array_merge(array(0), array_keys($values)));
+
+    // Next, determine the max delta from arguments.
+    foreach ($arguments as $key => $value) {
+      if (is_array($value)) {
+        $max_delta = max($max_delta, max(array_keys($value)));
+      }
+    }
+
+    // Finally, add missing elements for empty URLs.
+    for ($delta = 0; $delta <= $max_delta; $delta++) {
+      if (!isset($values[$delta])) {
+        $values[$delta] = '';
+      }
+    }
 
     foreach ($values as $delta => $value) {
       $item = array();
diff --git a/drupal7/sites/all/modules/link/link.module b/drupal7/sites/all/modules/link/link.module
index 31449e21..acd44744 100644
--- a/drupal7/sites/all/modules/link/link.module
+++ b/drupal7/sites/all/modules/link/link.module
@@ -18,6 +18,8 @@ define('LINK_TARGET_DEFAULT', 'default');
 define('LINK_TARGET_NEW_WINDOW', '_blank');
 define('LINK_TARGET_TOP', '_top');
 define('LINK_TARGET_USER', 'user');
+define('LINK_HTTP_PROTOCOL', 'http');
+define('LINK_HTTPS_PROTOCOL', 'https');
 
 /**
  * Maximum URLs length - needs to match value in link.install.
@@ -67,6 +69,7 @@ function link_field_info() {
         'title_label_use_field_label' => FALSE,
         'title_maxlength' => 128,
         'enable_tokens' => 1,
+        'convert_aliases' => 0,
         'display' => array(
           'url_cutoff' => 80,
         ),
@@ -187,6 +190,13 @@ function link_field_instance_settings_form($field, $instance) {
     );
   }
 
+  $form['convert_aliases'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Convert local aliases'),
+    '#default_value' => isset($instance['settings']['convert_aliases']) ? $instance['settings']['convert_aliases'] : '',
+    '#description' => t('If checked, a path alias is converted to the internal system path, the same way as when saving menu links.'),
+  );
+
   $form['display'] = array(
     '#tree' => TRUE,
   );
@@ -307,7 +317,7 @@ function link_field_load($entity_type, $entities, $field, $instances, $langcode,
   foreach ($entities as $id => $entity) {
     foreach ($items[$id] as $delta => $item) {
       $items[$id][$delta]['attributes'] = _link_load($field, $item, $instances[$id]);
-      $items[$id][$delta]['original_title'] = $item['title'];
+      $items[$id][$delta]['original_title'] = isset($item['title']) ? $item['title'] : NULL;
       $items[$id][$delta]['original_url'] = $item['url'];
     }
   }
@@ -341,6 +351,18 @@ function link_field_validate($entity_type, $entity, $field, $instance, $langcode
       'error_element' => array('url' => FALSE, 'title' => TRUE),
     );
   }
+
+  // Specific logic for when using the 'select' option on the title field.
+  if ($instance['settings']['title'] == 'select') {
+    // The title is required.
+    if (!empty($item['title']) && !empty($item['url'])) {
+      $errors[$field['field_name']][$langcode][$delta][] = array(
+        'error' => 'link_required',
+        'message' => t('The title field is required when a URL is provided.'),
+        'error_element' => array('url' => FALSE, 'title' => TRUE),
+      );
+    }
+  }
 }
 
 /**
@@ -449,6 +471,68 @@ function _link_process(&$item, $delta, $field, $entity, $instance) {
     $item['url'] = trim($item['url']);
   }
 
+  // Optionally convert aliases to the system path.
+  if (!empty($instance['settings']['convert_aliases'])) {
+    global $base_url;
+
+    // Check if either the site's absolute URL or the relative base URL are at
+    // the start of the URL, if so remove them.
+    $base_paths = array(
+      $base_url . base_path(),
+      base_path(),
+    );
+
+    // Work out the correct base_path to use based on the HTTPS settings.
+    if (isset($GLOBALS['base_secure_url'])) {
+      $base_paths[] = $GLOBALS['base_secure_url'] . base_path();
+    }
+    if (isset($GLOBALS['base_insecure_url'])) {
+      $base_paths[] = $GLOBALS['base_insecure_url'] . base_path();
+    }
+
+    // Add any additional paths.
+    if ($extra_paths = variable_get('link_base_urls', array())) {
+      // Create versions with and without the base path.
+      foreach ($extra_paths as $extra_path) {
+        $base_paths[] = $extra_path;
+        $base_paths[] = $extra_path . base_path();
+      }
+    }
+
+    $paths_to_test = array(
+      $item['url'],
+    );
+
+    foreach ($base_paths as $path) {
+      // Verify the path is at the beginning of the URL string.
+      if (strpos($item['url'], $path) === 0) {
+        $strlen = drupal_strlen($path);
+        $paths_to_test[] = drupal_substr($item['url'], $strlen);
+      }
+    }
+
+    // Check each of the paths to see if one of them is a system path.
+    foreach (array_unique($paths_to_test) as $path) {
+      $language = NULL;
+
+      // If we have locale enabled attempt to remove the language prefix first.
+      if (module_exists('locale')) {
+        require_once DRUPAL_ROOT . '/includes/language.inc';
+        list($language, $path) = language_url_split_prefix($path, language_list());
+      }
+
+      // Attempt to get a system path.
+      $normal_path = drupal_get_normal_path($path, $language);
+
+      // If we get back a different path it means Drupal found a system path we
+      // can use.
+      if ($normal_path != $path) {
+        $item['url'] = $normal_path;
+        break;
+      }
+    }
+  }
+
   // If no attributes are set then make sure $item['attributes'] is an empty
   // array, so $field['attributes'] can override it.
   if (empty($item['attributes'])) {
@@ -516,11 +600,28 @@ function _link_validate(&$item, $delta, $field, $entity, $instance, $langcode, &
       'error_element' => array('url' => FALSE, 'title' => TRUE),
     );
   }
+
+  // Specific logic for when using the 'select' option on the title field.
+  if ($instance['settings']['title'] == 'select') {
+    // The title is required.
+    if (!empty($item['title']) && !empty($item['url'])) {
+      $errors[$field['field_name']][$langcode][$delta][] = array(
+        'error' => 'link_required',
+        'message' => t('The title field is required when a URL is provided.'),
+        'error_element' => array('url' => FALSE, 'title' => TRUE),
+      );
+    }
+  }
 }
 
 /**
  * Clean up user-entered values for a link field according to field settings.
  *
+ * Note: this cannot be properly unit tested as it checks for certain entity
+ * values.
+ *
+ * @todo Rewrite so that the logic can be unit tested.
+ *
  * @param array $item
  *   A single link item, usually containing url, title, and attributes.
  * @param int $delta
@@ -557,9 +658,15 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) {
   $entity_type = $instance['entity_type'];
   $entity_info = entity_get_info($entity_type);
   $property_id = $entity_info['entity keys']['id'];
-  $entity_token_type = isset($entity_info['token type']) ? $entity_info['token type'] : (
-  $entity_type == 'taxonomy_term' || $entity_type == 'taxonomy_vocabulary' ? str_replace('taxonomy_', '', $entity_type) : $entity_type
-  );
+  if (isset($entity_info['token type'])) {
+    $entity_token_type = $entity_info['token type'];
+  }
+  elseif ($entity_type == 'taxonomy_term' || $entity_type == 'taxonomy_vocabulary') {
+    $entity_token_type = str_replace('taxonomy_', '', $entity_type);
+  }
+  else {
+    $entity_token_type = $entity_type;
+  }
   if (isset($instance['settings']['enable_tokens']) && $instance['settings']['enable_tokens']) {
     $text_tokens = token_scan($item['url']);
     if (!empty($text_tokens)) {
@@ -582,10 +689,15 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) {
     $type = LINK_EXTERNAL;
   }
   elseif ($type == LINK_FRAGMENT || $type == LINK_QUERY) {
-    // If type is a fragment or query, then use the current URL.
-    $item['url'] = $_GET['q'] . $item['url'];
+    // Treat fragment or query-only links as external.
+    if (!empty($instance['settings']['absolute_url'])) {
+      $item['url'] = $_GET['q'] . $item['url'];
+    }
+    else {
+      $item['external'] = TRUE;
+    }
   }
-  $url = link_cleanup_url($item['url']);
+  $url = link_cleanup_url($item['url'], variable_get('link_default_protocol', LINK_HTTP_PROTOCOL));
   $url_parts = _link_parse_url($url);
 
   if (!empty($url_parts['url'])) {
@@ -603,7 +715,13 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) {
     $display_url = str_replace('mailto:', '', $url);
   }
   elseif ($type === LINK_EXTERNAL) {
-    $display_url = $item['url'];
+    $display_url = url($url_parts['url'],
+      array(
+        'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
+        'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
+        'absolute' => TRUE,
+      )
+    );
   }
   elseif ($type == LINK_TEL) {
     $display_url = str_replace('tel:', '', $url);
@@ -828,6 +946,21 @@ function _link_parse_str($query) {
   return $query_array;
 }
 
+/**
+ * Implements hook_menu().
+ */
+function link_menu() {
+  $items['admin/config/content/link'] = array(
+    'title' => 'Link settings',
+    'description' => 'Settings for the link module.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('link_admin_settings'),
+    'access arguments' => array('access administration pages'),
+    'file' => 'link.admin.inc',
+  );
+  return $items;
+}
+
 /**
  * Implements hook_theme().
  */
@@ -1001,6 +1134,7 @@ function link_field_process($element, $form_state, $complete_form) {
       '#description' => t('Select the a title for this link.'),
       '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
       '#options' => $options,
+      '#empty_value' => '',
     );
   }
 
@@ -1148,7 +1282,6 @@ function link_field_formatter_settings_form($field, $instance, $view_mode, $form
  * Implements hook_field_formatter_settings_summary().
  */
 function link_field_formatter_settings_summary($field, $instance, $view_mode) {
-
   $display = $instance['display'][$view_mode];
 
   if ($display['type'] == 'link_domain') {
@@ -1239,14 +1372,25 @@ function theme_link_formatter_link_host($vars) {
  * Formats a link as an absolute URL.
  */
 function theme_link_formatter_link_absolute($vars) {
-  $absolute = array('absolute' => TRUE);
-  return empty($vars['element']['url']) ? '' : url($vars['element']['url'], $absolute + $vars['element']);
+  // If no URL value is present there's no point in continuing.
+  if (empty($vars['element']['url'])) {
+    return '';
+  }
+
+  // Hardcode the 'absolute' argument.
+  $vars['element']['absolute'] = TRUE;
+  return url($vars['element']['url'], $vars['element']);
 }
 
 /**
  * Formats a link using the URL's domain for it's link text.
  */
 function theme_link_formatter_link_domain($vars) {
+  // If no URL value is present there's no point in continuing.
+  if (empty($vars['element']['url'])) {
+    return '';
+  }
+
   $link_options = $vars['element'];
   unset($link_options['title']);
   unset($link_options['url']);
@@ -1254,13 +1398,18 @@ function theme_link_formatter_link_domain($vars) {
   if (!empty($vars['display']['settings']['strip_www'])) {
     $domain = str_replace('www.', '', $domain);
   }
-  return $vars['element']['url'] ? l($domain, $vars['element']['url'], $link_options) : '';
+  return l($domain, $vars['element']['url'], $link_options);
 }
 
 /**
  * Formats a link without the http:// or https://.
  */
 function theme_link_formatter_link_no_protocol($vars) {
+  // If no URL value is present there's no point in continuing.
+  if (empty($vars['element']['url'])) {
+    return '';
+  }
+
   $link_options = $vars['element'];
   unset($link_options['title']);
   unset($link_options['url']);
@@ -1270,43 +1419,63 @@ function theme_link_formatter_link_no_protocol($vars) {
   $replace = '';
   $display_url = preg_replace($search, $replace, $vars['element']['url'], 1);
 
-  return $vars['element']['url'] ? l($display_url, $vars['element']['url'], $link_options) : '';
+  return l($display_url, $vars['element']['url'], $link_options);
 }
 
 /**
  * Formats a link's title as plain text.
  */
 function theme_link_formatter_link_title_plain($vars) {
-  return empty($vars['element']['title']) ? '' : check_plain(decode_entities($vars['element']['title']));
+  // If no title value is present there's no point in continuing.
+  if (empty($vars['element']['title'])) {
+    return '';
+  }
+
+  return check_plain(decode_entities($vars['element']['title']));
 }
 
 /**
  * Formats a link using an alternate display URL for its link text.
  */
 function theme_link_formatter_link_url($vars) {
+  // If no URL value is present there's no point in continuing.
+  if (empty($vars['element']['url'])) {
+    return '';
+  }
+
   $link_options = $vars['element'];
   if (isset($link_options['attributes']['class'])) {
     $link_options['attributes']['class'] = array($link_options['attributes']['class']);
   }
   unset($link_options['title']);
   unset($link_options['url']);
-  return $vars['element']['url'] ? l($vars['element']['display_url'], $vars['element']['url'], $link_options) : '';
+  return l($vars['element']['display_url'], $vars['element']['url'], $link_options);
 }
 
 /**
  * Formats a link using "Link" as the link text.
  */
 function theme_link_formatter_link_short($vars) {
+  // If no URL value is present there's no point in continuing.
+  if (empty($vars['element']['url'])) {
+    return '';
+  }
+
   $link_options = $vars['element'];
   unset($link_options['title']);
   unset($link_options['url']);
-  return $vars['element']['url'] ? l(t('Link'), $vars['element']['url'], $link_options) : '';
+  return l(t('Link'), $vars['element']['url'], $link_options);
 }
 
 /**
  * Formats a link using the field's label as link text.
  */
 function theme_link_formatter_link_label($vars) {
+  // If no URL value is present there's no point in continuing.
+  if (empty($vars['element']['url'])) {
+    return '';
+  }
+
   $link_options = $vars['element'];
   unset($link_options['title']);
   unset($link_options['url']);
@@ -1315,7 +1484,7 @@ function theme_link_formatter_link_label($vars) {
     $i18n_string_name = "field:{$vars['field']['field_name']}:{$vars['field']['bundle']}:label";
     $label = i18n_string_translate($i18n_string_name, $label);
   }
-  return $vars['element']['url'] ? l($label, $vars['element']['url'], $link_options) : '';
+  return l($label, $vars['element']['url'], $link_options);
 }
 
 /**
@@ -1329,8 +1498,8 @@ function theme_link_formatter_link_separate($vars) {
   unset($link_options['url']);
   $title = empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']);
 
-  // @TODO static html markup looks not very elegant
-  // needs smarter output solution and an optional title/url seperator
+  // @todo Static html markup looks not very elegant, needs smarter output
+  // solution and an optional title/URL seperator.
   $url_parts = _link_parse_url($vars['element']['url']);
   $output = '';
   $output .= '<div class="link-item ' . $class . '">';
@@ -1344,11 +1513,10 @@ function theme_link_formatter_link_separate($vars) {
 
 /**
  * Implements hook_token_list().
- *
- * @TODO: hook_token_list no longer exists - this should change to
- *   hook_token_info().
  */
 function link_token_list($type = 'all') {
+  // @todo hook_token_list() no longer exists, this should be rewritten as
+  // hook_token_info().
   if ($type === 'field' || $type === 'all') {
     $tokens = array();
     $tokens['link']['url'] = t("Link URL");
@@ -1360,11 +1528,10 @@ function link_token_list($type = 'all') {
 
 /**
  * Implements hook_token_values().
- *
- * @TODO: hook_token_values no longer exists - this should change to
- *   hook_tokens().
  */
 function link_token_values($type, $object = NULL) {
+  // @todo hook_token_values() no longer exists, this should be rewritten as
+  // hook_tokens().
   if ($type === 'field') {
     $item = $object[0];
 
@@ -1398,6 +1565,12 @@ function link_views_api() {
  *   The protocol to be prepended to the url if one is not specified.
  */
 function link_cleanup_url($url, $protocol = 'http') {
+  try {
+    link_ensure_valid_default_protocol();
+  }
+  catch (Exception $e) {
+    watchdog('link', $e->getMessage(), array(), WATCHDOG_ERROR);
+  }
   $url = trim($url);
   $type = link_url_type($url);
 
@@ -1418,6 +1591,23 @@ function link_cleanup_url($url, $protocol = 'http') {
   return $url;
 }
 
+/**
+ * Validate that the protocol is either HTTP or HTTPS.
+ *
+ * @throws Exception
+ */
+function link_ensure_valid_default_protocol() {
+  $protocol = variable_get('link_default_protocol', LINK_HTTP_PROTOCOL);
+  if ($protocol !== LINK_HTTP_PROTOCOL && $protocol !== LINK_HTTPS_PROTOCOL) {
+    variable_set('link_default_protocol', LINK_HTTP_PROTOCOL);
+    throw new Exception(t('Protocol was set to !protocol but must be !HTTP or !HTTPS. Set to default HTTP.', array(
+      '!protocol' => $protocol,
+      '!HTTP' => LINK_HTTP_PROTOCOL,
+      '!HTTPS' => LINK_HTTPS_PROTOCOL,
+    )));
+  }
+}
+
 /**
  * Validates a URL.
  *
@@ -1430,7 +1620,6 @@ function link_cleanup_url($url, $protocol = 'http') {
  *   True if a valid link, FALSE otherwise.
  */
 function link_validate_url($text, $langcode = NULL) {
-
   $text = _link_clean_relative($text);
   $text = link_cleanup_url($text);
   $type = link_url_type($text);
@@ -1499,7 +1688,7 @@ function _link_clean_relative($url) {
  *   the LINK_(linktype) constants.
  */
 function link_url_type($text) {
-  // @TODO Complete letters.
+  // @todo Complete letters.
   // @codingStandardsIgnoreStart
   $link_ichars_domain = (string) html_entity_decode(implode("", array(
     "&#x00BF;", // ¿
@@ -1574,7 +1763,7 @@ function link_url_type($text) {
   // @codingStandardsIgnoreEnd
 
   $link_ichars = $link_ichars_domain . (string) html_entity_decode(implode("", array(
-      // ß.
+    // ß.
     "&#x00DF;",
   )), ENT_QUOTES, 'UTF-8');
   $allowed_protocols = variable_get('filter_allowed_protocols', array(
@@ -1598,7 +1787,7 @@ function link_url_type($text) {
   // captured.
   $protocol = '((?:' . implode("|", $allowed_protocols) . '):\/\/)';
   $authentication = "(?:(?:(?:[\w\.\-\+!$&'\(\)*\+,;=" . $link_ichars . "]|%[0-9a-f]{2})+(?::(?:[\w" . $link_ichars . "\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})*)?)?@)";
-  $domain = '(?:(?:[a-z0-9' . $link_ichars_domain . ']([a-z0-9' . $link_ichars_domain . '\-_\[\]])*)(\.(([a-z0-9' . $link_ichars_domain . '\-_\[\]])+\.)*(' . $link_domains . '|[a-z]{2}))?)';
+  $domain = '(?:(?:[a-zA-Z0-9' . $link_ichars_domain . ']([a-zA-Z0-9' . $link_ichars_domain . '\-_\[\]])*)(\.(([a-zA-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}))';
@@ -1805,6 +1994,7 @@ function link_field_item_property_info() {
     'type' => 'text',
     'label' => t('The URL of the link.'),
     'setter callback' => 'entity_property_verbatim_set',
+    'getter callback' => 'link_url_property_get',
   );
   $properties['attributes'] = array(
     'type' => 'struct',
@@ -1820,6 +2010,17 @@ function link_field_item_property_info() {
   return $properties;
 }
 
+/**
+ * Callback for getting the URL property.
+ *
+ * @see entity_metadata_entity_get_properties()
+ */
+function link_url_property_get($data, array $options, $name, $type, $info) {
+  $url = entity_property_verbatim_get($data, $options, $name, $type, $info);
+
+  return url($url, $data + $options);
+}
+
 /**
  * Entity property info getter callback for link attributes.
  */
diff --git a/drupal7/sites/all/modules/link/tests/link.test b/drupal7/sites/all/modules/link/tests/LinkBaseTestClass.test
similarity index 85%
rename from drupal7/sites/all/modules/link/tests/link.test
rename to drupal7/sites/all/modules/link/tests/LinkBaseTestClass.test
index 6c963679..2b7409bb 100644
--- a/drupal7/sites/all/modules/link/tests/link.test
+++ b/drupal7/sites/all/modules/link/tests/LinkBaseTestClass.test
@@ -2,14 +2,19 @@
 
 /**
  * @file
- * Link base test file - contains common functions for testing links.
+ * Base test class with helper methods, etc.
  */
 
 /**
- * Base Test Class.
+ * Base test class with helper methods, etc.
  */
 class LinkBaseTestClass extends DrupalWebTestCase {
 
+  /**
+   * Default permissions used on tests.
+   *
+   * @var array
+   */
   protected $permissions = array(
     'access content',
     'administer content types',
@@ -26,8 +31,12 @@ class LinkBaseTestClass extends DrupalWebTestCase {
    * {@inheritdoc}
    */
   public function setUp(array $modules = array()) {
+    // Most of the tests require the field UI to create & modify fields.
     $modules[] = 'field_ui';
+
+    // This module.
     $modules[] = 'link';
+
     parent::setUp($modules);
 
     $this->web_user = $this->drupalCreateUser($this->permissions);
diff --git a/drupal7/sites/all/modules/link/tests/LinkConvertInternalPathsTest.test b/drupal7/sites/all/modules/link/tests/LinkConvertInternalPathsTest.test
new file mode 100644
index 00000000..7031b202
--- /dev/null
+++ b/drupal7/sites/all/modules/link/tests/LinkConvertInternalPathsTest.test
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Confirm path aliases are saved internally as the system path.
+ */
+
+/**
+ * Confirm path aliases are saved internally as the system path.
+ */
+class LinkConvertInternalPathsTest extends LinkBaseTestClass {
+
+  /**
+   * Description of the tests.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Conversion of internal path aliases',
+      'description' => 'Test that internal path aliases are saved as system paths.',
+      'group' => 'Link',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp(array $modules = array()) {
+    $modules[] = 'path';
+    parent::setUp($modules);
+  }
+
+  /**
+   * Test the alias handling.
+   */
+  public function testInternalPathConversion() {
+    // Create 2 fields, one which converts aliases and one which doesn't.
+    $settings = array(
+      'instance[settings][convert_aliases]' => TRUE,
+    );
+    $field_name_converts = $this->createLinkField('page', $settings);
+    $field_name_plain = $this->createLinkField('page');
+
+    // Programatically create a node with an alias to link to.
+    $aliased_node = (object) array(
+      'type' => 'page',
+      'uid' => 1,
+      'title' => $this->randomName(),
+      'path' => array(
+        'alias' => $this->randomName(),
+      ),
+      // This is needed for path alias to be saved.
+      'language' => LANGUAGE_NONE,
+    );
+    node_save($aliased_node);
+
+    $this->drupalGet($aliased_node->path['alias']);
+    $this->assertText($aliased_node->title, 'Aliased node created.');
+
+    $this->drupalGet('node/add/page');
+
+    $label = $this->randomName();
+    $edit = array(
+      'title' => $label,
+      $field_name_converts . '[und][0][title]' => $label,
+      $field_name_converts . '[und][0][url]' => $aliased_node->path['alias'],
+      $field_name_plain . '[und][0][title]' => $label,
+      $field_name_plain . '[und][0][url]' => $aliased_node->path['alias'],
+    );
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertRaw(' has been created.', 'Node created');
+
+    // Load the node that was created.
+    $url = $this->getUrl();
+    $split = explode('/', $url);
+    $nid = array_pop($split);
+
+    $node = node_load($nid);
+
+    $link_field_converts_items = field_get_items('node', $node, $field_name_converts);
+    $this->assertEqual($link_field_converts_items[0]['url'], "node/{$aliased_node->nid}", "The field value was saved as the internal path for the alias.");
+
+    $link_field_plain_items = field_get_items('node', $node, $field_name_plain);
+    $this->assertEqual($link_field_plain_items[0]['url'], $aliased_node->path['alias'], "The field value was saved as the given alias.");
+  }
+
+}
diff --git a/drupal7/sites/all/modules/link/tests/LinkDefaultProtocolTest.test b/drupal7/sites/all/modules/link/tests/LinkDefaultProtocolTest.test
new file mode 100644
index 00000000..661872fa
--- /dev/null
+++ b/drupal7/sites/all/modules/link/tests/LinkDefaultProtocolTest.test
@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * Test setting a global protocol, and using it for links when needed.
+ *
+ * Ensure that any links that are created not using an explict protocol (e.g.
+ * a link entered as www.drupal.org, without a http:// or https:// prefix) will
+ * use the globally defined protocol.
+ *
+ * Any links created with an explicit protocol will continue to use that
+ * protocol and will ignore the global setting.
+ */
+class LinkDefaultProtocolTest extends LinkBaseTestClass {
+
+  /**
+   * Get Info.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Set default link protocol',
+      'description' => 'Test that an url with a default protocol set.',
+      'group' => 'Link',
+    );
+  }
+
+  /**
+   * A link without a default protocol uses the default (HTTP).
+   */
+  public function testHttpDefaultProtocolOnUrlWithoutDefinedProtocol() {
+    $user = $this->drupalCreateUser($this->permissions);
+    $this->drupalLogin($user);
+
+    $this->createNodeWithLink('www.example.com');
+
+    $this->drupalGet('node/1');
+
+    $this->assertRaw('http://www.example.com');
+  }
+
+  /**
+   * The protocol is set to HTTP by default if a wrong protocol has been set.
+   */
+  public function testHttpDefaultProtocolFromVariableConf() {
+    variable_set('link_default_protocol', 'banana');
+
+    try {
+      link_ensure_valid_default_protocol();
+    }
+    catch (Exception $e) {
+    }
+
+    $protocol = variable_get('link_default_protocol');
+
+    $this->assertEqual($protocol, LINK_HTTP_PROTOCOL);
+  }
+
+  /**
+   * A link with no defined protocol in the URL uses the default (HTTPS).
+   */
+  public function testHttpsDefaultProtocolOnUrlWithoutDefinedProtocol() {
+    $this->setupDefaultProtocol(LINK_HTTPS_PROTOCOL);
+
+    $this->createNodeWithLink('www.example.com');
+
+    $this->drupalGet('node/1');
+
+    $this->assertRaw('https://www.example.com');
+  }
+
+  /**
+   * A link with a defined protocol HTTPS overrides the default HTTP protocol.
+   */
+  public function testHttpDefaultProtocolOnUrlWithDefinedHttpsProtocol() {
+    $this->setupDefaultProtocol(LINK_HTTP_PROTOCOL);
+
+    $this->createNodeWithLink('https://www.example.com');
+
+    $this->drupalGet('node/1');
+
+    $this->assertRaw('https://www.example.com');
+  }
+
+  /**
+   * A link with a defined protocol HTTP overrides the default HTTPS protocol.
+   */
+  public function testHttpsDefaultProtocolOnUrlWithDefinedHttpProtocol() {
+    $this->setupDefaultProtocol(LINK_HTTPS_PROTOCOL);
+
+    $this->createNodeWithLink('http://www.example.com');
+
+    $this->drupalGet('node/1');
+
+    $this->assertRaw('http://www.example.com');
+  }
+
+  /**
+   * Setup the default global link protocol to either HTTP or HTTPS.
+   *
+   * @param string $protocol
+   *   The protocol to use.
+   */
+  private function setupDefaultProtocol($protocol) {
+    $user = $this->drupalCreateUser($this->permissions);
+    $this->drupalLogin($user);
+
+    $this->drupalPost(
+      'admin/config/content/link',
+      array('link_default_protocol' => $protocol),
+      'Save configuration'
+    );
+  }
+
+  /**
+   * Create a page node with a link field.
+   *
+   * @param string $url
+   *   The link URL.
+   *
+   * @return object
+   *   The created node.
+   */
+  private function createNodeWithLink($url) {
+    $link_field = $this->createLinkField('page');
+
+    $settings = array(
+      'title' => 'Basic page link test',
+      $link_field => array(
+        LANGUAGE_NONE => array(
+          array(
+            'title' => 'Field protocol link Test',
+            'url' => $url,
+          ),
+        ),
+      ),
+    );
+
+    return $this->drupalCreateNode($settings);
+  }
+
+}
diff --git a/drupal7/sites/all/modules/link/tests/link.entity_token.test b/drupal7/sites/all/modules/link/tests/LinkEntityTokenTest.test
similarity index 98%
rename from drupal7/sites/all/modules/link/tests/link.entity_token.test
rename to drupal7/sites/all/modules/link/tests/LinkEntityTokenTest.test
index e7df239f..be7c8834 100644
--- a/drupal7/sites/all/modules/link/tests/link.entity_token.test
+++ b/drupal7/sites/all/modules/link/tests/LinkEntityTokenTest.test
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Contains simpletests making sure entity_token integration works.
+ * Testing that tokens can be used in link titles.
  */
 
 /**
diff --git a/drupal7/sites/all/modules/link/tests/link.attribute.test b/drupal7/sites/all/modules/link/tests/LinkFieldAttributesTest.test
similarity index 96%
rename from drupal7/sites/all/modules/link/tests/link.attribute.test
rename to drupal7/sites/all/modules/link/tests/LinkFieldAttributesTest.test
index 11cae3cb..b95fbebb 100644
--- a/drupal7/sites/all/modules/link/tests/link.attribute.test
+++ b/drupal7/sites/all/modules/link/tests/LinkFieldAttributesTest.test
@@ -2,33 +2,27 @@
 
 /**
  * @file
- * Basic simpletests to test options on link module.
+ * Field attributes test.
  */
 
 /**
- * Attribute Crud Test.
+ * Field attributes test.
  */
-class LinkAttributeCrudTest extends DrupalWebTestCase {
+class LinkFieldAttributesTest extends LinkBaseTestClass {
 
+  /**
+   * Track a zebra value, used to alternate the output.
+   *
+   * @var int
+   */
   private $zebra;
 
-  protected $permissions = array(
-    'access content',
-    'administer content types',
-    'administer nodes',
-    'administer filters',
-    'access comments',
-    'post comments',
-    'skip comment approval',
-    'access administration pages',
-  );
-
   /**
    * Get Info.
    */
   public static function getInfo() {
     return array(
-      'name' => 'Link Attribute Tests',
+      'name' => 'Link field attribute Tests',
       'description' => 'Tests the field attributes, making sure they appear in various displayed situations.',
       'group' => 'Link',
     );
@@ -38,18 +32,9 @@ class LinkAttributeCrudTest extends DrupalWebTestCase {
    * {@inheritdoc}
    */
   public function setUp(array $modules = array()) {
-    $modules[] = 'field_ui';
-    $modules[] = 'link';
     parent::setUp($modules);
 
     $this->zebra = 0;
-
-    // Create and login user.
-    $this->web_user = $this->drupalCreateUser(array(
-      'administer content types',
-      'administer fields',
-    ));
-    $this->drupalLogin($this->web_user);
   }
 
   /**
diff --git a/drupal7/sites/all/modules/link/tests/link.crud_browser.test b/drupal7/sites/all/modules/link/tests/LinkFieldCrudTest.test
similarity index 87%
rename from drupal7/sites/all/modules/link/tests/link.crud_browser.test
rename to drupal7/sites/all/modules/link/tests/LinkFieldCrudTest.test
index b5063d1a..876dd2ef 100644
--- a/drupal7/sites/all/modules/link/tests/link.crud_browser.test
+++ b/drupal7/sites/all/modules/link/tests/LinkFieldCrudTest.test
@@ -2,13 +2,13 @@
 
 /**
  * @file
- * Testing CRUD API in the browser.
+ * Testing that users can not input bad URLs or labels.
  */
 
 /**
  * Testing that users can not input bad URLs or labels.
  */
-class LinkUiTest extends DrupalWebTestcase {
+class LinkFieldCrudTest extends LinkBaseTestClass {
 
   /**
    * Link supposed to be good.
@@ -98,37 +98,43 @@ class LinkUiTest extends DrupalWebTestcase {
       array(
         'href' => 'http://example.com/' . $this->randomName(),
         'label' => $this->randomName() . '<script>alert("hi");</script>',
-        'msg' => 'js label',
+        'msg' => 'JS in label',
         'type' => self::LINK_INPUT_TYPE_BAD_TITLE,
       ),
       array(
         'href' => 'http://example.com/' . $this->randomName(),
         'label' => $this->randomName() . '<script src="http://devil.site.com"></script>',
-        'msg' => 'js label',
+        'msg' => 'Remote JS in label',
         'type' => self::LINK_INPUT_TYPE_BAD_TITLE,
       ),
       array(
         'href' => 'http://example.com/' . $this->randomName(),
         'label' => $this->randomName() . '" onmouseover="alert(\'hi\')',
-        'msg' => 'js label',
+        'msg' => 'JS in label',
         'type' => self::LINK_INPUT_TYPE_BAD_TITLE,
       ),
       array(
         'href' => 'http://example.com/' . $this->randomName(),
         'label' => $this->randomName() . '\' onmouseover="alert(\'hi\')',
-        'msg' => 'js label',
+        'msg' => 'Escaped JS in label',
         'type' => self::LINK_INPUT_TYPE_BAD_TITLE,
       ),
       array(
         'href' => 'javascript:alert("http://example.com/' . $this->randomName() . '")',
         'label' => $this->randomName(),
-        'msg' => 'js url',
+        'msg' => 'JS in URL',
         'type' => self::LINK_INPUT_TYPE_BAD_URL,
       ),
+      array(
+        'href' => 'http://example.com?foo=bar&fruit=mango',
+        'label' => $this->randomName(),
+        'msg' => 'URL with querystring',
+        'type' => self::LINK_INPUT_TYPE_GOOD,
+      ),
       array(
         'href' => 'http://ecs-es.kelkoo.es/ctl/go/sitesearchGo?.ts=1338833010331&.sig=qP9GXeEFH6syBzwmzYkxmsvp1EI-',
         'label' => 'http://ecs-es.kelkoo.es/ctl/go/sitesearchGo?.ts=1338833010331&.sig=qP9GXeEFH6syBzwmzYkxmsvp1EI-',
-        'msg' => 'Url with . in querystring',
+        'msg' => 'URL with . in querystring',
         'type' => self::LINK_INPUT_TYPE_GOOD,
       ),
     );
@@ -173,13 +179,11 @@ class LinkUiTest extends DrupalWebTestcase {
       // If simpletest starts using something to override the error system, this
       // will flag us and let us know it's broken.
       $this->assertFalse(libxml_use_internal_errors(TRUE));
+      $expected_href = $input['href'];
       if (isset($input['expected_href'])) {
-        $path = '//a[@href="' . $input['expected_href'] . '" and text()="' . $input['label'] . '"]';
+        $expected_href = $input['expected_href'];
       }
-      else {
-        $path = '//a[@href="' . $input['href'] . '" and text()="' . $input['label'] . '"]';
-      }
-      $elements = $this->xpath($path);
+      $elements = $this->xpath('//a[@href="' . $expected_href . '" and text()="' . $input['label'] . '"]');
       libxml_use_internal_errors(FALSE);
       $this->assertIdentical(isset($elements[0]), $input['type'] == self::LINK_INPUT_TYPE_GOOD, $input['msg']);
     }
@@ -508,4 +512,61 @@ class LinkUiTest extends DrupalWebTestcase {
     $this->assertPattern('|class\s?=\s?"' . $link_class_name . '"|', "Classes $link_class_name exist on page.");
   }
 
+  /**
+   * Tests that link titles properly preserve the query params.
+   */
+  public function testLinkTitlePreservesQueryParams() {
+    // Create field.
+    $field_name = $this->createLinkField();
+
+    // Create page form.
+    $this->drupalGet('node/add/page');
+    $this->assertField($field_name . '[und][0][title]', 'Title found');
+    $this->assertField($field_name . '[und][0][url]', 'URL found');
+
+    $link_title_tests = array(
+      array(
+        'href' => 'http://example.com/' . $this->randomName(),
+      ),
+      array(
+        'href' => 'http://example.com/' . $this->randomName() . '?property=value',
+      ),
+      array(
+        'href' => 'http://example.com/' . $this->randomName() . '#position',
+      ),
+      array(
+        'href' => 'http://example.com/' . $this->randomName() . '#lower?property=value2',
+      ),
+      array(
+        'href' => 'http://example.com/' . $this->randomName() . '?property=value2#lower',
+      ),
+    );
+
+    foreach ($link_title_tests as &$input) {
+      $this->drupalGet('node/add/page');
+      $this->assertResponse(200);
+
+      // Intentionally set empty title, to force using the url as the title.
+      $edit = array(
+        'title' => 'Some node title',
+        $field_name . '[und][0][title]' => '',
+        $field_name . '[und][0][url]' => $input['href'],
+      );
+      $this->drupalPost(NULL, $edit, t('Save'));
+      // Confirm the node saved correctly.
+      $this->assertResponse(200);
+      $this->assertText($edit['title']);
+      $input['url'] = $this->getUrl();
+    }
+
+    // Change to anonymous user.
+    $this->drupalLogout();
+
+    // Loop over the nodes, confirm each URL is rendered correctly.
+    foreach ($link_title_tests as $input) {
+      $this->drupalGet($input['url']);
+      $this->assertRaw(l($input['href'], $input['href']), "Test that the link title has been set to " . $input['href']);
+    }
+  }
+
 }
diff --git a/drupal7/sites/all/modules/link/tests/link.validate.test b/drupal7/sites/all/modules/link/tests/LinkFieldValidateTest.test
similarity index 60%
rename from drupal7/sites/all/modules/link/tests/link.validate.test
rename to drupal7/sites/all/modules/link/tests/LinkFieldValidateTest.test
index 1694b496..adea034a 100644
--- a/drupal7/sites/all/modules/link/tests/link.validate.test
+++ b/drupal7/sites/all/modules/link/tests/LinkFieldValidateTest.test
@@ -2,13 +2,24 @@
 
 /**
  * @file
- * Tests that exercise the validation functions in the link module.
+ * Link field Validation Tests.
  */
 
 /**
- * Validate Test Case.
+ * Link field Validation Tests.
  */
-class LinkValidateTestCase extends LinkBaseTestClass {
+class LinkFieldValidateTest extends LinkBaseTestClass {
+
+  /**
+   * Get Info.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Link field validation tests',
+      'description' => 'Tests the field validation.',
+      'group' => 'Link',
+    );
+  }
 
   /**
    * Create Link.
@@ -47,24 +58,6 @@ class LinkValidateTestCase extends LinkBaseTestClass {
     $this->assertEqual($url, $node->{$field_name}[LANGUAGE_NONE][0]['url']);
   }
 
-}
-
-/**
- * Class for Validate Test.
- */
-class LinkValidateTest extends LinkValidateTestCase {
-
-  /**
-   * Get Info.
-   */
-  public static function getInfo() {
-    return array(
-      'name' => 'Link Validation Tests',
-      'description' => 'Tests the field validation.',
-      'group' => 'Link',
-    );
-  }
-
   /**
    * Validate basic URL.
    */
@@ -233,6 +226,8 @@ class LinkValidateTest extends LinkValidateTestCase {
    *
    * Test if a bad url can sneak through un-filtered if we play with the
    * validation...
+   *
+   * @todo Enable this, fix any problems that come up.
    */
   public function xTestLinkValidateSwitchingBetweenValidationStatus() {
     $this->acquireContentTypes(1);
@@ -344,24 +339,6 @@ class LinkValidateTest extends LinkValidateTestCase {
     $this->linkTestValidateUrl('ftp://www.example.com/');
   }
 
-}
-
-/**
- * Validate Test News.
- */
-class LinkValidateTestNews extends LinkValidateTestCase {
-
-  /**
-   * Get Info.
-   */
-  public static function getInfo() {
-    return array(
-      'name' => 'Link News Validation Tests',
-      'description' => 'Tests the field validation for usenet urls.',
-      'group' => 'Link',
-    );
-  }
-
   /**
    * Validate a news link to a message group.
    */
@@ -376,24 +353,6 @@ class LinkValidateTestNews extends LinkValidateTestCase {
     $this->linkTestValidateUrl('news:hj0db8$vrm$1@news.eternal-september.org');
   }
 
-}
-
-/**
- * Validate Specific URL.
- */
-class LinkValidateSpecificURL extends LinkValidateTestCase {
-
-  /**
-   * Get Info.
-   */
-  public static function getInfo() {
-    return array(
-      'name' => 'Link Specific URL Validation Tests',
-      'description' => 'Tests field validation with unusual urls',
-      'group' => 'Link',
-    );
-  }
-
   /**
    * Lets throw in a lot of umlouts for testing!
    */
@@ -440,260 +399,3 @@ class LinkValidateSpecificURL extends LinkValidateTestCase {
   }
 
 }
-
-/**
- * Validate Url Light.
- *
- * A series of tests of links, only going against the link_validate_url function
- * in link.module.
- *
- * Validation is guided by the rules in http://tools.ietf.org/html/rfc1738 !
- */
-class LinkValidateUrlLight extends DrupalWebTestCase {
-
-  /**
-   * Get Info.
-   */
-  public static function getInfo() {
-    return array(
-      'name' => 'Link Light Validation Tests',
-      'description' => 'Tests the link_validate_url() function by itself, without invoking the full drupal/cck lifecycle.',
-      'group' => 'Link',
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setUp(array $modules = array()) {
-    $modules[] = 'link';
-    parent::setUp($modules);
-  }
-
-  /**
-   * Name Link Type.
-   *
-   * Translates the LINK type constants to english for display and debugging of
-   * tests.
-   *
-   * @codingStandardsIgnoreStart
-   */
-  public function name_Link_Type($type) {
-    // @codingStandardsIgnoreEnd
-    switch ($type) {
-      case LINK_FRONT:
-        return "Front";
-
-      case LINK_EMAIL:
-        return "Email";
-
-      case LINK_TEL:
-        return "Telephone";
-
-      case LINK_NEWS:
-        return "Newsgroup";
-
-      case LINK_INTERNAL:
-        return "Internal Link";
-
-      case LINK_EXTERNAL:
-        return "External Link";
-
-      case FALSE:
-        return "Invalid Link";
-
-      default:
-        return "Bad Value:" . $type;
-    }
-  }
-
-  /**
-   * Make sure that a link labeled <front> works.
-   */
-  public function testValidateFrontLink() {
-    $valid = link_validate_url('<front>');
-    $this->assertEqual(LINK_FRONT, $valid, 'Make sure that front link is verified and identified');
-  }
-
-  /**
-   * Validate Email Link.
-   */
-  public function testValidateEmailLink() {
-    $valid = link_validate_url('mailto:bob@example.com');
-    $this->assertEqual(LINK_EMAIL, $valid, "Make sure a basic mailto is verified and identified");
-  }
-
-  /**
-   * Validate Email Link Bad.
-   */
-  public function testValidateEmailLinkBad() {
-    $valid = link_validate_url(':bob@example.com');
-    $this->assertEqual(FALSE, $valid, 'Make sure just a bad address is correctly failed');
-  }
-
-  /**
-   * Confirm that valid tel: links work as expected.
-   */
-  public function testValidateTelLinks() {
-    $links = array(
-      'tel:01',
-      'tel:123456789012345',
-      'tel:+123456789012345',
-    );
-    foreach ($links as $link) {
-      $type = link_url_type($link);
-      $this->assertEqual(LINK_TEL, $type, 'Test ' . $link . ' is a tel link.');
-      $valid = link_validate_url($link);
-      $this->assertTrue($valid, 'Test ' . $link . ' is valid tel link.');
-    }
-  }
-
-  /**
-   * Confirm that invalid tel: links work as expected.
-   */
-  public function testValidateTelLinksBad() {
-    $links = array(
-      'tel:0',
-      'tel:1234567890123456',
-      'tel:+1',
-      'tel:+0123456789',
-      'tel:+1234567890123456',
-      ':12345678',
-    );
-    foreach ($links as $link) {
-      $type = link_url_type($link);
-      $this->assertFalse($type, 'Test ' . $link . ' is not a tel link.');
-      $valid = link_validate_url($link);
-      $this->assertFalse($valid, 'Test ' . $link . ' is not a valid tel link.');
-    }
-  }
-
-  /**
-   * Validate Newsgroup Link.
-   */
-  public function testValidateNewsgroupLink() {
-    $valid = link_validate_url('news:comp.infosystems.www.misc');
-    $this->assertEqual(LINK_NEWS, $valid, 'Make sure link to newsgroup validates as news.');
-  }
-
-  /**
-   * Validate News Article Link.
-   */
-  public function testValidateNewsArticleLink() {
-    $valid = link_validate_url('news:hj0db8$vrm$1@news.eternal-september.org');
-    $this->assertEqual(LINK_NEWS, $valid, 'Make sure link to specific article validates as news.');
-  }
-
-  /**
-   * Validate Bad Newsgroup Link.
-   */
-  public function testValidateBadNewsgroupLink() {
-    $valid = link_validate_url('news:comp.bad_name.misc');
-    $this->assertEqual(FALSE, $valid, 'newsgroup names can\'t contain underscores, so it should come back as invalid.');
-  }
-
-  /**
-   * Validate Internal Links.
-   */
-  public function testValidateInternalLinks() {
-    $tempfile = drupal_tempnam('public://files', 'test');
-    $links = array(
-      'rss.xml',
-      'foo#bar',
-      file_uri_target($tempfile),
-      drupal_realpath($tempfile),
-    );
-
-    foreach ($links as $link) {
-      $type = link_url_type($link);
-      $this->assertEqual(LINK_INTERNAL, $type, 'Test ' . $link . ' is an internal link.');
-      $valid = link_validate_url($link);
-      $this->assertTrue($valid, 'Test ' . $link . ' is valid internal link.');
-    }
-  }
-
-  /**
-   * Validate External Links.
-   */
-  public function testValidateExternalLinks() {
-    $links = array(
-      'http://localhost:8080/',
-      'www.example.com',
-      'www.example.com/',
-      'http://username:p%40ssw0rd!@www.example.com/',
-      'http://@www.example.com/',
-      'http://username:@www.example.com/',
-      'http://username:password@www.example.com:8080/',
-      'http://127.0.0.1:80/',
-      'http://127.173.24.255:4723/',
-      '127.173.24.255:4723/',
-      'http://255.255.255.255:4823/',
-      'www.test-site.com',
-      'http://example.com/index.php?q=node/123',
-      'http://example.com/?first_name=Joe Bob&last_name=Smith',
-      // Anchors.
-      'http://www.example.com/index.php#test',
-      'http://www.example.com/index.php#this@that.',
-      'http://www.example.com/index.php#',
-      'http://www.cnn.com/video/#/video/politics/2008/12/09/intv.madeleine.albright.cnn',
-      'http://www.archive.org/stream/aesopsfables00aesorich#page/n7/mode/2up',
-      'http://www.example.com/blah/#this@that?',
-    );
-
-    // Test all of the protocols.
-    $allowed_protocols = variable_get('filter_allowed_protocols', array(
-      'http',
-      'https',
-      'ftp',
-      'news',
-      'nntp',
-      'telnet',
-      'mailto',
-      'irc',
-      'ssh',
-      'sftp',
-      'webcal',
-    ));
-
-    foreach ($allowed_protocols as $protocol) {
-      if ($protocol !== 'news' && $protocol !== 'mailto') {
-        $links[] = $protocol . '://www.example.com';
-      }
-    }
-    foreach ($links as $link) {
-      $type = link_url_type($link);
-      $this->assertEqual(LINK_EXTERNAL, $type, 'Testing that ' . $link . ' is an external link.');
-      $valid = link_validate_url($link);
-      $this->assertTrue($valid, 'Test ' . $link . ' is valid external link.');
-      // The following two lines are commented out and only used for
-      // comparisons.
-      // $valid2 = valid_url($link, TRUE);
-      // $this->assertEqual(TRUE, $valid2, "Using valid_url() on $link.");.
-    }
-  }
-
-  /**
-   * Check Invalid External Links.
-   */
-  public function testInvalidExternalLinks() {
-    $links = array(
-      'http://www.ex ample.com/',
-      // Bad ip!
-      'http://25.0.0/',
-      'http://4827.0.0.2/',
-      // ß not allowed in domain names!
-      'http://www.testß.com/',
-      // Bad TLD.
-      'http://.www.foo.bar./',
-      // Domains can't have sections starting with a dash.
-      // 'http://www.-fudge.com/',
-      'http://example.com/index.php?page=this\that',
-      'example@example.com',
-    );
-    foreach ($links as $link) {
-      $valid = link_validate_url($link);
-      $this->assertEqual(FALSE, $valid, 'Testing that ' . $link . ' is not a valid link.');
-    }
-  }
-
-}
diff --git a/drupal7/sites/all/modules/link/tests/link.multilingual.test b/drupal7/sites/all/modules/link/tests/LinkPathPrefixesTest.test
similarity index 98%
rename from drupal7/sites/all/modules/link/tests/link.multilingual.test
rename to drupal7/sites/all/modules/link/tests/LinkPathPrefixesTest.test
index 2aeec79c..e02efaf5 100644
--- a/drupal7/sites/all/modules/link/tests/link.multilingual.test
+++ b/drupal7/sites/all/modules/link/tests/LinkPathPrefixesTest.test
@@ -8,7 +8,18 @@
 /**
  * Testing that absolute / relative outputs match the expected format.
  */
-class LinkMultilingualTestCase extends LinkBaseTestClass {
+class LinkPathPrefixesTest extends LinkBaseTestClass {
+
+  /**
+   *
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Link language path prefix',
+      'description' => 'Tests that path properly work with language path prefixes.',
+      'group' => 'Link',
+    );
+  }
 
   /**
    * {@inheritdoc}
@@ -64,23 +75,6 @@ class LinkMultilingualTestCase extends LinkBaseTestClass {
     drupal_static_reset('locale_language_url_rewrite_url');
   }
 
-}
-/**
- *
- */
-class LinkMultilingualPathTest extends LinkMultilingualTestCase {
-
-  /**
-   *
-   */
-  public static function getInfo() {
-    return array(
-      'name' => 'Link language path prefix',
-      'description' => 'Tests that path properly work with language path prefixes.',
-      'group' => 'Link',
-    );
-  }
-
   /**
    * Creates a link field, fills it, then uses a loaded node to test paths.
    */
diff --git a/drupal7/sites/all/modules/link/tests/LinkSanitizeTest.test b/drupal7/sites/all/modules/link/tests/LinkSanitizeTest.test
new file mode 100644
index 00000000..fb6c73a2
--- /dev/null
+++ b/drupal7/sites/all/modules/link/tests/LinkSanitizeTest.test
@@ -0,0 +1,178 @@
+<?php
+
+/**
+ * Test for _link_sanitize().
+ *
+ * @todo Rewrite as a unit test once _link_sanitize() is refactored.
+ */
+class LinkSanitizeTest extends LinkBaseTestClass {
+
+  /**
+   * Backup of $_GET['q'] to be able to revert it to its original state.
+   *
+   * @var string
+   */
+  protected $q;
+
+  /**
+   * Get Info.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Link Sanitation Tests',
+      'description' => 'Tests the _link_sanitize() function by itself, without invoking the field life cycle.',
+      'group' => 'Link',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp(array $modules = array()) {
+    $modules[] = 'node';
+    parent::setUp($modules);
+    $this->q = $_GET['q'];
+    $_GET['q'] = 'test-path';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function tearDown(array $modules = array()) {
+    $_GET['q'] = $this->q;
+    parent::tearDown();
+  }
+
+  /**
+   * Generate parameters for testing _link_sanitize().
+   */
+  protected function generateParams($settings = array()) {
+    $field = array();
+    $instance['entity_type'] = 'node';
+    $defaults['attributes'] = array();
+    $defaults['display']['url_cutoff'] = 20;
+    $defaults['title'] = 'Test title';
+    $settings += $defaults;
+    $settings['display'] += $defaults['display'];
+    $instance['settings'] = $settings;
+    $entity = NULL;
+    return array($field, $instance, $entity);
+  }
+
+  /**
+   * Test that relative hash URLs are returned as is.
+   */
+  public function testRelativeHash() {
+    $item['url'] = '#hash-only';
+    list($field, $instance, $entity) = $this->generateParams();
+    _link_sanitize($item, NULL, $field, $instance, $entity);
+    $this->assertEqual('#hash-only', $item['url']);
+  }
+
+  /**
+   * Test that relative hash URLs can be turned into absolute URLs.
+   */
+  public function testAbsoluteHash() {
+    $item['url'] = '#hash-only';
+    list($field, $instance, $entity) = $this->generateParams(array(
+      'absolute_url' => TRUE,
+    ));
+    _link_sanitize($item, NULL, $field, $instance, $entity);
+    $this->assertEqual('test-path', $item['url']);
+    $this->assertEqual(NULL, $item['query']);
+    $this->assertEqual('hash-only', $item['fragment']);
+  }
+
+  /**
+   * Test that relative query URLs are returned as is.
+   */
+  public function testRelativeQuery() {
+    $item['url'] = '?query=only';
+    list($field, $instance, $entity) = $this->generateParams();
+    _link_sanitize($item, NULL, $field, $instance, $entity);
+    $this->assertEqual('?query=only', $item['url']);
+  }
+
+  /**
+   * Test that query URLs can be turned into absolute URLs.
+   */
+  public function testAbsoluteQuery() {
+    $item['url'] = '?query=only';
+    list($field, $instance, $entity) = $this->generateParams(array(
+      'absolute_url' => TRUE,
+    ));
+    _link_sanitize($item, NULL, $field, $instance, $entity);
+    $this->assertEqual('test-path', $item['url']);
+    $this->assertEqual(array('query' => 'only'), $item['query']);
+    $this->assertEqual(NULL, $item['fragment']);
+  }
+
+  /**
+   * Test that query URLs can be turned into absolute URLs.
+   */
+  public function testBlankTitle() {
+    $item['title'] = '';
+    $item['url'] = 'https://www.drupal.org/';
+    list($field, $instance, $entity) = $this->generateParams();
+    // Disable the URL cutoff to confirm the URL.
+    $instance['settings']['display']['url_cutoff'] = FALSE;
+    _link_sanitize($item, NULL, $field, $instance, $entity);
+    $this->verbose('<pre>' . print_r($item, TRUE) . '</pre>');
+    $this->assertEqual('https://www.drupal.org/', $item['display_url']);
+    $this->assertEqual('https://www.drupal.org/', $item['url']);
+    $this->assertEqual(array(), $item['query']);
+    $this->assertEqual(NULL, $item['fragment']);
+  }
+
+  /**
+   * Test that query URLs can be turned into absolute URLs.
+   */
+  public function testBlankTitleWithQuery() {
+    $item['title'] = '';
+    $item['url'] = 'https://www.drupal.org/?page=42';
+    list($field, $instance, $entity) = $this->generateParams();
+    // Disable the URL cutoff to confirm the URL.
+    $instance['settings']['display']['url_cutoff'] = FALSE;
+    _link_sanitize($item, NULL, $field, $instance, $entity);
+    $this->verbose('<pre>' . print_r($item, TRUE) . '</pre>');
+    $this->assertEqual('https://www.drupal.org/?page=42', $item['display_url']);
+    $this->assertEqual('https://www.drupal.org/', $item['url']);
+    $this->assertEqual(array('page' => 42), $item['query']);
+    $this->assertEqual(NULL, $item['fragment']);
+  }
+
+  /**
+   * Test that query URLs can be turned into absolute URLs.
+   */
+  public function testBlankTitleWithFragment() {
+    $item['title'] = '';
+    $item['url'] = 'https://www.drupal.org/#something';
+    list($field, $instance, $entity) = $this->generateParams();
+    // Disable the URL cutoff to confirm the URL.
+    $instance['settings']['display']['url_cutoff'] = FALSE;
+    _link_sanitize($item, NULL, $field, $instance, $entity);
+    $this->verbose('<pre>' . print_r($item, TRUE) . '</pre>');
+    $this->assertEqual('https://www.drupal.org/#something', $item['display_url']);
+    $this->assertEqual('https://www.drupal.org/', $item['url']);
+    $this->assertEqual(array(), $item['query']);
+    $this->assertEqual('something', $item['fragment']);
+  }
+
+  /**
+   * Test that query URLs can be turned into absolute URLs.
+   */
+  public function testBlankTitleWithQueryAndFragment() {
+    $item['title'] = '';
+    $item['url'] = 'https://www.drupal.org/?page=42#something';
+    list($field, $instance, $entity) = $this->generateParams();
+    // Disable the URL cutoff to confirm the URL.
+    $instance['settings']['display']['url_cutoff'] = FALSE;
+    _link_sanitize($item, NULL, $field, $instance, $entity);
+    $this->verbose('<pre>' . print_r($item, TRUE) . '</pre>');
+    $this->assertEqual('https://www.drupal.org/?page=42#something', $item['display_url']);
+    $this->assertEqual('https://www.drupal.org/', $item['url']);
+    $this->assertEqual(array('page' => 42), $item['query']);
+    $this->assertEqual('something', $item['fragment']);
+  }
+
+}
diff --git a/drupal7/sites/all/modules/link/tests/link.token.test b/drupal7/sites/all/modules/link/tests/LinkTokenTest.test
similarity index 99%
rename from drupal7/sites/all/modules/link/tests/link.token.test
rename to drupal7/sites/all/modules/link/tests/LinkTokenTest.test
index f21f5721..466483cb 100644
--- a/drupal7/sites/all/modules/link/tests/link.token.test
+++ b/drupal7/sites/all/modules/link/tests/LinkTokenTest.test
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Contains simpletests making sure token integration works.
+ * Testing that tokens can be used in link titles.
  */
 
 /**
diff --git a/drupal7/sites/all/modules/link/tests/LinkUnitTestCase.test b/drupal7/sites/all/modules/link/tests/LinkUnitTestCase.test
new file mode 100644
index 00000000..d3f9435e
--- /dev/null
+++ b/drupal7/sites/all/modules/link/tests/LinkUnitTestCase.test
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Unit tests for Link module's internal APIs.
+ */
+
+/**
+ * Unit tests for Link module's internal APIs.
+ */
+class LinkUnitTestCase extends DrupalUnitTestCase {
+
+  /**
+   *
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Link Unit Tets',
+      'description' => 'Unit tests for the Link module.',
+      'group' => 'Link',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    drupal_load('module', 'link');
+    parent::setUp();
+  }
+
+  /**
+   * Test _link_parse_url().
+   */
+  public function testLinkParseUrl() {
+    // Each of the keys is the URL to check, it will then be converted into a
+    // matching array with three possible elements - 'url', 'query' and
+    // 'fragment'.
+    $urls = array(
+      'https://www.drupal.org' => array(
+        'url' => 'https://www.drupal.org',
+      ),
+      'https://www.drupal.org/?page=42' => array(
+        'url' => 'https://www.drupal.org/',
+        'query' => array(
+          'page' => 42,
+        ),
+      ),
+      'https://www.drupal.org/#footer' => array(
+        'url' => 'https://www.drupal.org/',
+        'fragment' => 'footer',
+      ),
+      'https://www.drupal.org/?page=42#footer' => array(
+        'url' => 'https://www.drupal.org/',
+        'query' => array(
+          'page' => 42,
+        ),
+        'fragment' => 'footer',
+      ),
+    );
+    foreach ($urls as $url => $expected_parts) {
+      $actual_parts = _link_parse_url($url);
+
+      // First off, compare the URL segment.
+      $this->assertEqual($expected_parts['url'], $actual_parts['url']);
+
+      // Secondly, compare the query string, if it was expected.
+      if (isset($expected_parts['query'])) {
+        $this->assertTrue(isset($actual_parts['query']));
+        $this->assertTrue(is_array($actual_parts['query']));
+        $this->assertEqual(count($expected_parts['query']), count($actual_parts['query']));
+      }
+      // If it was not expected, make sure it wasn't added anyway.
+      else {
+        $this->assertFalse(isset($actual_parts['query']));
+      }
+
+      // Lastly, compare the query fragment, if it was expected.
+      if (isset($expected_parts['fragment'])) {
+        $this->assertEqual($expected_parts['fragment'], $actual_parts['fragment']);
+      }
+      // If it was not expected, make sure it wasn't added anyway.
+      else {
+        $this->assertFalse(isset($actual_parts['fragment']));
+      }
+    }
+  }
+
+}
diff --git a/drupal7/sites/all/modules/link/tests/LinkValidationApiTest.test b/drupal7/sites/all/modules/link/tests/LinkValidationApiTest.test
new file mode 100644
index 00000000..850fab34
--- /dev/null
+++ b/drupal7/sites/all/modules/link/tests/LinkValidationApiTest.test
@@ -0,0 +1,264 @@
+<?php
+
+/**
+ * @file
+ * Validate the link_validate_url() validation API.
+ */
+
+/**
+ * Validate the link_validate_url() validation API.
+ *
+ * Validation is guided by the rules in http://tools.ietf.org/html/rfc1738.
+ */
+class LinkValidationApiTest extends LinkBaseTestClass {
+
+  /**
+   * Get Info.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Link Validation API Tests',
+      'description' => 'Tests the link_validate_url() function by itself, without invoking the full drupal/cck lifecycle.',
+      'group' => 'Link',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp(array $modules = array()) {
+    $modules[] = 'link';
+    parent::setUp($modules);
+  }
+
+  /**
+   * Name Link Type.
+   *
+   * Translates the LINK type constants to english for display and debugging of
+   * tests.
+   *
+   * @todo What is this for? Can it be removed?
+   *
+   * @codingStandardsIgnoreStart
+   */
+  public function name_Link_Type($type) {
+    // @codingStandardsIgnoreEnd
+    switch ($type) {
+      case LINK_FRONT:
+        return "Front";
+
+      case LINK_EMAIL:
+        return "Email";
+
+      case LINK_TEL:
+        return "Telephone";
+
+      case LINK_NEWS:
+        return "Newsgroup";
+
+      case LINK_INTERNAL:
+        return "Internal Link";
+
+      case LINK_EXTERNAL:
+        return "External Link";
+
+      case FALSE:
+        return "Invalid Link";
+
+      default:
+        return "Bad Value:" . $type;
+    }
+  }
+
+  /**
+   * Make sure that a link labeled <front> works.
+   */
+  public function testValidateFrontLink() {
+    $valid = link_validate_url('<front>');
+    $this->assertEqual(LINK_FRONT, $valid, 'Make sure that front link is verified and identified');
+  }
+
+  /**
+   * Validate Email Link.
+   */
+  public function testValidateEmailLink() {
+    $valid = link_validate_url('mailto:bob@example.com');
+    $this->assertEqual(LINK_EMAIL, $valid, "Make sure a basic mailto is verified and identified");
+  }
+
+  /**
+   * Validate Email Link Bad.
+   */
+  public function testValidateEmailLinkBad() {
+    $valid = link_validate_url(':bob@example.com');
+    $this->assertEqual(FALSE, $valid, 'Make sure just a bad address is correctly failed');
+  }
+
+  /**
+   * Confirm that valid tel: links work as expected.
+   */
+  public function testValidateTelLinks() {
+    $links = array(
+      'tel:01',
+      'tel:123456789012345',
+      'tel:+123456789012345',
+    );
+    foreach ($links as $link) {
+      $type = link_url_type($link);
+      $this->assertEqual(LINK_TEL, $type, 'Test ' . $link . ' is a tel link.');
+      $valid = link_validate_url($link);
+      $this->assertTrue($valid, 'Test ' . $link . ' is valid tel link.');
+    }
+  }
+
+  /**
+   * Confirm that invalid tel: links work as expected.
+   */
+  public function testValidateTelLinksBad() {
+    $links = array(
+      'tel:0',
+      'tel:1234567890123456',
+      'tel:+1',
+      'tel:+0123456789',
+      'tel:+1234567890123456',
+      ':12345678',
+    );
+    foreach ($links as $link) {
+      $type = link_url_type($link);
+      $this->assertFalse($type, 'Test ' . $link . ' is not a tel link.');
+      $valid = link_validate_url($link);
+      $this->assertFalse($valid, 'Test ' . $link . ' is not a valid tel link.');
+    }
+  }
+
+  /**
+   * Validate Newsgroup Link.
+   */
+  public function testValidateNewsgroupLink() {
+    $valid = link_validate_url('news:comp.infosystems.www.misc');
+    $this->assertEqual(LINK_NEWS, $valid, 'Make sure link to newsgroup validates as news.');
+  }
+
+  /**
+   * Validate News Article Link.
+   */
+  public function testValidateNewsArticleLink() {
+    $valid = link_validate_url('news:hj0db8$vrm$1@news.eternal-september.org');
+    $this->assertEqual(LINK_NEWS, $valid, 'Make sure link to specific article validates as news.');
+  }
+
+  /**
+   * Validate Bad Newsgroup Link.
+   */
+  public function testValidateBadNewsgroupLink() {
+    $valid = link_validate_url('news:comp.bad_name.misc');
+    $this->assertEqual(FALSE, $valid, 'newsgroup names can\'t contain underscores, so it should come back as invalid.');
+  }
+
+  /**
+   * Validate Internal Links.
+   */
+  public function testValidateInternalLinks() {
+    $tempfile = drupal_tempnam('public://files', 'test');
+    $links = array(
+      'rss.xml',
+      'foo#bar',
+      file_uri_target($tempfile),
+      drupal_realpath($tempfile),
+    );
+
+    foreach ($links as $link) {
+      $type = link_url_type($link);
+      $this->assertEqual(LINK_INTERNAL, $type, 'Test ' . $link . ' is an internal link.');
+      $valid = link_validate_url($link);
+      $this->assertTrue($valid, 'Test ' . $link . ' is valid internal link.');
+    }
+  }
+
+  /**
+   * Validate External Links.
+   */
+  public function testValidateExternalLinks() {
+    $links = array(
+      'http://localhost:8080/',
+      'www.example.com',
+      'www.example.com/',
+      'http://username:p%40ssw0rd!@www.example.com/',
+      'http://@www.example.com/',
+      'http://username:@www.example.com/',
+      'http://username:password@www.example.com:8080/',
+      'http://127.0.0.1:80/',
+      'http://127.173.24.255:4723/',
+      '127.173.24.255:4723/',
+      'http://255.255.255.255:4823/',
+      'www.test-site.com',
+      'http://example.com/index.php?q=node/123',
+      'http://example.com/?first_name=Joe Bob&last_name=Smith',
+      // Anchors.
+      'http://www.example.com/index.php#test',
+      'http://www.example.com/index.php#this@that.',
+      'http://www.example.com/index.php#',
+      'http://www.cnn.com/video/#/video/politics/2008/12/09/intv.madeleine.albright.cnn',
+      'http://www.archive.org/stream/aesopsfables00aesorich#page/n7/mode/2up',
+      'http://www.example.com/blah/#this@that?',
+    );
+
+    // Test all of the protocols.
+    $allowed_protocols = variable_get('filter_allowed_protocols', array(
+      'http',
+      'https',
+      'ftp',
+      'news',
+      'nntp',
+      'telnet',
+      'mailto',
+      'irc',
+      'ssh',
+      'sftp',
+      'webcal',
+    ));
+
+    foreach ($allowed_protocols as $protocol) {
+      if ($protocol !== 'news' && $protocol !== 'mailto') {
+        $links[] = $protocol . '://www.example.com';
+      }
+    }
+    foreach ($links as $link) {
+      $type = link_url_type($link);
+      $this->assertEqual(LINK_EXTERNAL, $type, 'Testing that ' . $link . ' is an external link.');
+      $valid = link_validate_url($link);
+      $this->assertTrue($valid, 'Test ' . $link . ' is valid external link.');
+      // The following two lines are commented out and only used for
+      // comparisons.
+      // @code
+      // $valid2 = valid_url($link, TRUE);
+      // $this->assertEqual(TRUE, $valid2, "Using valid_url() on $link.");.
+      // @endcode
+    }
+  }
+
+  /**
+   * Check Invalid External Links.
+   */
+  public function testInvalidExternalLinks() {
+    $links = array(
+      'http://www.ex ample.com/',
+      // Bad ip!
+      'http://25.0.0/',
+      'http://4827.0.0.2/',
+      // ß not allowed in domain names!
+      'http://www.testß.com/',
+      // Bad TLD.
+      'http://.www.foo.bar./',
+      // Domains can't have sections starting with a dash.
+      // 'http://www.-fudge.com/',
+      'http://example.com/index.php?page=this\that',
+      'example@example.com',
+    );
+    foreach ($links as $link) {
+      $valid = link_validate_url($link);
+      $this->assertEqual(FALSE, $valid, 'Testing that ' . $link . ' is not a valid link.');
+    }
+  }
+
+}
diff --git a/drupal7/sites/all/modules/link/tests/link.crud.test b/drupal7/sites/all/modules/link/tests/link.crud.test
deleted file mode 100644
index de050b18..00000000
--- a/drupal7/sites/all/modules/link/tests/link.crud.test
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php
-
-/**
- * @file
- * File for Crud Tests.
- *
- * Basic CRUD simpletests for the link module, based off of content.crud.test in
- * CCK.
- */
-
-/**
- * Content Crud.
- */
-class LinkContentCrudTest extends DrupalWebTestCase {
-
-  /**
-   * Get Info.
-   */
-  public static function getInfo() {
-    return array(
-      'name' => 'Link CRUD - Basic API tests',
-      'description' => 'Tests the field CRUD (create, read, update, delete) API.',
-      'group' => 'Link',
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setUp(array $modules = array()) {
-    $modules[] = 'field_ui';
-    $modules[] = 'link';
-    parent::setUp($modules);
-  }
-
-  /**
-   * Create Field API.
-   *
-   * All we're doing here is creating a content type, creating a simple link
-   * field on that content type.
-   */
-  public function testLinkCreateFieldApi() {
-    $content_type_friendly = $this->randomName(20);
-    $content_type_machine = strtolower($this->randomName(10));
-
-    // Create and login user.
-    $this->web_user = $this->drupalCreateUser(array(
-      'administer content types',
-      'administer fields',
-    ));
-    $this->drupalLogin($this->web_user);
-
-    $this->drupalGet('admin/structure/types');
-
-    // Create the content type.
-    $this->clickLink(t('Add content type'));
-
-    $edit = array(
-      'name' => $content_type_friendly,
-      'type' => $content_type_machine,
-    );
-    $this->drupalPost(NULL, $edit, t('Save and add fields'));
-    $this->assertText(t('The content type @name has been added.', array('@name' => $content_type_friendly)));
-
-    // Now add a singleton field.
-    $single_field_name_friendly = $this->randomName(20);
-    $single_field_name_machine = strtolower($this->randomName(10));
-    $edit = array(
-      'fields[_add_new_field][label]' => $single_field_name_friendly,
-      'fields[_add_new_field][field_name]' => $single_field_name_machine,
-      'fields[_add_new_field][type]' => 'link_field',
-      'fields[_add_new_field][widget_type]' => 'link_field',
-    );
-    $this->drupalPost(NULL, $edit, t('Save'));
-
-    // We'll go with the default settings for this run-through.
-    $this->drupalPost(NULL, array(), t('Save field settings'));
-
-    // Using all the default settings, so press the button.
-    $this->drupalPost(NULL, array(), t('Save settings'));
-    $this->assertText(t('Saved @name configuration.', array('@name' => $single_field_name_friendly)));
-
-    // Somehow clicking "save" isn't enough, and we have to do a
-    // node_types_rebuild().
-    node_types_rebuild();
-    menu_rebuild();
-    $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $content_type_machine))->fetchField();
-    $this->assertTrue($type_exists, 'The new content type has been created in the database.');
-  }
-
-}
