Projet

Général

Profil

Paste
Télécharger (70,5 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / link / link.module @ bad4e148

1
<?php
2

    
3
/**
4
 * @file
5
 * Defines simple link field types.
6
 */
7

    
8
define('LINK_EXTERNAL', 'external');
9
define('LINK_INTERNAL', 'internal');
10
define('LINK_FRONT', 'front');
11
define('LINK_FRAGMENT', 'fragment');
12
define('LINK_QUERY', 'query');
13
define('LINK_EMAIL', 'email');
14
define('LINK_TEL', 'tel');
15
define('LINK_NEWS', 'news');
16
define('LINK_FILE', 'file');
17
define('LINK_TARGET_DEFAULT', 'default');
18
define('LINK_TARGET_NEW_WINDOW', '_blank');
19
define('LINK_TARGET_TOP', '_top');
20
define('LINK_TARGET_USER', 'user');
21
define('LINK_HTTP_PROTOCOL', 'http');
22
define('LINK_HTTPS_PROTOCOL', 'https');
23

    
24
/**
25
 * Maximum URLs length - needs to match value in link.install.
26
 */
27
define('LINK_URL_MAX_LENGTH', 2048);
28

    
29
/**
30
 * Implements hook_help().
31
 */
32
function link_help($path, $arg) {
33
  switch ($path) {
34
    case 'admin/help#link':
35
      $output = '<p><strong>' . t('About') . '</strong></p>';
36
      $output .= '<p>' . t('The link provides a standard custom content field for links. Links can be easily added to any content types and profiles and include advanced validating and different ways of storing internal or external links and URLs. It also supports additional link text title, site wide tokens for titles and title attributes, target attributes, css class attribution, static repeating values, input conversion, and many more.') . '</p>';
37
      $output .= '<p><strong>' . t('Requirements / Dependencies') . '</strong></p>';
38
      $output .= '<p>' . 'Fields API is provided already by core [no dependencies].' . '</p>';
39
      $output .= '<p><strong>Configuration</strong></p>';
40
      $output .= '<p>' . 'Configuration is only slightly more complicated than a text field. Link text titles for URLs can be made required, set as instead of URL, optional (default), or left out entirely. If no link text title is provided, the trimmed version of the complete URL will be displayed. The target attribute should be set to "_blank", "top", or left out completely (checkboxes provide info). The rel=nofollow attribute prevents the link from being followed by certain search engines.' . '</p>';
41
      return $output;
42
  }
43
}
44

    
45
/**
46
 * Implements hook_field_info().
47
 */
48
function link_field_info() {
49
  return array(
50
    'link_field' => array(
51
      'label' => t('Link'),
52
      'description' => t('Store a title, href, and attributes in the database to assemble a link.'),
53
      'settings' => array(
54
        'attributes' => _link_default_attributes(),
55
        'url' => 0,
56
        'title' => 'optional',
57
        'title_value' => '',
58
        'title_maxlength' => 128,
59
        'enable_tokens' => 1,
60
        'display' => array(
61
          'url_cutoff' => 80,
62
        ),
63
      ),
64
      'instance_settings' => array(
65
        'attributes' => _link_default_attributes(),
66
        'url' => 0,
67
        'title' => 'optional',
68
        'title_value' => '',
69
        'title_label_use_field_label' => FALSE,
70
        'title_maxlength' => 128,
71
        'enable_tokens' => 1,
72
        'convert_aliases' => 0,
73
        'display' => array(
74
          'url_cutoff' => 80,
75
        ),
76
        'validate_url' => 1,
77
        'absolute_url' => 1,
78
      ),
79
      'default_widget' => 'link_field',
80
      'default_formatter' => 'link_default',
81
      // Support hook_entity_property_info() from contrib "Entity API".
82
      'property_type' => 'field_item_link',
83
      'property_callbacks' => array('link_field_property_info_callback'),
84
    ),
85
  );
86
}
87

    
88
/**
89
 * Implements hook_field_instance_settings_form().
90
 */
91
function link_field_instance_settings_form($field, $instance) {
92
  $form = array(
93
    '#element_validate' => array('link_field_settings_form_validate'),
94
  );
95

    
96
  $form['absolute_url'] = array(
97
    '#type' => 'checkbox',
98
    '#title' => t('Absolute URL'),
99
    '#default_value' => isset($instance['settings']['absolute_url']) && ($instance['settings']['absolute_url'] !== '') ? $instance['settings']['absolute_url'] : TRUE,
100
    '#description' => t('If checked, the URL will always render as an absolute URL.'),
101
  );
102

    
103
  $form['validate_url'] = array(
104
    '#type' => 'checkbox',
105
    '#title' => t('Validate URL'),
106
    '#default_value' => isset($instance['settings']['validate_url']) && ($instance['settings']['validate_url'] !== '') ? $instance['settings']['validate_url'] : TRUE,
107
    '#description' => t('If checked, the URL field will be verified as a valid URL during validation.'),
108
  );
109

    
110
  $form['url'] = array(
111
    '#type' => 'checkbox',
112
    '#title' => t('Optional URL'),
113
    '#default_value' => isset($instance['settings']['url']) ? $instance['settings']['url'] : '',
114
    '#return_value' => 'optional',
115
    '#description' => t('If checked, the URL field is optional and submitting a title alone will be acceptable. If the URL is omitted, the title will be displayed as plain text.'),
116
  );
117

    
118
  $title_options = array(
119
    'optional' => t('Optional Title'),
120
    'required' => t('Required Title'),
121
    'value' => t('Static Title'),
122
    'select' => t('Selected Title'),
123
    'none' => t('No Title'),
124
  );
125

    
126
  $form['title'] = array(
127
    '#type' => 'radios',
128
    '#title' => t('Link Title'),
129
    '#default_value' => isset($instance['settings']['title']) ? $instance['settings']['title'] : 'optional',
130
    '#options' => $title_options,
131
    '#description' => t('If the link title is optional or required, a field will be displayed to the end user. If the link title is static, the link will always use the same title. If <a href="http://drupal.org/project/token">token module</a> is installed, the static title value may use any other entity field as its value. Static and token-based titles may include most inline XHTML tags such as <em>strong</em>, <em>em</em>, <em>img</em>, <em>span</em>, etc.'),
132
  );
133

    
134
  $form['title_value'] = array(
135
    '#type' => 'textfield',
136
    '#title' => t('Static or default title'),
137
    '#default_value' => isset($instance['settings']['title_value']) ? $instance['settings']['title_value'] : '',
138
    '#description' => t('This title will 1) always be used if "Static Title" is selected above, or 2) used if "Optional title" is selected above and no title is entered when creating content.'),
139
    '#states' => array(
140
      'visible' => array(
141
        ':input[name="instance[settings][title]"]' => array('value' => 'value'),
142
      ),
143
    ),
144
  );
145

    
146
  $form['title_allowed_values'] = array(
147
    '#type' => 'textarea',
148
    '#title' => t('Title allowed values'),
149
    '#default_value' => isset($instance['settings']['title_allowed_values']) ? $instance['settings']['title_allowed_values'] : '',
150
    '#description' => t('When using "Selected Title", you can allow users to select the title from a limited set of values (eg. Home, Office, Other). Enter here all possible values that title can take, one value per line.'),
151
    '#states' => array(
152
      'visible' => array(
153
        ':input[name="instance[settings][title]"]' => array('value' => 'select'),
154
      ),
155
    ),
156
  );
157

    
158
  $form['title_label_use_field_label'] = array(
159
    '#type' => 'checkbox',
160
    '#title' => t('Use field label as the label for the title field'),
161
    '#default_value' => isset($instance['settings']['title_label_use_field_label']) ? $instance['settings']['title_label_use_field_label'] : FALSE,
162
    '#description' => t('If this is checked the field label will be hidden.'),
163
  );
164

    
165
  $form['title_maxlength'] = array(
166
    '#type' => 'textfield',
167
    '#title' => t('Max length of title field'),
168
    '#default_value' => isset($instance['settings']['title_maxlength']) ? $instance['settings']['title_maxlength'] : '128',
169
    '#description' => t('Set a maximum length on the title field (applies only if Link Title is optional or required).  The maximum limit is 255 characters.'),
170
    '#maxlength' => 3,
171
    '#size' => 3,
172
  );
173

    
174
  if (module_exists('token')) {
175
    // Add token module replacements fields.
176
    $form['enable_tokens'] = array(
177
      '#type' => 'checkbox',
178
      '#title' => t('Allow user-entered tokens'),
179
      '#default_value' => isset($instance['settings']['enable_tokens']) ? $instance['settings']['enable_tokens'] : 1,
180
      '#description' => t('Checking will allow users to enter tokens in URLs and Titles on the entity edit form. This does not affect the field settings on this page.'),
181
    );
182

    
183
    $entity_info = entity_get_info($instance['entity_type']);
184
    $form['tokens_help'] = array(
185
      '#theme' => 'token_tree',
186
      '#token_types' => array($entity_info['token type']),
187
      '#global_types' => TRUE,
188
      '#click_insert' => TRUE,
189
      '#dialog' => TRUE,
190
    );
191
  }
192

    
193
  $form['convert_aliases'] = array(
194
    '#type' => 'checkbox',
195
    '#title' => t('Convert local aliases'),
196
    '#default_value' => isset($instance['settings']['convert_aliases']) ? $instance['settings']['convert_aliases'] : '',
197
    '#description' => t('If checked, a path alias is converted to the internal system path, the same way as when saving menu links.'),
198
  );
199

    
200
  $form['display'] = array(
201
    '#tree' => TRUE,
202
  );
203
  $form['display']['url_cutoff'] = array(
204
    '#type' => 'textfield',
205
    '#title' => t('URL Display Cutoff'),
206
    '#default_value' => isset($instance['settings']['display']['url_cutoff']) ? $instance['settings']['display']['url_cutoff'] : '80',
207
    '#description' => t('If the user does not include a title for this link, the URL will be used as the title. When should the link title be trimmed and finished with an elipsis (&hellip;)? Leave blank for no limit.'),
208
    '#maxlength' => 3,
209
    '#size' => 3,
210
  );
211

    
212
  // Target options. E.g. New window = target="_blank".
213
  $target_options = array(
214
    LINK_TARGET_DEFAULT => t('Default (no target attribute)'),
215
    LINK_TARGET_TOP => t('Open link in window root'),
216
    LINK_TARGET_NEW_WINDOW => t('Open link in new window'),
217
    LINK_TARGET_USER => t('Allow the user to choose'),
218
  );
219

    
220
  $form['attributes'] = array(
221
    '#tree' => TRUE,
222
  );
223

    
224
  $form['attributes']['target'] = array(
225
    '#type' => 'radios',
226
    '#title' => t('Link Target'),
227
    '#default_value' => empty($instance['settings']['attributes']['target']) ? LINK_TARGET_DEFAULT : $instance['settings']['attributes']['target'],
228
    '#options' => $target_options,
229
  );
230
  $form['attributes']['rel'] = array(
231
    '#type' => 'textfield',
232
    '#title' => t('Rel Attribute'),
233
    '#description' => t('When output, this link will have this rel attribute. The most common usage is <a href="http://en.wikipedia.org/wiki/Nofollow" target="blank">rel=&quot;nofollow&quot;</a> which prevents some search engines from spidering entered links.'),
234
    '#default_value' => empty($instance['settings']['attributes']['rel']) ? '' : $instance['settings']['attributes']['rel'],
235
    '#field_prefix' => 'rel = "',
236
    '#field_suffix' => '"',
237
    '#size' => 20,
238
  );
239
  $rel_remove_options = array(
240
    'default' => t('Keep rel as set up above (untouched/default)'),
241
    'rel_remove_external' => t('Remove rel if given link is external'),
242
    'rel_remove_internal' => t('Remove rel if given link is internal'),
243
  );
244
  $form['rel_remove'] = array(
245
    '#type' => 'radios',
246
    '#title' => t('Remove rel attribute automatically'),
247
    '#default_value' => !isset($instance['settings']['rel_remove']) ? 'default' : $instance['settings']['rel_remove'],
248
    '#description' => t('Turn on/off if rel attribute should be removed automatically, if user given link is internal/external'),
249
    '#options' => $rel_remove_options,
250
  );
251
  $form['attributes']['configurable_class'] = array(
252
    '#title' => t("Allow the user to enter a custom link class per link"),
253
    '#type' => 'checkbox',
254
    '#default_value' => empty($instance['settings']['attributes']['configurable_class']) ? '' : $instance['settings']['attributes']['configurable_class'],
255
  );
256
  $form['attributes']['class'] = array(
257
    '#type' => 'textfield',
258
    '#title' => t('Additional CSS Class'),
259
    '#description' => t('When output, this link will have this class attribute. Multiple classes should be separated by spaces. Only alphanumeric characters and hyphens are allowed'),
260
    '#default_value' => empty($instance['settings']['attributes']['class']) ? '' : $instance['settings']['attributes']['class'],
261
  );
262
  $form['attributes']['configurable_title'] = array(
263
    '#title' => t("Allow the user to enter a link 'title' attribute"),
264
    '#type' => 'checkbox',
265
    '#default_value' => empty($instance['settings']['attributes']['configurable_title']) ? '' : $instance['settings']['attributes']['configurable_title'],
266
  );
267
  $form['attributes']['title'] = array(
268
    '#title' => t("Default link 'title' Attribute"),
269
    '#type' => 'textfield',
270
    '#description' => t('When output, links will use this "title" attribute if the user does not provide one and when different from the link text. Read <a href="http://www.w3.org/TR/WCAG10-HTML-TECHS/#links" target="blank">WCAG 1.0 Guidelines</a> for links comformances. Tokens values will be evaluated.'),
271
    '#default_value' => empty($instance['settings']['attributes']['title']) ? '' : $instance['settings']['attributes']['title'],
272
    '#field_prefix' => 'title = "',
273
    '#field_suffix' => '"',
274
    '#size' => 20,
275
  );
276
  return $form;
277
}
278

    
279
/**
280
 * Form validate.
281
 *
282
 * #element_validate handler for link_field_instance_settings_form().
283
 */
284
function link_field_settings_form_validate($element, &$form_state, $complete_form) {
285
  if ($form_state['values']['instance']['settings']['title'] === 'value' && empty($form_state['values']['instance']['settings']['title_value'])) {
286
    form_set_error('instance][settings][title_value', t('A default title must be provided if the title is a static value.'));
287
  }
288
  if ($form_state['values']['instance']['settings']['title'] === 'select'
289
    && empty($form_state['values']['instance']['settings']['title_allowed_values'])) {
290
    form_set_error('instance][settings][title_allowed_values', t('You must enter one or more allowed values for link Title, the title is a selected value.'));
291
  }
292
  if (!empty($form_state['values']['instance']['settings']['display']['url_cutoff']) && !is_numeric($form_state['values']['instance']['settings']['display']['url_cutoff'])) {
293
    form_set_error('display', t('URL Display Cutoff value must be numeric.'));
294
  }
295
  if (empty($form_state['values']['instance']['settings']['title_maxlength'])) {
296
    form_set_value($element['title_maxlength'], '128', $form_state);
297
  }
298
  elseif (!is_numeric($form_state['values']['instance']['settings']['title_maxlength'])) {
299
    form_set_error('title_maxlength', t('The max length of the link title must be numeric.'));
300
  }
301
  elseif ($form_state['values']['instance']['settings']['title_maxlength'] > 255) {
302
    form_set_error('title_maxlength', t('The max length of the link title cannot be greater than 255 characters.'));
303
  }
304
}
305

    
306
/**
307
 * Implements hook_field_is_empty().
308
 */
309
function link_field_is_empty($item, $field) {
310
  return empty($item['title']) && empty($item['url']);
311
}
312

    
313
/**
314
 * Implements hook_field_load().
315
 */
316
function link_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
317
  foreach ($entities as $id => $entity) {
318
    foreach ($items[$id] as $delta => $item) {
319
      $items[$id][$delta]['attributes'] = _link_load($field, $item, $instances[$id]);
320
      $items[$id][$delta]['original_title'] = isset($item['title']) ? $item['title'] : NULL;
321
      $items[$id][$delta]['original_url'] = $item['url'];
322
    }
323
  }
324
}
325

    
326
/**
327
 * Implements hook_field_validate().
328
 */
329
function link_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
330
  $optional_field_found = FALSE;
331
  if ($instance['settings']['validate_url'] !== 0 || is_null($instance['settings']['validate_url']) || !isset($instance['settings']['validate_url'])) {
332
    foreach ($items as $delta => $value) {
333
      _link_validate($items[$delta], $delta, $field, $entity, $instance, $langcode, $optional_field_found, $errors);
334
    }
335
  }
336

    
337
  foreach ($items as $delta => $value) {
338
    if (isset($value['attributes']) && is_string($value['attributes'])) {
339
      $errors[$field['field_name']][$langcode][$delta][] = array(
340
        'error' => 'link_required',
341
        'message' => t('String values are not acceptable for attributes.'),
342
        'error_element' => array('url' => TRUE, 'title' => FALSE),
343
      );
344
    }
345
  }
346

    
347
  if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] && !$optional_field_found) {
348
    $errors[$field['field_name']][$langcode][0][] = array(
349
      'error' => 'link_required',
350
      'message' => t('At least one title or URL must be entered.'),
351
      'error_element' => array('url' => FALSE, 'title' => TRUE),
352
    );
353
  }
354

    
355
  // Specific logic for when using the 'select' option on the title field.
356
  if ($instance['settings']['title'] == 'select') {
357
    // The title is required.
358
    if (!empty($item['title']) && !empty($item['url'])) {
359
      $errors[$field['field_name']][$langcode][$delta][] = array(
360
        'error' => 'link_required',
361
        'message' => t('The title field is required when a URL is provided.'),
362
        'error_element' => array('url' => FALSE, 'title' => TRUE),
363
      );
364
    }
365
  }
366
}
367

    
368
/**
369
 * Implements hook_field_insert().
370
 */
371
function link_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
372
  foreach ($items as $delta => $value) {
373
    _link_process($items[$delta], $delta, $field, $entity, $instance);
374
  }
375
}
376

    
377
/**
378
 * Implements hook_field_update().
379
 */
380
function link_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
381
  foreach ($items as $delta => $value) {
382
    _link_process($items[$delta], $delta, $field, $entity, $instance);
383
  }
384
}
385

    
386
/**
387
 * Implements hook_field_prepare_view().
388
 */
389
function link_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
390
  foreach ($items as $entity_id => $entity_items) {
391
    foreach ($entity_items as $delta => $value) {
392
      _link_sanitize($items[$entity_id][$delta], $delta, $field, $instances[$entity_id], $entities[$entity_id]);
393
    }
394
  }
395
}
396

    
397
/**
398
 * Implements hook_field_widget_info().
399
 */
400
function link_field_widget_info() {
401
  return array(
402
    'link_field' => array(
403
      'label' => 'Link',
404
      'field types' => array('link_field'),
405
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
406
    ),
407
  );
408
}
409

    
410
/**
411
 * Implements hook_field_widget_form().
412
 */
413
function link_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
414
  $element += array(
415
    '#type' => $instance['widget']['type'],
416
    '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
417
  );
418
  return $element;
419
}
420

    
421
/**
422
 * Implements hook_field_widget_error().
423
 */
424
function link_field_widget_error($element, $error, $form, &$form_state) {
425
  if (!empty($error['error_element']['title'])) {
426
    form_error($element['title'], $error['message']);
427
  }
428
  elseif (!empty($error['error_element']['url'])) {
429
    form_error($element['url'], $error['message']);
430
  }
431
}
432

    
433
/**
434
 * Unpacks the item attributes for use.
435
 */
436
function _link_load($field, $item, $instance) {
437
  if (isset($item['attributes'])) {
438
    if (!is_array($item['attributes'])) {
439
      $item['attributes'] = unserialize($item['attributes']);
440
    }
441
    return $item['attributes'];
442
  }
443
  elseif (isset($instance['settings']['attributes'])) {
444
    return $instance['settings']['attributes'];
445
  }
446
  else {
447
    return $field['settings']['attributes'];
448
  }
449
}
450

    
451
/**
452
 * Prepares the item attributes and url for storage.
453
 *
454
 * @param array $item
455
 *   Link field values.
456
 * @param array $delta
457
 *   The sequence number for current values.
458
 * @param array $field
459
 *   The field structure array.
460
 * @param object $entity
461
 *   Entity object.
462
 * @param array $instance
463
 *   The instance structure for $field on $entity's bundle.
464
 *
465
 * @codingStandardsIgnoreStart
466
 */
467
function _link_process(&$item, $delta, $field, $entity, $instance) {
468
  // @codingStandardsIgnoreEnd
469
  // Trim whitespace from URL.
470
  if (!empty($item['url'])) {
471
    $item['url'] = trim($item['url']);
472
  }
473

    
474
  // Optionally convert aliases to the system path.
475
  if (!empty($instance['settings']['convert_aliases'])) {
476
    global $base_url;
477

    
478
    // Check if either the site's absolute URL or the relative base URL are at
479
    // the start of the URL, if so remove them.
480
    $base_paths = array(
481
      $base_url . base_path(),
482
      base_path(),
483
    );
484

    
485
    // Work out the correct base_path to use based on the HTTPS settings.
486
    if (isset($GLOBALS['base_secure_url'])) {
487
      $base_paths[] = $GLOBALS['base_secure_url'] . base_path();
488
    }
489
    if (isset($GLOBALS['base_insecure_url'])) {
490
      $base_paths[] = $GLOBALS['base_insecure_url'] . base_path();
491
    }
492

    
493
    // Add any additional paths.
494
    if ($extra_paths = variable_get('link_base_urls', array())) {
495
      // Create versions with and without the base path.
496
      foreach ($extra_paths as $extra_path) {
497
        $base_paths[] = $extra_path;
498
        $base_paths[] = $extra_path . base_path();
499
      }
500
    }
501

    
502
    $paths_to_test = array(
503
      $item['url'],
504
    );
505

    
506
    foreach ($base_paths as $path) {
507
      // Verify the path is at the beginning of the URL string.
508
      if (strpos($item['url'], $path) === 0) {
509
        $strlen = drupal_strlen($path);
510
        $paths_to_test[] = drupal_substr($item['url'], $strlen);
511
      }
512
    }
513

    
514
    // Check each of the paths to see if one of them is a system path.
515
    foreach (array_unique($paths_to_test) as $path) {
516
      $language = NULL;
517

    
518
      // If we have locale enabled attempt to remove the language prefix first.
519
      if (module_exists('locale')) {
520
        require_once DRUPAL_ROOT . '/includes/language.inc';
521
        list($language, $path) = language_url_split_prefix($path, language_list());
522
      }
523

    
524
      // Attempt to get a system path.
525
      $normal_path = drupal_get_normal_path($path, $language);
526

    
527
      // If we get back a different path it means Drupal found a system path we
528
      // can use.
529
      if ($normal_path != $path) {
530
        $item['url'] = $normal_path;
531
        break;
532
      }
533
    }
534
  }
535

    
536
  // If no attributes are set then make sure $item['attributes'] is an empty
537
  // array, so $field['attributes'] can override it.
538
  if (empty($item['attributes'])) {
539
    $item['attributes'] = array();
540
  }
541

    
542
  // Serialize the attributes array.
543
  if (!is_string($item['attributes'])) {
544
    $item['attributes'] = serialize($item['attributes']);
545
  }
546

    
547
  // Don't save an invalid default value (e.g. 'http://').
548
  if ((isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url']) && is_object($entity)) {
549
    $langcode = !empty($entity) ? field_language($instance['entity_type'], $entity, $instance['field_name']) : LANGUAGE_NONE;
550
    if (!link_validate_url($item['url'], $langcode)) {
551
      unset($item['url']);
552
    }
553
  }
554
}
555

    
556
/**
557
 * Validates that the link field has been entered properly.
558
 */
559
function _link_validate(&$item, $delta, $field, $entity, $instance, $langcode, &$optional_field_found, &$errors) {
560
  if ($item['url'] && !(isset($instance['default_value'][$delta]['url']) && $item['url'] === $instance['default_value'][$delta]['url'] && !$instance['required'])) {
561
    // Validate the link.
562
    if (!link_validate_url(trim($item['url']), $langcode)) {
563
      $errors[$field['field_name']][$langcode][$delta][] = array(
564
        'error' => 'link_required',
565
        'message' => t('The value %value provided for %field is not a valid URL.', array(
566
          '%value' => trim($item['url']),
567
          '%field' => $instance['label'],
568
        )),
569
        'error_element' => array('url' => TRUE, 'title' => FALSE),
570
      );
571
    }
572
    // Require a title for the link if necessary.
573
    if ($instance['settings']['title'] == 'required' && strlen(trim($item['title'])) == 0) {
574
      $errors[$field['field_name']][$langcode][$delta][] = array(
575
        'error' => 'link_required',
576
        'message' => t('Titles are required for all links.'),
577
        'error_element' => array('url' => FALSE, 'title' => TRUE),
578
      );
579
    }
580
  }
581
  // Require a link if we have a title.
582
  if ($instance['settings']['url'] !== 'optional' && strlen(isset($item['title']) ? $item['title'] : NULL) > 0 && strlen(trim($item['url'])) == 0) {
583
    $errors[$field['field_name']][$langcode][$delta][] = array(
584
      'error' => 'link_required',
585
      'message' => t('You cannot enter a title without a link url.'),
586
      'error_element' => array('url' => TRUE, 'title' => FALSE),
587
    );
588
  }
589
  // In a totally bizzaro case, where URLs and titles are optional but the field
590
  // is required, ensure there is at least one link.
591
  if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional'
592
    && (strlen(trim($item['url'])) !== 0 || strlen(trim($item['title'])) !== 0)) {
593
    $optional_field_found = TRUE;
594
  }
595
  // Require entire field.
596
  if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] == 1 && !$optional_field_found && isset($instance['id'])) {
597
    $errors[$field['field_name']][$langcode][$delta][] = array(
598
      'error' => 'link_required',
599
      'message' => t('At least one title or URL must be entered.'),
600
      'error_element' => array('url' => FALSE, 'title' => TRUE),
601
    );
602
  }
603

    
604
  // Specific logic for when using the 'select' option on the title field.
605
  if ($instance['settings']['title'] == 'select') {
606
    // The title is required.
607
    if (!empty($item['title']) && !empty($item['url'])) {
608
      $errors[$field['field_name']][$langcode][$delta][] = array(
609
        'error' => 'link_required',
610
        'message' => t('The title field is required when a URL is provided.'),
611
        'error_element' => array('url' => FALSE, 'title' => TRUE),
612
      );
613
    }
614
  }
615
}
616

    
617
/**
618
 * Clean up user-entered values for a link field according to field settings.
619
 *
620
 * Note: this cannot be properly unit tested as it checks for certain entity
621
 * values.
622
 *
623
 * @todo Rewrite so that the logic can be unit tested.
624
 *
625
 * @param array $item
626
 *   A single link item, usually containing url, title, and attributes.
627
 * @param int $delta
628
 *   The delta value if this field is one of multiple fields.
629
 * @param array $field
630
 *   The CCK field definition.
631
 * @param object $entity
632
 *   The entity containing this link.
633
 *
634
 * @codingStandardsIgnoreStart
635
 */
636
function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) {
637
  // @codingStandardsIgnoreEnd
638
  // As this function can be called multiple times and the item is changed by
639
  // reference we need to ensure that there's always the original data to
640
  // process otherwise processed data are processed again which might leads to
641
  // unexpected results.
642
  if (isset($item['_link_sanitized'])) {
643
    return;
644
  }
645

    
646
  // Store a flag to check in case of a second call.
647
  $item['_link_sanitized'] = TRUE;
648

    
649
  // Don't try to process empty links.
650
  if (empty($item['url']) && empty($item['title'])) {
651
    return;
652
  }
653
  if (empty($item['html'])) {
654
    $item['html'] = FALSE;
655
  }
656

    
657
  // Replace URL tokens.
658
  $entity_type = $instance['entity_type'];
659
  $entity_info = entity_get_info($entity_type);
660
  $property_id = $entity_info['entity keys']['id'];
661
  if (isset($entity_info['token type'])) {
662
    $entity_token_type = $entity_info['token type'];
663
  }
664
  elseif ($entity_type == 'taxonomy_term' || $entity_type == 'taxonomy_vocabulary') {
665
    $entity_token_type = str_replace('taxonomy_', '', $entity_type);
666
  }
667
  else {
668
    $entity_token_type = $entity_type;
669
  }
670
  if (isset($instance['settings']['enable_tokens']) && $instance['settings']['enable_tokens']) {
671
    $text_tokens = token_scan($item['url']);
672
    if (!empty($text_tokens)) {
673
      // Load the entity if necessary for entities in views.
674
      if (isset($entity->{$property_id})) {
675
        $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
676
        $entity_loaded = array_pop($entity_loaded);
677
      }
678
      else {
679
        $entity_loaded = $entity;
680
      }
681
      $item['url'] = token_replace($item['url'], array($entity_token_type => $entity_loaded));
682
    }
683
  }
684

    
685
  $type = link_url_type($item['url']);
686
  // If the type of the URL cannot be determined and URL validation is disabled,
687
  // then assume LINK_EXTERNAL for later processing.
688
  if ($type == FALSE && $instance['settings']['validate_url'] === 0) {
689
    $type = LINK_EXTERNAL;
690
  }
691
  elseif ($type == LINK_FRAGMENT || $type == LINK_QUERY) {
692
    // Treat fragment or query-only links as external.
693
    if (!empty($instance['settings']['absolute_url'])) {
694
      $item['url'] = $_GET['q'] . $item['url'];
695
    }
696
    else {
697
      $item['external'] = TRUE;
698
    }
699
  }
700
  $url = link_cleanup_url($item['url'], variable_get('link_default_protocol', LINK_HTTP_PROTOCOL));
701
  $url_parts = _link_parse_url($url);
702

    
703
  if (!empty($url_parts['url'])) {
704
    $item = array(
705
      'url' => $url_parts['url'],
706
      'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
707
      'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
708
      'absolute' => !empty($instance['settings']['absolute_url']),
709
      'html' => TRUE,
710
    ) + $item;
711
  }
712

    
713
  // Create a shortened URL for display.
714
  if ($type == LINK_EMAIL) {
715
    $display_url = str_replace('mailto:', '', $url);
716
  }
717
  elseif ($type === LINK_EXTERNAL) {
718
    $display_url = url($url_parts['url'],
719
      array(
720
        'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
721
        'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
722
        'absolute' => TRUE,
723
      )
724
    );
725
  }
726
  elseif ($type == LINK_TEL) {
727
    $display_url = str_replace('tel:', '', $url);
728
  }
729
  else {
730
    $display_url = url($url_parts['url'],
731
      array(
732
        'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
733
        'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
734
        'absolute' => !empty($instance['settings']['absolute_url']),
735
      )
736
    );
737
  }
738
  if ($instance['settings']['display']['url_cutoff'] && strlen($display_url) > $instance['settings']['display']['url_cutoff']) {
739
    $display_url = substr($display_url, 0, $instance['settings']['display']['url_cutoff']) . "…";
740
  }
741
  $item['display_url'] = $display_url;
742

    
743
  // Use the title defined at the instance level.
744
  if ($instance['settings']['title'] == 'value' && strlen(trim($instance['settings']['title_value']))) {
745
    $title = $instance['settings']['title_value'];
746
    if (function_exists('i18n_string_translate')) {
747
      $i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value";
748
      $title = i18n_string_translate($i18n_string_name, $title);
749
    }
750
  }
751
  // Use the title defined by the user at the widget level.
752
  elseif (isset($item['title']) && drupal_strlen(trim($item['title']))) {
753
    $title = $item['title'];
754
  }
755
  // Use the static title if a user-defined title is optional and a static title
756
  // has been defined.
757
  elseif ($instance['settings']['title'] == 'optional' && drupal_strlen(trim($instance['settings']['title_value']))) {
758
    $title = $instance['settings']['title_value'];
759
  }
760
  else {
761
    $title = '';
762
  }
763

    
764
  // Replace title tokens.
765
  if ($title && $instance['settings']['enable_tokens']) {
766
    $text_tokens = token_scan($title);
767
    if (!empty($text_tokens)) {
768
      // Load the entity if necessary for entities in views.
769
      if (isset($entity->{$property_id})) {
770
        $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
771
        $entity_loaded = array_pop($entity_loaded);
772
      }
773
      else {
774
        $entity_loaded = $entity;
775
      }
776
      $title = token_replace($title, array($entity_token_type => $entity_loaded));
777
    }
778
  }
779
  if ($title && ($instance['settings']['title'] == 'value' || $instance['settings']['enable_tokens'])) {
780
    $title = filter_xss($title, array(
781
      'b',
782
      'br',
783
      'code',
784
      'em',
785
      'i',
786
      'img',
787
      'span',
788
      'strong',
789
      'sub',
790
      'sup',
791
      'tt',
792
      'u',
793
    ));
794
    $item['html'] = TRUE;
795
  }
796
  $item['title'] = empty($title) && $title !== '0' ? $item['display_url'] : $title;
797

    
798
  if (!isset($item['attributes'])) {
799
    $item['attributes'] = array();
800
  }
801

    
802
  // Unserialize attributtes array if it has not been unserialized yet.
803
  if (!is_array($item['attributes'])) {
804
    $item['attributes'] = (array) unserialize($item['attributes']);
805
  }
806

    
807
  // Add default attributes.
808
  if (!is_array($instance['settings']['attributes'])) {
809
    $instance['settings']['attributes'] = _link_default_attributes();
810
  }
811
  else {
812
    $instance['settings']['attributes'] += _link_default_attributes();
813
  }
814

    
815
  // Merge item attributes with attributes defined at the field level.
816
  $item['attributes'] += $instance['settings']['attributes'];
817

    
818
  // If user is not allowed to choose target attribute, use default defined at
819
  // field level.
820
  if ($instance['settings']['attributes']['target'] != LINK_TARGET_USER) {
821
    $item['attributes']['target'] = $instance['settings']['attributes']['target'];
822
  }
823
  elseif ($item['attributes']['target'] == LINK_TARGET_USER) {
824
    $item['attributes']['target'] = LINK_TARGET_DEFAULT;
825
  }
826

    
827
  // Remove the target attribute if the default (no target) is selected.
828
  if (empty($item['attributes']) || (isset($item['attributes']['target']) && $item['attributes']['target'] == LINK_TARGET_DEFAULT)) {
829
    unset($item['attributes']['target']);
830
  }
831

    
832
  // Remove rel attribute for internal or external links if selected.
833
  if (isset($item['attributes']['rel']) && isset($instance['settings']['rel_remove']) && $instance['settings']['rel_remove'] != 'default') {
834
    if (($instance['settings']['rel_remove'] != 'rel_remove_internal' && $type != LINK_INTERNAL) ||
835
      ($instance['settings']['rel_remove'] != 'rel_remove_external' && $type != LINK_EXTERNAL)) {
836
      unset($item['attributes']['rel']);
837
    }
838
  }
839

    
840
  // Handle "title" link attribute.
841
  if (!empty($item['attributes']['title']) && module_exists('token')) {
842
    $text_tokens = token_scan($item['attributes']['title']);
843
    if (!empty($text_tokens)) {
844
      // Load the entity (necessary for entities in views).
845
      if (isset($entity->{$property_id})) {
846
        $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
847
        $entity_loaded = array_pop($entity_loaded);
848
      }
849
      else {
850
        $entity_loaded = $entity;
851
      }
852
      $item['attributes']['title'] = token_replace($item['attributes']['title'], array($entity_token_type => $entity_loaded), array('clear' => TRUE));
853
    }
854
    $item['attributes']['title'] = filter_xss($item['attributes']['title'], array(
855
      'b',
856
      'br',
857
      'code',
858
      'em',
859
      'i',
860
      'img',
861
      'span',
862
      'strong',
863
      'sub',
864
      'sup',
865
      'tt',
866
      'u',
867
    ));
868
  }
869
  // Handle attribute classes.
870
  if (!empty($item['attributes']['class'])) {
871
    $classes = explode(' ', $item['attributes']['class']);
872
    foreach ($classes as &$class) {
873
      $class = drupal_clean_css_identifier($class);
874
    }
875
    $item['attributes']['class'] = implode(' ', $classes);
876
  }
877
  unset($item['attributes']['configurable_class']);
878

    
879
  // Remove title attribute if it's equal to link text.
880
  if (isset($item['attributes']['title']) && $item['attributes']['title'] == $item['title']) {
881
    unset($item['attributes']['title']);
882
  }
883
  unset($item['attributes']['configurable_title']);
884

    
885
  // Remove empty attributes.
886
  $item['attributes'] = array_filter($item['attributes']);
887
}
888

    
889
/**
890
 * Because parse_url doesn't work with relative urls.
891
 *
892
 * @param string $url
893
 *   URL to parse.
894
 *
895
 * @return array
896
 *   Array of url pieces - only 'url', 'query', and 'fragment'.
897
 */
898
function _link_parse_url($url) {
899
  $url_parts = array();
900
  // Separate out the anchor, if any.
901
  if (strpos($url, '#') !== FALSE) {
902
    $url_parts['fragment'] = substr($url, strpos($url, '#') + 1);
903
    $url = substr($url, 0, strpos($url, '#'));
904
  }
905
  // Separate out the query string, if any.
906
  if (strpos($url, '?') !== FALSE) {
907
    $query = substr($url, strpos($url, '?') + 1);
908
    $url_parts['query'] = _link_parse_str($query);
909
    $url = substr($url, 0, strpos($url, '?'));
910
  }
911
  $url_parts['url'] = $url;
912
  return $url_parts;
913
}
914

    
915
/**
916
 * Replaces the PHP parse_str() function.
917
 *
918
 * Because parse_str replaces the following characters in query parameters name
919
 * in order to maintain compatibility with deprecated register_globals
920
 * directive:
921
 *
922
 *   - chr(32) ( ) (space)
923
 *   - chr(46) (.) (dot)
924
 *   - chr(91) ([) (open square bracket)
925
 *   - chr(128) - chr(159) (various)
926
 *
927
 * @param string $query
928
 *   Query to parse.
929
 *
930
 * @return array
931
 *   Array of query parameters.
932
 *
933
 * @see http://php.net/manual/en/language.variables.external.php#81080
934
 */
935
function _link_parse_str($query) {
936
  $query_array = array();
937

    
938
  $pairs = explode('&', $query);
939
  foreach ($pairs as $pair) {
940
    $name_value = explode('=', $pair, 2);
941
    $name = urldecode($name_value[0]);
942
    $value = isset($name_value[1]) ? urldecode($name_value[1]) : NULL;
943
    $query_array[$name] = $value;
944
  }
945

    
946
  return $query_array;
947
}
948

    
949
/**
950
 * Implements hook_menu().
951
 */
952
function link_menu() {
953
  $items['admin/config/content/link'] = array(
954
    'title' => 'Link settings',
955
    'description' => 'Settings for the link module.',
956
    'page callback' => 'drupal_get_form',
957
    'page arguments' => array('link_admin_settings'),
958
    'access arguments' => array('access administration pages'),
959
    'file' => 'link.admin.inc',
960
  );
961
  return $items;
962
}
963

    
964
/**
965
 * Implements hook_theme().
966
 */
967
function link_theme() {
968
  return array(
969
    'link_formatter_link_default' => array(
970
      'variables' => array('element' => NULL, 'field' => NULL),
971
    ),
972
    'link_formatter_link_plain' => array(
973
      'variables' => array('element' => NULL, 'field' => NULL),
974
    ),
975
    'link_formatter_link_host' => array(
976
      'variables' => array('element' => NULL),
977
    ),
978
    'link_formatter_link_absolute' => array(
979
      'variables' => array('element' => NULL, 'field' => NULL),
980
    ),
981
    'link_formatter_link_domain' => array(
982
      'variables' => array(
983
        'element' => NULL,
984
        'display' => NULL,
985
        'field' => NULL,
986
      ),
987
    ),
988
    'link_formatter_link_no_protocol' => array(
989
      'variables' => array('element' => NULL, 'field' => NULL),
990
    ),
991
    'link_formatter_link_title_plain' => array(
992
      'variables' => array('element' => NULL, 'field' => NULL),
993
    ),
994
    'link_formatter_link_url' => array(
995
      'variables' => array('element' => NULL, 'field' => NULL),
996
    ),
997
    'link_formatter_link_short' => array(
998
      'variables' => array('element' => NULL, 'field' => NULL),
999
    ),
1000
    'link_formatter_link_label' => array(
1001
      'variables' => array('element' => NULL, 'field' => NULL),
1002
    ),
1003
    'link_formatter_link_separate' => array(
1004
      'variables' => array('element' => NULL, 'field' => NULL),
1005
    ),
1006
    'link_field' => array(
1007
      'render element' => 'element',
1008
    ),
1009
  );
1010
}
1011

    
1012
/**
1013
 * Formats a link field widget.
1014
 */
1015
function theme_link_field($vars) {
1016
  drupal_add_css(drupal_get_path('module', 'link') . '/css/link.css');
1017
  $element = $vars['element'];
1018
  // Prefix single value link fields with the name of the field.
1019
  if (empty($element['#field']['multiple'])) {
1020
    if (isset($element['url']) && !isset($element['title'])) {
1021
      $element['url']['#title_display'] = 'invisible';
1022
    }
1023
  }
1024

    
1025
  $output = '';
1026
  $output .= '<div class="link-field-subrow clearfix">';
1027
  if (isset($element['title'])) {
1028
    $output .= '<div class="link-field-title link-field-column">' . drupal_render($element['title']) . '</div>';
1029
  }
1030
  $output .= '<div class="link-field-url' . (isset($element['title']) ? ' link-field-column' : '') . '">' . drupal_render($element['url']) . '</div>';
1031
  $output .= '</div>';
1032
  if (!empty($element['attributes']['target'])) {
1033
    $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['target']) . '</div>';
1034
  }
1035
  if (!empty($element['attributes']['title'])) {
1036
    $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['title']) . '</div>';
1037
  }
1038
  if (!empty($element['attributes']['class'])) {
1039
    $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['class']) . '</div>';
1040
  }
1041
  $output .= drupal_render_children($element);
1042
  return $output;
1043
}
1044

    
1045
/**
1046
 * Implements hook_element_info().
1047
 */
1048
function link_element_info() {
1049
  $elements = array();
1050
  $elements['link_field'] = array(
1051
    '#input' => TRUE,
1052
    '#process' => array('link_field_process'),
1053
    '#theme' => 'link_field',
1054
    '#theme_wrappers' => array('form_element'),
1055
  );
1056
  return $elements;
1057
}
1058

    
1059
/**
1060
 * Returns the default attributes and their values.
1061
 */
1062
function _link_default_attributes() {
1063
  return array(
1064
    'target' => LINK_TARGET_DEFAULT,
1065
    'class' => '',
1066
    'rel' => '',
1067
  );
1068
}
1069

    
1070
/**
1071
 * Processes the link type element before displaying the field.
1072
 *
1073
 * Build the form element. When creating a form using FAPI #process,
1074
 * note that $element['#value'] is already set.
1075
 *
1076
 * The $fields array is in
1077
 * $complete_form['#field_info'][$element['#field_name']].
1078
 */
1079
function link_field_process($element, $form_state, $complete_form) {
1080
  $instance = field_widget_instance($element, $form_state);
1081
  if (!$instance) {
1082
    // The element comes from a custom form, we have to manually create the
1083
    // $instance settings.
1084
    $instance['settings'] = array(
1085
      'title_maxlength' => isset($element['#title_maxlength']) ? $element['#title_maxlength'] : 128,
1086
      'title' => isset($element['#title_mode']) ? $element['#title_mode'] : 'optional',
1087
      'title_label_use_field_label' => isset($element['#title_label_use_field_label']) ? $element['#title_label_use_field_label'] : FALSE,
1088
      'url' => isset($element['#url']) ? $element['#url'] : 'optional',
1089
    );
1090
    if (isset($element['#attributes'])) {
1091
      $instance['settings']['attributes'] = $element['#attributes'];
1092
    }
1093
  }
1094
  $settings = $instance['settings'];
1095
  $element['url'] = array(
1096
    '#type' => 'textfield',
1097
    '#maxlength' => LINK_URL_MAX_LENGTH,
1098
    '#title' => t('URL'),
1099
    '#required' => ($element['#delta'] == 0 && $settings['url'] !== 'optional') ? $element['#required'] : FALSE,
1100
    '#default_value' => isset($element['#value']['url']) ? $element['#value']['url'] : NULL,
1101
  );
1102
  if (in_array($settings['title'], array('optional', 'required'))) {
1103
    // Figure out the label of the title field.
1104
    if (!empty($settings['title_label_use_field_label'])) {
1105
      // Use the element label as the title field label.
1106
      $title_label = $element['#title'];
1107
      // Hide the field label because there is no need for the duplicate labels.
1108
      $element['#title_display'] = 'invisible';
1109
    }
1110
    else {
1111
      $title_label = t('Title');
1112
    }
1113

    
1114
    // Default value.
1115
    $title_maxlength = 128;
1116
    if (!empty($settings['title_maxlength'])) {
1117
      $title_maxlength = $settings['title_maxlength'];
1118
    }
1119

    
1120
    $element['title'] = array(
1121
      '#type' => 'textfield',
1122
      '#maxlength' => $title_maxlength,
1123
      '#title' => $title_label,
1124
      '#description' => t('The link title is limited to @maxlength characters maximum.', array('@maxlength' => $title_maxlength)),
1125
      '#required' => ($settings['title'] == 'required' && (($element['#delta'] == 0 && $element['#required']) || !empty($element['#value']['url']))) ? TRUE : FALSE,
1126
      '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
1127
    );
1128
  }
1129
  elseif ($settings['title'] == 'select') {
1130
    $options = drupal_map_assoc(array_filter(explode("\n", str_replace("\r", "\n", trim($settings['title_allowed_values'])))));
1131
    $element['title'] = array(
1132
      '#type' => 'select',
1133
      '#title' => t('Title'),
1134
      '#description' => t('Select the a title for this link.'),
1135
      '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
1136
      '#options' => $options,
1137
      '#empty_value' => '',
1138
    );
1139
  }
1140

    
1141
  // Initialize field attributes as an array if it is not an array yet.
1142
  if (!is_array($settings['attributes'])) {
1143
    $settings['attributes'] = array();
1144
  }
1145
  // Add default attributes.
1146
  $settings['attributes'] += _link_default_attributes();
1147
  $attributes = isset($element['#value']['attributes']) ? $element['#value']['attributes'] : $settings['attributes'];
1148
  if (!empty($settings['attributes']['target']) && $settings['attributes']['target'] == LINK_TARGET_USER) {
1149
    $element['attributes']['target'] = array(
1150
      '#type' => 'checkbox',
1151
      '#title' => t('Open URL in a New Window'),
1152
      '#return_value' => LINK_TARGET_NEW_WINDOW,
1153
      '#default_value' => isset($attributes['target']) ? $attributes['target'] : FALSE,
1154
    );
1155
  }
1156
  if (!empty($settings['attributes']['configurable_title']) && $settings['attributes']['configurable_title'] == 1) {
1157
    $element['attributes']['title'] = array(
1158
      '#type' => 'textfield',
1159
      '#title' => t('Link "title" attribute'),
1160
      '#default_value' => isset($attributes['title']) ? $attributes['title'] : '',
1161
      '#field_prefix' => 'title = "',
1162
      '#field_suffix' => '"',
1163
    );
1164
  }
1165
  if (!empty($settings['attributes']['configurable_class']) && $settings['attributes']['configurable_class'] == 1) {
1166
    $element['attributes']['class'] = array(
1167
      '#type' => 'textfield',
1168
      '#title' => t('Custom link class'),
1169
      '#default_value' => isset($attributes['class']) ? $attributes['class'] : '',
1170
      '#field_prefix' => 'class = "',
1171
      '#field_suffix' => '"',
1172
    );
1173
  }
1174

    
1175
  // If the title field is available or there are field accepts multiple values
1176
  // then allow the individual field items display the required asterisk if
1177
  // needed.
1178
  if (isset($element['title']) || isset($element['_weight'])) {
1179
    // To prevent an extra required indicator, disable the required flag on the
1180
    // base element since all the sub-fields are already required if desired.
1181
    $element['#required'] = FALSE;
1182
  }
1183

    
1184
  return $element;
1185
}
1186

    
1187
/**
1188
 * Implements hook_field_formatter_info().
1189
 */
1190
function link_field_formatter_info() {
1191
  return array(
1192
    'link_default' => array(
1193
      'label' => t('Title, as link (default)'),
1194
      'field types' => array('link_field'),
1195
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1196
      'settings' => array(
1197
        'custom_title' => '',
1198
      ),
1199
    ),
1200
    'link_title_plain' => array(
1201
      'label' => t('Title, as plain text'),
1202
      'field types' => array('link_field'),
1203
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1204
    ),
1205
    'link_host' => array(
1206
      'label' => t('Host, as plain text'),
1207
      'field types' => array('link_field'),
1208
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1209
    ),
1210
    'link_url' => array(
1211
      'label' => t('URL, as link'),
1212
      'field types' => array('link_field'),
1213
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1214
    ),
1215
    'link_plain' => array(
1216
      'label' => t('URL, as plain text'),
1217
      'field types' => array('link_field'),
1218
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1219
    ),
1220
    'link_absolute' => array(
1221
      'label' => t('URL, absolute'),
1222
      'field types' => array('link_field'),
1223
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1224
    ),
1225
    'link_domain' => array(
1226
      'label' => t('Domain, as link'),
1227
      'field types' => array('link_field'),
1228
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1229
      'settings' => array(
1230
        'strip_www' => FALSE,
1231
      ),
1232
    ),
1233
    'link_no_protocol' => array(
1234
      'label' => t('URL with the protocol removed'),
1235
      'field types' => array('link_field'),
1236
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1237
    ),
1238
    'link_short' => array(
1239
      'label' => t('Short, as link with title "Link"'),
1240
      'field types' => array('link_field'),
1241
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1242
    ),
1243
    'link_label' => array(
1244
      'label' => t('Label, as link with label as title'),
1245
      'field types' => array('link_field'),
1246
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1247
    ),
1248
    'link_separate' => array(
1249
      'label' => t('Separate title and URL'),
1250
      'field types' => array('link_field'),
1251
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1252
    ),
1253
  );
1254
}
1255

    
1256
/**
1257
 * Implements hook_field_formatter_settings_form().
1258
 */
1259
function link_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
1260
  $display = $instance['display'][$view_mode];
1261
  $settings = $display['settings'];
1262
  $element = array();
1263
  if ($display['type'] == 'link_domain') {
1264
    $element['strip_www'] = array(
1265
      '#title' => t('Strip www. from domain'),
1266
      '#type' => 'checkbox',
1267
      '#default_value' => $settings['strip_www'],
1268
    );
1269
  }
1270
  if ($display['type'] == 'link_default') {
1271
    $element['custom_title'] = array(
1272
      '#title' => t('Override title'),
1273
      '#description' => t('Optionally override the title for the link(s).'),
1274
      '#type' => 'textfield',
1275
      '#default_value' => $settings['custom_title'],
1276
    );
1277
  }
1278
  return $element;
1279
}
1280

    
1281
/**
1282
 * Implements hook_field_formatter_settings_summary().
1283
 */
1284
function link_field_formatter_settings_summary($field, $instance, $view_mode) {
1285
  $display = $instance['display'][$view_mode];
1286

    
1287
  if ($display['type'] == 'link_domain') {
1288
    if ($display['settings']['strip_www']) {
1289
      return t('Strip www. from domain');
1290
    }
1291
    else {
1292
      return t('Leave www. in domain');
1293
    }
1294
  }
1295
  if ($display['type'] == 'link_default') {
1296
    if ($display['settings']['custom_title']) {
1297
      return t('Title: %title', array('%title' => $display['settings']['custom_title']));
1298
    }
1299
  }
1300
  return '';
1301
}
1302

    
1303
/**
1304
 * Implements hook_field_formatter_view().
1305
 */
1306
function link_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
1307
  $elements = array();
1308
  foreach ($items as $delta => $item) {
1309
    if (!empty($display['settings']['custom_title'])) {
1310
      $item['title'] = $display['settings']['custom_title'];
1311
    }
1312
    $elements[$delta] = array(
1313
      '#theme' => 'link_formatter_' . $display['type'],
1314
      '#element' => $item,
1315
      '#field' => $instance,
1316
      '#display' => array(
1317
        'settings' => $display['settings'],
1318
      ),
1319
    );
1320
  }
1321
  return $elements;
1322
}
1323

    
1324
/**
1325
 * Formats a link.
1326
 */
1327
function theme_link_formatter_link_default($vars) {
1328
  $link_options = $vars['element'];
1329
  unset($link_options['title']);
1330
  unset($link_options['url']);
1331

    
1332
  if (isset($link_options['attributes']['class'])) {
1333
    $link_options['attributes']['class'] = array($link_options['attributes']['class']);
1334
  }
1335
  // Display a normal link if both title and URL are available.
1336
  if (!empty($vars['element']['title']) && !empty($vars['element']['url'])) {
1337
    return l($vars['element']['title'], rawurldecode($vars['element']['url']), $link_options);
1338
  }
1339
  // If only a title, display the title.
1340
  elseif (!empty($vars['element']['title'])) {
1341
    return !empty($link_options['html']) ? $vars['element']['title'] : check_plain($vars['element']['title']);
1342
  }
1343
  elseif (!empty($vars['element']['url'])) {
1344
    return l($vars['element']['title'], rawurldecode($vars['element']['url']), $link_options);
1345
  }
1346
}
1347

    
1348
/**
1349
 * Formats a link (or its title) as plain text.
1350
 */
1351
function theme_link_formatter_link_plain($vars) {
1352
  $link_options = $vars['element'];
1353
  if (isset($link_options['title'])) {
1354
    unset($link_options['title']);
1355
  }
1356
  else {
1357
    $vars['element']['title'] = '';
1358
  }
1359
  unset($link_options['url']);
1360
  return empty($vars['element']['url']) ? check_plain($vars['element']['title']) : url($vars['element']['url'], $link_options);
1361
}
1362

    
1363
/**
1364
 * Theme function for 'host' text field formatter.
1365
 */
1366
function theme_link_formatter_link_host($vars) {
1367
  $host = @parse_url($vars['element']['url']);
1368
  return isset($host['host']) ? check_plain($host['host']) : '';
1369
}
1370

    
1371
/**
1372
 * Formats a link as an absolute URL.
1373
 */
1374
function theme_link_formatter_link_absolute($vars) {
1375
  // If no URL value is present there's no point in continuing.
1376
  if (empty($vars['element']['url'])) {
1377
    return '';
1378
  }
1379

    
1380
  // Hardcode the 'absolute' argument.
1381
  $vars['element']['absolute'] = TRUE;
1382
  return url($vars['element']['url'], $vars['element']);
1383
}
1384

    
1385
/**
1386
 * Formats a link using the URL's domain for it's link text.
1387
 */
1388
function theme_link_formatter_link_domain($vars) {
1389
  // If no URL value is present there's no point in continuing.
1390
  if (empty($vars['element']['url'])) {
1391
    return '';
1392
  }
1393

    
1394
  $link_options = $vars['element'];
1395
  unset($link_options['title']);
1396
  unset($link_options['url']);
1397
  $domain = parse_url($vars['element']['display_url'], PHP_URL_HOST);
1398
  if (!empty($vars['display']['settings']['strip_www'])) {
1399
    $domain = str_replace('www.', '', $domain);
1400
  }
1401
  return l($domain, $vars['element']['url'], $link_options);
1402
}
1403

    
1404
/**
1405
 * Formats a link without the http:// or https://.
1406
 */
1407
function theme_link_formatter_link_no_protocol($vars) {
1408
  // If no URL value is present there's no point in continuing.
1409
  if (empty($vars['element']['url'])) {
1410
    return '';
1411
  }
1412

    
1413
  $link_options = $vars['element'];
1414
  unset($link_options['title']);
1415
  unset($link_options['url']);
1416
  // We drop any scheme of the url.
1417
  $scheme = parse_url($vars['element']['url']);
1418
  $search = '/' . preg_quote($scheme['scheme'] . '://', '/') . '/';
1419
  $replace = '';
1420
  $display_url = preg_replace($search, $replace, $vars['element']['url'], 1);
1421

    
1422
  return l($display_url, $vars['element']['url'], $link_options);
1423
}
1424

    
1425
/**
1426
 * Formats a link's title as plain text.
1427
 */
1428
function theme_link_formatter_link_title_plain($vars) {
1429
  // If no title value is present there's no point in continuing.
1430
  if (empty($vars['element']['title'])) {
1431
    return '';
1432
  }
1433

    
1434
  return check_plain(decode_entities($vars['element']['title']));
1435
}
1436

    
1437
/**
1438
 * Formats a link using an alternate display URL for its link text.
1439
 */
1440
function theme_link_formatter_link_url($vars) {
1441
  // If no URL value is present there's no point in continuing.
1442
  if (empty($vars['element']['url'])) {
1443
    return '';
1444
  }
1445

    
1446
  $link_options = $vars['element'];
1447
  if (isset($link_options['attributes']['class'])) {
1448
    $link_options['attributes']['class'] = array($link_options['attributes']['class']);
1449
  }
1450
  unset($link_options['title']);
1451
  unset($link_options['url']);
1452
  return l($vars['element']['display_url'], $vars['element']['url'], $link_options);
1453
}
1454

    
1455
/**
1456
 * Formats a link using "Link" as the link text.
1457
 */
1458
function theme_link_formatter_link_short($vars) {
1459
  // If no URL value is present there's no point in continuing.
1460
  if (empty($vars['element']['url'])) {
1461
    return '';
1462
  }
1463

    
1464
  $link_options = $vars['element'];
1465
  unset($link_options['title']);
1466
  unset($link_options['url']);
1467
  return l(t('Link'), $vars['element']['url'], $link_options);
1468
}
1469

    
1470
/**
1471
 * Formats a link using the field's label as link text.
1472
 */
1473
function theme_link_formatter_link_label($vars) {
1474
  // If no URL value is present there's no point in continuing.
1475
  if (empty($vars['element']['url'])) {
1476
    return '';
1477
  }
1478

    
1479
  $link_options = $vars['element'];
1480
  unset($link_options['title']);
1481
  unset($link_options['url']);
1482
  $label = $vars['field']['label'];
1483
  if (function_exists('i18n_string_translate')) {
1484
    $i18n_string_name = "field:{$vars['field']['field_name']}:{$vars['field']['bundle']}:label";
1485
    $label = i18n_string_translate($i18n_string_name, $label);
1486
  }
1487
  return l($label, $vars['element']['url'], $link_options);
1488
}
1489

    
1490
/**
1491
 * Formats a link as separate title and URL elements.
1492
 */
1493
function theme_link_formatter_link_separate($vars) {
1494
  $class = empty($vars['element']['attributes']['class']) ? '' : ' ' . $vars['element']['attributes']['class'];
1495
  unset($vars['element']['attributes']['class']);
1496
  $link_options = $vars['element'];
1497
  unset($link_options['title']);
1498
  unset($link_options['url']);
1499
  $title = empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']);
1500

    
1501
  // @todo Static html markup looks not very elegant, needs smarter output
1502
  // solution and an optional title/URL seperator.
1503
  $url_parts = _link_parse_url($vars['element']['url']);
1504
  $output = '';
1505
  $output .= '<div class="link-item ' . $class . '">';
1506
  if (!empty($title)) {
1507
    $output .= '<div class="link-title">' . $title . '</div>';
1508
  }
1509
  $output .= '<div class="link-url">' . l($url_parts['url'], $vars['element']['url'], $link_options) . '</div>';
1510
  $output .= '</div>';
1511
  return $output;
1512
}
1513

    
1514
/**
1515
 * Implements hook_token_list().
1516
 */
1517
function link_token_list($type = 'all') {
1518
  // @todo hook_token_list() no longer exists, this should be rewritten as
1519
  // hook_token_info().
1520
  if ($type === 'field' || $type === 'all') {
1521
    $tokens = array();
1522
    $tokens['link']['url'] = t("Link URL");
1523
    $tokens['link']['title'] = t("Link title");
1524
    $tokens['link']['view'] = t("Formatted html link");
1525
    return $tokens;
1526
  }
1527
}
1528

    
1529
/**
1530
 * Implements hook_token_values().
1531
 */
1532
function link_token_values($type, $object = NULL) {
1533
  // @todo hook_token_values() no longer exists, this should be rewritten as
1534
  // hook_tokens().
1535
  if ($type === 'field') {
1536
    $item = $object[0];
1537

    
1538
    $tokens['url'] = $item['url'];
1539
    $tokens['title'] = $item['title'];
1540
    $tokens['view'] = isset($item['view']) ? $item['view'] : '';
1541

    
1542
    return $tokens;
1543
  }
1544
}
1545

    
1546
/**
1547
 * Implements hook_views_api().
1548
 */
1549
function link_views_api() {
1550
  return array(
1551
    'api' => 2,
1552
    'path' => drupal_get_path('module', 'link') . '/views',
1553
  );
1554
}
1555

    
1556
/**
1557
 * Forms a valid URL if possible from an entered address.
1558
 *
1559
 * Trims whitespace and automatically adds an http:// to addresses without a
1560
 * protocol specified.
1561
 *
1562
 * @param string $url
1563
 *   The url entered by the user.
1564
 * @param string $protocol
1565
 *   The protocol to be prepended to the url if one is not specified.
1566
 */
1567
function link_cleanup_url($url, $protocol = 'http') {
1568
  try {
1569
    link_ensure_valid_default_protocol();
1570
  }
1571
  catch (Exception $e) {
1572
    watchdog('link', $e->getMessage(), array(), WATCHDOG_ERROR);
1573
  }
1574
  $url = trim($url);
1575
  $type = link_url_type($url);
1576

    
1577
  if ($type === LINK_EXTERNAL) {
1578
    // Check if there is no protocol specified.
1579
    $protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i", $url);
1580
    if (empty($protocol_match)) {
1581
      // But should there be? Add an automatic http:// if it starts with a
1582
      // domain name.
1583
      $link_domains = _link_domains();
1584
      $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)(' . $link_domains . '|[a-z]{2}))/i', $url);
1585
      if (!empty($domain_match)) {
1586
        $url = $protocol . "://" . $url;
1587
      }
1588
    }
1589
  }
1590

    
1591
  return $url;
1592
}
1593

    
1594
/**
1595
 * Validate that the protocol is either HTTP or HTTPS.
1596
 *
1597
 * @throws Exception
1598
 */
1599
function link_ensure_valid_default_protocol() {
1600
  $protocol = variable_get('link_default_protocol', LINK_HTTP_PROTOCOL);
1601
  if ($protocol !== LINK_HTTP_PROTOCOL && $protocol !== LINK_HTTPS_PROTOCOL) {
1602
    variable_set('link_default_protocol', LINK_HTTP_PROTOCOL);
1603
    throw new Exception(t('Protocol was set to !protocol but must be !HTTP or !HTTPS. Set to default HTTP.', array(
1604
      '!protocol' => $protocol,
1605
      '!HTTP' => LINK_HTTP_PROTOCOL,
1606
      '!HTTPS' => LINK_HTTPS_PROTOCOL,
1607
    )));
1608
  }
1609
}
1610

    
1611
/**
1612
 * Validates a URL.
1613
 *
1614
 * @param string $text
1615
 *   Url to be validated.
1616
 * @param string $langcode
1617
 *   An optional language code to look up the path in.
1618
 *
1619
 * @return bool
1620
 *   True if a valid link, FALSE otherwise.
1621
 */
1622
function link_validate_url($text, $langcode = NULL) {
1623
  $text = _link_clean_relative($text);
1624
  $text = link_cleanup_url($text);
1625
  $type = link_url_type($text);
1626

    
1627
  if ($type && ($type == LINK_INTERNAL || $type == LINK_EXTERNAL)) {
1628
    $flag = valid_url($text, $type == LINK_EXTERNAL);
1629
    if (!$flag) {
1630
      $normal_path = drupal_get_normal_path($text, $langcode);
1631
      $parsed_link = parse_url($normal_path, PHP_URL_PATH);
1632
      if ($normal_path != $parsed_link) {
1633
        $normal_path = $parsed_link;
1634
      }
1635
      $flag = drupal_valid_path($normal_path);
1636
    }
1637
    if (!$flag) {
1638
      $flag = file_exists($normal_path);
1639
    }
1640
    if (!$flag) {
1641
      $uri = file_build_uri($normal_path);
1642
      $flag = file_exists($uri);
1643
    }
1644
  }
1645
  else {
1646
    $flag = (bool) $type;
1647
  }
1648

    
1649
  return $flag;
1650
}
1651

    
1652
/**
1653
 * Cleaner of relatives urls.
1654
 *
1655
 * @param string $url
1656
 *   The url to clean up the relative protocol.
1657
 */
1658
function _link_clean_relative($url) {
1659
  $check = substr($url, 0, 2);
1660
  if (isset($_SERVER['HTTPS']) &&
1661
    ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1) ||
1662
    isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
1663
    $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
1664
    $protocol = 'https://';
1665
  }
1666
  else {
1667
    $protocol = 'http://';
1668
  }
1669

    
1670
  if ($check == '//') {
1671
    $url = str_replace('//', $protocol, $url);
1672
  }
1673

    
1674
  return $url;
1675
}
1676

    
1677
/**
1678
 * Type check a URL.
1679
 *
1680
 * Accepts all URLs following RFC 1738 standard for URL formation and all e-mail
1681
 * addresses following the RFC 2368 standard for mailto address formation.
1682
 *
1683
 * @param string $text
1684
 *   Url to be checked.
1685
 *
1686
 * @return mixed
1687
 *   Returns boolean FALSE if the URL is not valid. On success, returns one of
1688
 *   the LINK_(linktype) constants.
1689
 */
1690
function link_url_type($text) {
1691
  // @todo Complete letters.
1692
  // @codingStandardsIgnoreStart
1693
  $link_ichars_domain = (string) html_entity_decode(implode("", array(
1694
    "&#x00BF;", // ¿
1695
    "&#x00C0;", // À
1696
    "&#x00C1;", // Á
1697
    "&#x00C2;", // Â
1698
    "&#x00C3;", // Ã
1699
    "&#x00C4;", // Ä
1700
    "&#x00C5;", // Å
1701
    "&#x00C6;", // Æ
1702
    "&#x00C7;", // Ç
1703
    "&#x00C8;", // È
1704
    "&#x00C9;", // É
1705
    "&#x00CA;", // Ê
1706
    "&#x00CB;", // Ë
1707
    "&#x00CC;", // Ì
1708
    "&#x00CD;", // Í
1709
    "&#x00CE;", // Î
1710
    "&#x00CF;", // Ï
1711
    "&#x00D0;", // Ð
1712
    "&#x00D1;", // Ñ
1713
    "&#x00D2;", // Ò
1714
    "&#x00D3;", // Ó
1715
    "&#x00D4;", // Ô
1716
    "&#x00D5;", // Õ
1717
    "&#x00D6;", // Ö
1718
    // ×
1719
    "&#x00D8;", // Ø
1720
    "&#x00D9;", // Ù
1721
    "&#x00DA;", // Ú
1722
    "&#x00DB;", // Û
1723
    "&#x00DC;", // Ü
1724
    "&#x00DD;", // Ý
1725
    "&#x00DE;", // Þ
1726
    // ß (see LINK_ICHARS)
1727
    "&#x00E0;", // à
1728
    "&#x00E1;", // á
1729
    "&#x00E2;", // â
1730
    "&#x00E3;", // ã
1731
    "&#x00E4;", // ä
1732
    "&#x00E5;", // å
1733
    "&#x00E6;", // æ
1734
    "&#x00E7;", // ç
1735
    "&#x00E8;", // è
1736
    "&#x00E9;", // é
1737
    "&#x00EA;", // ê
1738
    "&#x00EB;", // ë
1739
    "&#x00EC;", // ì
1740
    "&#x00ED;", // í
1741
    "&#x00EE;", // î
1742
    "&#x00EF;", // ï
1743
    "&#x00F0;", // ð
1744
    "&#x00F1;", // ñ
1745
    "&#x00F2;", // ò
1746
    "&#x00F3;", // ó
1747
    "&#x00F4;", // ô
1748
    "&#x00F5;", // õ
1749
    "&#x00F6;", // ö
1750
    // ÷
1751
    "&#x00F8;", // ø
1752
    "&#x00F9;", // ù
1753
    "&#x00FA;", // ú
1754
    "&#x00FB;", // û
1755
    "&#x00FC;", // ü
1756
    "&#x00FD;", // ý
1757
    "&#x00FE;", // þ
1758
    "&#x00FF;", // ÿ
1759
    "&#x0152;", // Œ
1760
    "&#x0153;", // œ
1761
    "&#x0178;", // Ÿ
1762
  )), ENT_QUOTES, 'UTF-8');
1763
  // @codingStandardsIgnoreEnd
1764

    
1765
  $link_ichars = $link_ichars_domain . (string) html_entity_decode(implode("", array(
1766
    // ß.
1767
    "&#x00DF;",
1768
  )), ENT_QUOTES, 'UTF-8');
1769
  $allowed_protocols = variable_get('filter_allowed_protocols', array(
1770
    'http',
1771
    'https',
1772
    'ftp',
1773
    'file',
1774
    'news',
1775
    'nntp',
1776
    'telnet',
1777
    'mailto',
1778
    'irc',
1779
    'ssh',
1780
    'sftp',
1781
    'webcal',
1782
    'tel',
1783
  ));
1784
  $link_domains = _link_domains();
1785

    
1786
  // Starting a parenthesis group with (?: means that it is grouped, but is not
1787
  // captured.
1788
  $protocol = '((?:' . implode("|", $allowed_protocols) . '):\/\/)';
1789
  $authentication = "(?:(?:(?:[\w\.\-\+!$&'\(\)*\+,;=" . $link_ichars . "]|%[0-9a-f]{2})+(?::(?:[\w" . $link_ichars . "\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})*)?)?@)";
1790
  $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}))?)';
1791
  $ipv4 = '(?:[0-9]{1,3}(\.[0-9]{1,3}){3})';
1792
  $ipv6 = '(?:[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7})';
1793
  $port = '(?::([0-9]{1,5}))';
1794
  // Pattern specific to external links.
1795
  $external_pattern = '/^' . $protocol . '?' . $authentication . '?(' . $domain . '|' . $ipv4 . '|' . $ipv6 . ' |localhost)' . $port . '?';
1796

    
1797
  // Pattern specific to internal links.
1798
  $internal_pattern = "/^(?:[a-z0-9" . $link_ichars . "_\-+\[\] ]+)";
1799
  $internal_pattern_file = "/^(?:[a-z0-9" . $link_ichars . "_\-+\[\]\. \/\(\)][a-z0-9" . $link_ichars . "_\-+\[\]\. \(\)][a-z0-9" . $link_ichars . "_\-+\[\]\. \/\(\)]+)$/i";
1800

    
1801
  $directories = "(?:\/[a-z0-9" . $link_ichars . "_\-\.~+%=&,$'#!():;*@\[\]]*)*";
1802
  // Yes, four backslashes == a single backslash.
1803
  $query = "(?:\/?\?([?a-zA-Z0-9" . $link_ichars . "+_|\-\.~\/\\\\%=&,$'!():;*@\[\]{} ]*))";
1804
  $anchor = "(?:#[a-zA-Z0-9" . $link_ichars . "_\-\.~+%=&,$'():;*@\[\]\/\?!]*)";
1805

    
1806
  // The rest of the path for a standard URL.
1807
  // @codingStandardsIgnoreLine
1808
  $end = $directories . '?' . $query . '?' . $anchor . '?' . '$/i';
1809

    
1810
  $message_id = '[^@].*@' . $domain;
1811
  $newsgroup_name = '(?:[0-9a-z+-]*\.)*[0-9a-z+-]*';
1812
  $news_pattern = '/^news:(' . $newsgroup_name . '|' . $message_id . ')$/i';
1813

    
1814
  $user = '[a-zA-Z0-9' . $link_ichars . '_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+';
1815
  $email_pattern = '/^mailto:' . $user . '@' . '(?:' . $domain . '|' . $ipv4 . '|' . $ipv6 . '|localhost)' . $query . '?$/';
1816
  $tel_pattern = '/^tel:(?:\+[1-9]\d{1,14}|\d{2,15})$/';
1817

    
1818
  $file_pattern = "/^(?:file:\/\/)" . "(?:\/?[a-z0-9" . $link_ichars . "_\-\.\\\~+%=&,$'#!():;*@\[\]]*)*" . '$/i';
1819

    
1820
  if (strpos($text, '<front>') === 0) {
1821
    return LINK_FRONT;
1822
  }
1823
  if (in_array('mailto', $allowed_protocols) && preg_match($email_pattern, $text)) {
1824
    return LINK_EMAIL;
1825
  }
1826
  if (strpos($text, '#') === 0) {
1827
    return LINK_FRAGMENT;
1828
  }
1829
  if (strpos($text, '?') === 0) {
1830
    return LINK_QUERY;
1831
  }
1832
  if (in_array('tel', $allowed_protocols) && strpos($text, 'tel:') === 0) {
1833
    if (preg_match($tel_pattern, $text)) {
1834
      // Based on our tel pattern this is a 'valid' phone number so return tel
1835
      // type.
1836
      return LINK_TEL;
1837
    }
1838
    else {
1839
      // Based on our tel pattern this is using the tel protocol, but is not a
1840
      // 'valid' phone number. If we don't return false here $text will match
1841
      // LINK_EXTERNAL which is incorrect.
1842
      return FALSE;
1843
    }
1844
  }
1845
  if (in_array('news', $allowed_protocols) && preg_match($news_pattern, $text)) {
1846
    return LINK_NEWS;
1847
  }
1848
  if (in_array('file', $allowed_protocols) && preg_match($file_pattern, $text, $as)) {
1849
    return LINK_FILE;
1850
  }
1851
  if (preg_match($internal_pattern . $end, $text)) {
1852
    return LINK_INTERNAL;
1853
  }
1854
  if (drupal_valid_path($text) && url_is_external($text) == FALSE) {
1855
    return LINK_INTERNAL;
1856
  }
1857
  if (preg_match($external_pattern . $end, $text)) {
1858
    return LINK_EXTERNAL;
1859
  }
1860
  if (preg_match($internal_pattern_file, $text)) {
1861
    return LINK_INTERNAL;
1862
  }
1863

    
1864
  return FALSE;
1865
}
1866

    
1867
/**
1868
 * Returns the list of allowed domains.
1869
 *
1870
 * If the variable link_allowed_domains is set, restrict allowed domains to the
1871
 * strings in that array. If the variable link_allowed_domains is not set, allow
1872
 * all domains between 2 and 63 characters in length.
1873
 * See https://tools.ietf.org/html/rfc1034.
1874
 */
1875
function _link_domains() {
1876
  $link_allowed_domains = variable_get('link_allowed_domains', array());
1877
  return empty($link_allowed_domains) ? '[a-z][a-z0-9-]{1,62}' : implode('|', $link_allowed_domains);
1878
}
1879

    
1880
/**
1881
 * Implements hook_migrate_field_alter().
1882
 */
1883
function link_content_migrate_field_alter(&$field_value, $instance_value) {
1884
  if ($field_value['type'] == 'link') {
1885
    // Adjust the field type.
1886
    $field_value['type'] = 'link_field';
1887
    // Remove settings that are now on the instance.
1888
    foreach (array(
1889
      'attributes',
1890
      'display',
1891
      'url',
1892
      'title',
1893
      'title_value',
1894
      'enable_tokens',
1895
      'validate_url',
1896
    ) as $setting) {
1897
      unset($field_value['settings'][$setting]);
1898
    }
1899
  }
1900
}
1901

    
1902
/**
1903
 * Implements hook_migrate_instance_alter().
1904
 *
1905
 * Widget type also changed to link_field.
1906
 */
1907
function link_content_migrate_instance_alter(&$instance_value, $field_value) {
1908
  if ($field_value['type'] == 'link') {
1909
    // Grab settings that were previously on the field.
1910
    foreach (array(
1911
      'attributes',
1912
      'display',
1913
      'url',
1914
      'title',
1915
      'title_value',
1916
      'enable_tokens',
1917
      'validate_url',
1918
    ) as $setting) {
1919
      if (isset($field_value['settings'][$setting])) {
1920
        $instance_value['settings'][$setting] = $field_value['settings'][$setting];
1921
      }
1922
    }
1923
    // Adjust widget type.
1924
    if ($instance_value['widget']['type'] == 'link') {
1925
      $instance_value['widget']['type'] = 'link_field';
1926
    }
1927
    // Adjust formatter types.
1928
    foreach ($instance_value['display'] as $context => $settings) {
1929
      if (in_array($settings['type'], array(
1930
        'default',
1931
        'title_plain',
1932
        'url',
1933
        'plain',
1934
        'short',
1935
        'label',
1936
        'separate',
1937
      ))) {
1938
        $instance_value['display'][$context]['type'] = 'link_' . $settings['type'];
1939
      }
1940
    }
1941
  }
1942
}
1943

    
1944
/**
1945
 * Implements hook_field_settings_form().
1946
 */
1947
function link_field_settings_form() {
1948
  return array();
1949
}
1950

    
1951
/**
1952
 * Additional callback to adapt the property info of link fields.
1953
 *
1954
 * @see entity_metadata_field_entity_property_info()
1955
 */
1956
function link_field_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
1957
  $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
1958
  // Define a data structure so it's possible to deal with both the link title
1959
  // and URL.
1960
  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
1961
  $property['setter callback'] = 'entity_metadata_field_verbatim_set';
1962

    
1963
  // Auto-create the field item as soon as a property is set.
1964
  $property['auto creation'] = 'link_field_item_create';
1965

    
1966
  $property['property info'] = link_field_item_property_info();
1967
  $property['property info']['url']['required'] = $instance['required'] && !$instance['settings']['url'];
1968
  $property['property info']['title']['required'] = $instance['required'] && ($instance['settings']['title'] == 'required');
1969
  if ($instance['settings']['title'] == 'none') {
1970
    unset($property['property info']['title']);
1971
  }
1972
  unset($property['query callback']);
1973
}
1974

    
1975
/**
1976
 * Callback for creating a new, empty link field item.
1977
 *
1978
 * @see link_field_property_info_callback()
1979
 */
1980
function link_field_item_create() {
1981
  return array('title' => NULL, 'url' => NULL, 'display_url' => NULL);
1982
}
1983

    
1984
/**
1985
 * Defines info for the properties of the link-field item data structure.
1986
 */
1987
function link_field_item_property_info() {
1988
  $properties['title'] = array(
1989
    'type' => 'text',
1990
    'label' => t('The title of the link.'),
1991
    'setter callback' => 'entity_property_verbatim_set',
1992
  );
1993
  $properties['url'] = array(
1994
    'type' => 'text',
1995
    'label' => t('The URL of the link.'),
1996
    'setter callback' => 'entity_property_verbatim_set',
1997
    'getter callback' => 'link_url_property_get',
1998
  );
1999
  $properties['attributes'] = array(
2000
    'type' => 'struct',
2001
    'label' => t('The attributes of the link.'),
2002
    'setter callback' => 'entity_property_verbatim_set',
2003
    'getter callback' => 'link_attribute_property_get',
2004
  );
2005
  $properties['display_url'] = array(
2006
    'type' => 'uri',
2007
    'label' => t('The full URL of the link.'),
2008
    'setter callback' => 'entity_property_verbatim_set',
2009
  );
2010
  return $properties;
2011
}
2012

    
2013
/**
2014
 * Callback for getting the URL property.
2015
 *
2016
 * @see entity_metadata_entity_get_properties()
2017
 */
2018
function link_url_property_get($data, array $options, $name, $type, $info) {
2019
  $url = entity_property_verbatim_get($data, $options, $name, $type, $info);
2020

    
2021
  return url($url, $data + $options);
2022
}
2023

    
2024
/**
2025
 * Entity property info getter callback for link attributes.
2026
 */
2027
function link_attribute_property_get($data, array $options, $name, $type, $info) {
2028
  return isset($data[$name]) ? array_filter($data[$name]) : array();
2029
}
2030

    
2031
/**
2032
 * Implements hook_field_update_instance().
2033
 */
2034
function link_field_update_instance($instance, $prior_instance) {
2035
  if (function_exists('i18n_string_update') && isset($instance['widget']) && $instance['widget']['type'] == 'link_field' && $prior_instance['settings']['title_value'] != $instance['settings']['title_value']) {
2036
    $i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value";
2037
    i18n_string_update($i18n_string_name, $instance['settings']['title_value']);
2038
  }
2039
}
2040

    
2041
/**
2042
 * Implements hook_i18n_string_list_TEXTGROUP_alter().
2043
 */
2044
function link_i18n_string_list_field_alter(&$strings, $type = NULL, $object = NULL) {
2045
  if ($type != 'field_instance' || !is_array($object) || !isset($object['widget']['type'])) {
2046
    return;
2047
  }
2048
  if ($object['widget']['type'] == 'link_field' && isset($object['settings']['title_value'])) {
2049
    $strings['field'][$object['field_name']][$object['bundle']]['title_value']['string'] = $object['settings']['title_value'];
2050
  }
2051
}