Projet

Général

Profil

Paste
Télécharger (48,2 ko) Statistiques
| Branche: | Révision:

root / htmltest / sites / all / modules / link / link.module @ a5572547

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_EMAIL', 'email');
12
define('LINK_NEWS', 'news');
13
define('LINK_DOMAINS', 'aero|arpa|asia|biz|com|cat|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|mobi|local|xxx');
14

    
15
define('LINK_TARGET_DEFAULT', 'default');
16
define('LINK_TARGET_NEW_WINDOW', '_blank');
17
define('LINK_TARGET_TOP', '_top');
18
define('LINK_TARGET_USER', 'user');
19

    
20
/**
21
 * Maximum URLs length - needs to match value in link.install.
22
 */
23
define('LINK_URL_MAX_LENGTH', 2048);
24

    
25
/**
26
 * Implements hook_field_info().
27
 */
28
function link_field_info() {
29
  return array(
30
    'link_field' => array(
31
      'label' => t('Link'),
32
      'description' => t('Store a title, href, and attributes in the database to assemble a link.'),
33
      'settings' => array(
34
        'attributes' => _link_default_attributes(),
35
        'url' => 0,
36
        'title' => 'optional',
37
        'title_value' => '',
38
        'title_maxlength' => 128,
39
        'enable_tokens' => 1,
40
        'display' => array(
41
          'url_cutoff' => 80,
42
        ),
43
      ),
44
      'instance_settings' => array(
45
        'attributes' => _link_default_attributes(),
46
        'url' => 0,
47
        'title' => 'optional',
48
        'title_value' => '',
49
        'title_maxlength' => 128,
50
        'enable_tokens' => 1,
51
        'display' => array(
52
          'url_cutoff' => 80,
53
        ),
54
        'validate_url' => 1,
55
      ),
56
      'default_widget' => 'link_field',
57
      'default_formatter' => 'link_default',
58
      // Support hook_entity_property_info() from contrib "Entity API".
59
      'property_type' => 'field_item_link',
60
      'property_callbacks' => array('link_field_property_info_callback'),
61
    ),
62
  );
63
}
64

    
65
/**
66
 * Implements hook_field_instance_settings_form().
67
 */
68
function link_field_instance_settings_form($field, $instance) {
69
  $form = array(
70
    '#element_validate' => array('link_field_settings_form_validate'),
71
  );
72

    
73
  $form['validate_url'] = array(
74
    '#type' => 'checkbox',
75
    '#title' => t('Validate URL'),
76
    '#default_value' => isset($instance['settings']['validate_url']) && ($instance['settings']['validate_url'] !== '') ? $instance['settings']['validate_url'] : TRUE,
77
    '#description' => t('If checked, the URL field will be verified as a valid URL during validation.'),
78
  );
79

    
80
  $form['url'] = array(
81
    '#type' => 'checkbox',
82
    '#title' => t('Optional URL'),
83
    '#default_value' => isset($instance['settings']['url']) ? $instance['settings']['url'] : '',
84
    '#return_value' => 'optional',
85
    '#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.'),
86
  );
87

    
88
  $title_options = array(
89
    'optional' => t('Optional Title'),
90
    'required' => t('Required Title'),
91
    'value' => t('Static Title'),
92
    'none' => t('No Title'),
93
  );
94

    
95
  $form['title'] = array(
96
    '#type' => 'radios',
97
    '#title' => t('Link Title'),
98
    '#default_value' => isset($instance['settings']['title']) ? $instance['settings']['title'] : 'optional',
99
    '#options' => $title_options,
100
    '#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.'),
101
  );
102

    
103
  $form['title_value'] = array(
104
    '#type' => 'textfield',
105
    '#title' => t('Static title'),
106
    '#default_value' => isset($instance['settings']['title_value']) ? $instance['settings']['title_value'] : '',
107
    '#description' => t('This title will always be used if &ldquo;Static Title&rdquo; is selected above.'),
108
  );
109

    
110
  $form['title_maxlength'] = array(
111
    '#type' => 'textfield',
112
    '#title' => t('Max length of title field'),
113
    '#default_value' => isset($instance['settings']['title_maxlength']) ? $instance['settings']['title_maxlength'] : '128',
114
    '#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.'),
115
    '#maxlength' => 3,
116
    '#size' => 3,
117
  );
118

    
119
  if (module_exists('token')) {
120
    // Add token module replacements fields
121
    $form['enable_tokens'] = array(
122
      '#type' => 'checkbox',
123
      '#title' => t('Allow user-entered tokens'),
124
      '#default_value' => isset($instance['settings']['enable_tokens']) ? $instance['settings']['enable_tokens'] : 1,
125
      '#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.'),
126
    );
127
    
128
    $entity_info = entity_get_info($instance['entity_type']);
129
    $form['tokens_help'] = array(
130
      '#theme' => 'token_tree',
131
      '#token_types' => array($entity_info['token type']),
132
      '#global_types' => TRUE,
133
      '#click_insert' => TRUE,
134
      '#dialog' => TRUE,
135
    );
136
  }
137

    
138
  $form['display'] = array(
139
    '#tree' => TRUE,
140
  );
141
  $form['display']['url_cutoff'] = array(
142
    '#type' => 'textfield',
143
    '#title' => t('URL Display Cutoff'),
144
    '#default_value' => isset($instance['settings']['display']['url_cutoff']) ? $instance['settings']['display']['url_cutoff'] : '80',
145
    '#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.'),
146
    '#maxlength' => 3,
147
    '#size' => 3,
148
  );
149

    
150
  $target_options = array(
151
    LINK_TARGET_DEFAULT => t('Default (no target attribute)'),
152
    LINK_TARGET_TOP => t('Open link in window root'),
153
    LINK_TARGET_NEW_WINDOW => t('Open link in new window'),
154
    LINK_TARGET_USER => t('Allow the user to choose'),
155
  );
156
  $form['attributes'] = array(
157
    '#tree' => TRUE,
158
  );
159
  $form['attributes']['target'] = array(
160
    '#type' => 'radios',
161
    '#title' => t('Link Target'),
162
    '#default_value' => empty($instance['settings']['attributes']['target']) ? LINK_TARGET_DEFAULT : $instance['settings']['attributes']['target'],
163
    '#options' => $target_options,
164
  );
165
  $form['attributes']['rel'] = array(
166
    '#type' => 'textfield',
167
    '#title' => t('Rel Attribute'),
168
    '#description' => t('When output, this link will have this rel attribute. The most common usage is <a href="http://en.wikipedia.org/wiki/Nofollow">rel=&quot;nofollow&quot;</a> which prevents some search engines from spidering entered links.'),
169
    '#default_value' => empty($instance['settings']['attributes']['rel']) ? '' : $instance['settings']['attributes']['rel'],
170
    '#field_prefix' => 'rel = "',
171
    '#field_suffix' => '"',
172
    '#size' => 20,
173
  );
174
  $rel_remove_options = array(
175
    'default' => t('Keep rel as set up above (untouched/default)'),
176
    'rel_remove_external' => t('Remove rel if given link is external'),
177
    'rel_remove_internal' => t('Remove rel if given link is internal'),
178
  );
179
  $form['rel_remove'] = array(
180
    '#type' => 'radios',
181
    '#title' => t('Remove rel attribute automatically'),
182
    '#default_value' => !isset($instance['settings']['rel_remove']) ? 'default' : $instance['settings']['rel_remove'],
183
    '#description' => t('Turn on/off if rel attribute should be removed automatically, if user given link is internal/external'),
184
    '#options' => $rel_remove_options,
185
  );
186
  $form['attributes']['class'] = array(
187
    '#type' => 'textfield',
188
    '#title' => t('Additional CSS Class'),
189
    '#description' => t('When output, this link will have this class attribute. Multiple classes should be separated by spaces.'),
190
    '#default_value' => empty($instance['settings']['attributes']['class']) ? '' : $instance['settings']['attributes']['class'],
191
  );
192
  $form['attributes']['configurable_title'] = array(
193
    '#title' => t("Allow the user to enter a link 'title' attribute"),
194
    '#type' => 'checkbox',
195
    '#default_value' => empty($instance['settings']['attributes']['configurable_title']) ? '' : $instance['settings']['attributes']['configurable_title'],
196
  );
197
  $form['attributes']['title'] = array(
198
    '#title' => t("Default link 'title' Attribute"),
199
    '#type' => 'textfield',
200
    '#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">WCAG 1.0 Guidelines</a> for links comformances. Tokens values will be evaluated.'),
201
    '#default_value' => empty($instance['settings']['attributes']['title']) ? '' : $instance['settings']['attributes']['title'],
202
    '#field_prefix' => 'title = "',
203
    '#field_suffix' => '"',
204
    '#size' => 20,
205
  );
206
  return $form;
207
}
208

    
209
/**
210
 * #element_validate handler for link_field_instance_settings_form().
211
 */
212
function link_field_settings_form_validate($element, &$form_state, $complete_form) {
213
  if ($form_state['values']['instance']['settings']['title'] === 'value' && empty($form_state['values']['instance']['settings']['title_value'])) {
214
    form_set_error('title_value', t('A default title must be provided if the title is a static value.'));
215
  }
216
  if (!empty($form_state['values']['instance']['settings']['display']['url_cutoff']) && !is_numeric($form_state['values']['instance']['settings']['display']['url_cutoff'])) {
217
    form_set_error('display', t('URL Display Cutoff value must be numeric.'));
218
  }
219
  if (empty($form_state['values']['instance']['settings']['title_maxlength'])) {
220
    form_set_value($element['title_maxlength'], '128', $form_state);
221
  }
222
  elseif (!is_numeric($form_state['values']['instance']['settings']['title_maxlength'])) {
223
    form_set_error('title_maxlength', t('The max length of the link title must be numeric.'));
224
  }
225
  elseif ($form_state['values']['instance']['settings']['title_maxlength'] > 255) {
226
    form_set_error('title_maxlength', t('The max length of the link title cannot be greater than 255 characters.'));
227
  }
228
}
229

    
230
/**
231
 * Implements hook_field_is_empty().
232
 */
233
function link_field_is_empty($item, $field) {
234
  return empty($item['title']) && empty($item['url']);
235
}
236

    
237
/**
238
 * Implements hook_field_load().
239
 */
240
function link_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
241
  foreach ($entities as $id => $entity) {
242
    foreach ($items[$id] as $delta => $item) {
243
      $items[$id][$delta]['attributes'] = _link_load($field, $item, $instances[$id]);
244
    }
245
  }
246
}
247

    
248
/**
249
 * Implements hook_field_validate().
250
 */
251
function link_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
252
  $optional_field_found = FALSE;
253
  if ($instance['settings']['validate_url'] !== 0 || is_null($instance['settings']['validate_url']) || !isset($instance['settings']['validate_url'])) {
254
    foreach ($items as $delta => $value) {
255
      _link_validate($items[$delta], $delta, $field, $entity, $instance, $langcode, $optional_field_found, $errors);
256
    }
257
  }
258

    
259
  if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] && !$optional_field_found) {
260
    $errors[$field['field_name']][$langcode][0][] = array(
261
      'error' => 'link_required',
262
      'message' => t('At least one title or URL must be entered.'),
263
      'error_element' => array('url' => FALSE, 'title' => TRUE),
264
    );
265
  }
266
}
267

    
268
/**
269
 * Implements hook_field_insert().
270
 */
271
function link_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
272
  foreach ($items as $delta => $value) {
273
    _link_process($items[$delta], $delta, $field, $entity);
274
  }
275
}
276

    
277
/**
278
 * Implements hook_field_update().
279
 */
280
function link_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
281
  foreach ($items as $delta => $value) {
282
    _link_process($items[$delta], $delta, $field, $entity);
283
  }
284
}
285

    
286
/**
287
 * Implements hook_field_prepare_view().
288
 */
289
function link_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
290
  foreach ($items as $entity_id => $entity_items) {
291
    foreach ($entity_items as $delta => $value) {
292
      _link_sanitize($items[$entity_id][$delta], $delta, $field, $instances[$entity_id], $entities[$entity_id]);
293
    }
294
  }
295
}
296

    
297
/**
298
 * Implements hook_field_widget_info().
299
 */
300
function link_field_widget_info() {
301
  return array(
302
    'link_field' => array(
303
      'label' => 'Link',
304
      'field types' => array('link_field'),
305
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
306
    ),
307
  );
308
}
309

    
310
/**
311
 * Implements hook_field_widget_form().
312
 */
313
function link_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
314
  $element += array(
315
    '#type' => $instance['widget']['type'],
316
    '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
317
  );
318
  return $element;
319
}
320

    
321
/**
322
 * Implements hook_field_widget_error().
323
 */
324
function link_field_widget_error($element, $error, $form, &$form_state) {
325
  if ($error['error_element']['title']) {
326
    form_error($element['title'], $error['message']);
327
  }
328
  elseif ($error['error_element']['url']) {
329
    form_error($element['url'], $error['message']);
330
  }
331
}
332

    
333
/**
334
 * Unpacks the item attributes for use.
335
 */
336
function _link_load($field, $item, $instance) {
337
  if (isset($item['attributes'])) {
338
    if (!is_array($item['attributes'])) {
339
      $item['attributes'] = unserialize($item['attributes']);
340
    }
341
    return $item['attributes'];
342
  }
343
  elseif (isset($instance['settings']['attributes'])) {
344
    return $instance['settings']['attributes'];
345
  }
346
  else {
347
    return $field['settings']['attributes'];
348
  }
349
}
350

    
351
/**
352
 * Prepares the item attributes and url for storage.
353
 */
354
function _link_process(&$item, $delta = 0, $field, $entity) {
355
  // Trim whitespace from URL.
356
  $item['url'] = trim($item['url']);
357

    
358
  // If no attributes are set then make sure $item['attributes'] is an empty
359
  // array, so $field['attributes'] can override it.
360
  if (empty($item['attributes'])) {
361
    $item['attributes'] = array();
362
  }
363

    
364
  // Serialize the attributes array.
365
  if (!is_string($item['attributes'])) {
366
    $item['attributes'] = serialize($item['attributes']);
367
  }
368

    
369
  // Don't save an invalid default value (e.g. 'http://').
370
  if ((isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url']) && is_object($entity)) {
371
    if (!link_validate_url($item['url'])) {
372
      unset($item['url']);
373
    }
374
  }
375
}
376

    
377
/**
378
 * Validates that the link field has been entered properly.
379
 */
380
function _link_validate(&$item, $delta, $field, $entity, $instance, $langcode, &$optional_field_found, &$errors) {
381
  if ($item['url'] && !(isset($instance['default_value'][$delta]['url']) && $item['url'] === $instance['default_value'][$delta]['url'] && !$instance['required'])) {
382
    // Validate the link.
383
    if (link_validate_url(trim($item['url'])) == FALSE) {
384
      $errors[$field['field_name']][$langcode][$delta][] = array(
385
        'error' => 'link_required',
386
        'message' => t('The value provided for %field is not a valid URL.', array('%field' => $instance['label'])),
387
        'error_element' => array('url' => TRUE, 'title' => FALSE),
388
      );
389
    }
390
    // Require a title for the link if necessary.
391
    if ($instance['settings']['title'] == 'required' && strlen(trim($item['title'])) == 0) {
392
      $errors[$field['field_name']][$langcode][$delta][] = array(
393
        'error' => 'link_required',
394
        'message' => t('Titles are required for all links.'),
395
        'error_element' => array('url' => FALSE, 'title' => TRUE),
396
      );
397
    }
398
  }
399
  // Require a link if we have a title.
400
  if ($instance['settings']['url'] !== 'optional' && strlen(isset($item['title']) ? $item['title'] : NULL) > 0 && strlen(trim($item['url'])) == 0) {
401
    $errors[$field['field_name']][$langcode][$delta][] = array(
402
      'error' => 'link_required',
403
      'message' => t('You cannot enter a title without a link url.'),
404
      'error_element' => array('url' => TRUE, 'title' => FALSE),
405
    );
406
  }
407
  // In a totally bizzaro case, where URLs and titles are optional but the field is required, ensure there is at least one link.
408
  if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && (strlen(trim($item['url'])) !== 0 || strlen(trim($item['title'])) !== 0)) {
409
    $optional_field_found = TRUE;
410
  }
411
  // Require entire field
412
  if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] == 1 && !$optional_field_found && isset($instance['id'])) {
413
    $errors[$field['field_name']][$langcode][$delta][] = array(
414
      'error' => 'link_required',
415
      'message' => t('At least one title or URL must be entered.'),
416
      'error_element' => array('url' => FALSE, 'title' => TRUE),
417
    );
418
  }
419
}
420

    
421
/**
422
 * Clean up user-entered values for a link field according to field settings.
423
 *
424
 * @param $item
425
 *   A single link item, usually containing url, title, and attributes.
426
 * @param $delta
427
 *   The delta value if this field is one of multiple fields.
428
 * @param $field
429
 *   The CCK field definition.
430
 * @param $entity
431
 *   The entity containing this link.
432
 */
433
function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) {
434
  // Don't try to process empty links.
435
  if (empty($item['url']) && empty($item['title'])) {
436
    return;
437
  }
438

    
439
  // Replace URL tokens.
440
  $entity_type = $instance['entity_type'];
441
  $entity_info = entity_get_info($entity_type);
442
  $property_id = $entity_info['entity keys']['id'];
443
  $entity_token_type = isset($entity_info['token type']) ? $entity_info['token type'] : (
444
    $entity_type == 'taxonomy_term' || $entity_type == 'taxonomy_vocabulary' ? str_replace('taxonomy_', '', $entity_type) : $entity_type
445
  );
446
  if (isset($instance['settings']['enable_tokens']) && $instance['settings']['enable_tokens']) {
447
    global $user;
448
    // Load the entity if necessary for entities in views.
449
    if (isset($entity->{$property_id})) {
450
      $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
451
      $entity_loaded = array_pop($entity_loaded);
452
    }
453
    else {
454
      $entity_loaded = $entity;
455
    }
456
    $item['url'] = token_replace($item['url'], array($entity_token_type => $entity_loaded));
457
  }
458

    
459
  $type = link_validate_url($item['url']);
460
  // If the type of the URL cannot be determined and URL validation is disabled,
461
  // then assume LINK_EXTERNAL for later processing.
462
  if ($type == FALSE && $instance['settings']['validate_url'] === 0) {
463
    $type = LINK_EXTERNAL;
464
  }
465
  $url = link_cleanup_url($item['url']);
466
  $url_parts = _link_parse_url($url);
467

    
468
  if (!empty($url_parts['url'])) {
469
    $item['url'] = url($url_parts['url'],
470
      array(
471
        'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
472
        'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
473
        'absolute' => TRUE,
474
        'html' => TRUE,
475
      )
476
    );
477
  }
478

    
479
  // Create a shortened URL for display.
480
  if ($type == LINK_EMAIL) {
481
    $display_url = str_replace('mailto:', '', $url);
482
  }
483
  else {
484
    $display_url = url($url_parts['url'],
485
      array(
486
        'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
487
        'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
488
        'absolute' => TRUE,
489
      )
490
    );
491
  }
492
  if ($instance['settings']['display']['url_cutoff'] && strlen($display_url) > $instance['settings']['display']['url_cutoff']) {
493
    $display_url = substr($display_url, 0, $instance['settings']['display']['url_cutoff']) . "...";
494
  }
495
  $item['display_url'] = $display_url;
496

    
497
  // Use the title defined at the instance level.
498
  if ($instance['settings']['title'] == 'value' && strlen(trim($instance['settings']['title_value']))) {
499
    $title = $instance['settings']['title_value'];
500
    if (function_exists('i18n_string_translate')) {
501
      $i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value";
502
      $title = i18n_string_translate($i18n_string_name, $title);
503
    }
504
  }
505
  // Use the title defined by the user at the widget level.
506
  elseif (isset($item['title'])) {
507
    $title = $item['title'];
508
  }
509
  else {
510
    $title = '';
511
  }
512

    
513
  // Replace tokens.
514
  if ($title && ($instance['settings']['title'] == 'value' || $instance['settings']['enable_tokens'])) {
515
    // Load the entity if necessary for entities in views.
516
    if (isset($entity->{$property_id})) {
517
      $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
518
      $entity_loaded = array_pop($entity_loaded);
519
    }
520
    else {
521
      $entity_loaded = $entity;
522
    }
523
    $title = token_replace($title, array($entity_token_type => $entity_loaded));
524
    $title = filter_xss($title, array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u'));
525
    $item['html'] = TRUE;
526
  }
527
  $item['title'] = empty($title) ? $item['display_url'] : $title;
528

    
529
  if (!isset($item['attributes'])) {
530
    $item['attributes'] = array();
531
  }
532

    
533
  // Unserialize attributtes array if it has not been unserialized yet.
534
  if (!is_array($item['attributes'])) {
535
    $item['attributes'] = (array)unserialize($item['attributes']);
536
  }
537

    
538
  // Add default attributes.
539
  if (!is_array($instance['settings']['attributes'])) {
540
    $instance['settings']['attributes'] = _link_default_attributes();
541
  }
542
  else {
543
    $instance['settings']['attributes'] += _link_default_attributes();
544
  }
545

    
546
  // Merge item attributes with attributes defined at the field level.
547
  $item['attributes'] += $instance['settings']['attributes'];
548

    
549
  // If user is not allowed to choose target attribute, use default defined at
550
  // field level.
551
  if ($instance['settings']['attributes']['target'] != LINK_TARGET_USER) {
552
    $item['attributes']['target'] = $instance['settings']['attributes']['target'];
553
  }
554
  elseif ($item['attributes']['target'] == LINK_TARGET_USER) {
555
    $item['attributes']['target'] = LINK_TARGET_DEFAULT;
556
  }
557

    
558
  // Remove the target attribute if the default (no target) is selected.
559
  if (empty($item['attributes']) || (isset($item['attributes']['target']) && $item['attributes']['target'] == LINK_TARGET_DEFAULT)) {
560
    unset($item['attributes']['target']);
561
  }
562

    
563
  // Remove rel attribute for internal or external links if selected.
564
  if (isset($item['attributes']['rel']) && isset($instance['settings']['rel_remove']) && $instance['settings']['rel_remove'] != 'default') {
565
    if (($instance['settings']['rel_remove'] != 'rel_remove_internal' && $type != LINK_INTERNAL) ||
566
      ($instance['settings']['rel_remove'] != 'rel_remove_external' && $type != LINK_EXTERNAL)) {
567
      unset($item['attributes']['rel']);
568
    }
569
  }
570

    
571
  // Handle "title" link attribute.
572
  if (!empty($item['attributes']['title']) && module_exists('token')) {
573
    // Load the entity (necessary for entities in views).
574
    if (isset($entity->{$property_id})) {
575
      $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
576
      $entity_loaded = array_pop($entity_loaded);
577
    }
578
    else {
579
      $entity_loaded = $entity;
580
    }
581
    $item['attributes']['title'] = token_replace($item['attributes']['title'], array($entity_token_type => $entity_loaded));
582
    $item['attributes']['title'] = filter_xss($item['attributes']['title'], array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u'));
583
  }
584
  // Remove title attribute if it's equal to link text.
585
  if (isset($item['attributes']['title']) && $item['attributes']['title'] == $item['title']) {
586
    unset($item['attributes']['title']);
587
  }
588
  unset($item['attributes']['configurable_title']);
589

    
590
  // Remove empty attributes.
591
  $item['attributes'] = array_filter($item['attributes']);
592
}
593

    
594
/**
595
 * Because parse_url doesn't work with relative urls.
596
 *
597
 * @param string $url
598
 *   URL to parse.
599
 *
600
 * @return Array
601
 *   Array of url pieces - only 'url', 'query', and 'fragment'.
602
 */
603
function _link_parse_url($url) {
604
  $url_parts = array();
605
  // Separate out the anchor, if any.
606
  if (strpos($url, '#') !== FALSE) {
607
    $url_parts['fragment'] = substr($url, strpos($url, '#') + 1);
608
    $url = substr($url, 0, strpos($url, '#'));
609
  }
610
  // Separate out the query string, if any.
611
  if (strpos($url, '?') !== FALSE) {
612
    $query = substr($url, strpos($url, '?') + 1);
613
    $url_parts['query'] = _link_parse_str($query);
614
    $url = substr($url, 0, strpos($url, '?'));
615
  }
616
  $url_parts['url'] = $url;
617
  return $url_parts;
618
}
619

    
620
/**
621
 * Bacause parse_str replaces the following characters in query parameters name
622
 * in order to maintain compability with deprecated register_globals directive:
623
 *
624
 *   - chr(32) ( ) (space)
625
 *   - chr(46) (.) (dot)
626
 *   - chr(91) ([) (open square bracket)
627
 *   - chr(128) - chr(159) (various)
628
 *
629
 * @param string $query
630
 *   Query to parse.
631
 *
632
 * @return Array
633
 *   Array of query parameters.
634
 *
635
 * @see http://php.net/manual/en/language.variables.external.php#81080
636
 */
637
function _link_parse_str($query) {
638
  $query_array = array();
639

    
640
  $pairs = explode('&', $query);
641
  foreach ($pairs as $pair) {
642
    $name_value = explode('=', $pair);
643
    $name = urldecode($name_value[0]);
644
    $value = isset($name_value[1]) ? urldecode($name_value[1]) : NULL;
645
    $query_array[$name] = $value;
646
  }
647

    
648
  return $query_array;
649
}
650

    
651
/**
652
 * Implements hook_theme().
653
 */
654
function link_theme() {
655
  return array(
656
    'link_formatter_link_default' => array(
657
      'variables' => array('element' => NULL, 'field' => NULL),
658
    ),
659
    'link_formatter_link_plain' => array(
660
      'variables' => array('element' => NULL, 'field' => NULL),
661
    ),
662
    'link_formatter_link_absolute' => array(
663
      'variables' => array('element' => NULL, 'field' => NULL),
664
    ),
665
    'link_formatter_link_domain' => array(
666
      'variables' => array('element' => NULL, 'display' => NULL, 'field' => NULL),
667
    ),
668
    'link_formatter_link_title_plain' => array(
669
      'variables' => array('element' => NULL, 'field' => NULL),
670
    ),
671
    'link_formatter_link_url' => array(
672
      'variables' => array('element' => NULL, 'field' => NULL),
673
    ),
674
    'link_formatter_link_short' => array(
675
      'variables' => array('element' => NULL, 'field' => NULL),
676
    ),
677
    'link_formatter_link_label' => array(
678
      'variables' => array('element' => NULL, 'field' => NULL),
679
    ),
680
    'link_formatter_link_separate' => array(
681
      'variables' => array('element' => NULL, 'field' => NULL),
682
    ),
683
    'link_field' => array(
684
      'render element' => 'element',
685
    ),
686
  );
687
}
688

    
689
/**
690
 * Formats a link field widget.
691
 */
692
function theme_link_field($vars) {
693
  drupal_add_css(drupal_get_path('module', 'link') . '/link.css');
694
  $element = $vars['element'];
695
  // Prefix single value link fields with the name of the field.
696
  if (empty($element['#field']['multiple'])) {
697
    if (isset($element['url']) && !isset($element['title'])) {
698
      $element['url']['#title_display'] = 'invisible';
699
    }
700
  }
701

    
702
  $output = '';
703
  $output .= '<div class="link-field-subrow clearfix">';
704
  if (isset($element['title'])) {
705
    $output .= '<div class="link-field-title link-field-column">' . drupal_render($element['title']) . '</div>';
706
  }
707
  $output .= '<div class="link-field-url' . (isset($element['title']) ? ' link-field-column' : '') . '">' . drupal_render($element['url']) . '</div>';
708
  $output .= '</div>';
709
  if (!empty($element['attributes']['target'])) {
710
    $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['target']) . '</div>';
711
  }
712
  if (!empty($element['attributes']['title'])) {
713
    $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['title']) . '</div>';
714
  }
715
  return $output;
716
}
717

    
718
/**
719
 * Implements hook_element_info().
720
 */
721
function link_element_info() {
722
  $elements = array();
723
  $elements['link_field'] = array(
724
    '#input' => TRUE,
725
    '#process' => array('link_field_process'),
726
    '#theme' => 'link_field',
727
    '#theme_wrappers' => array('form_element'),
728
  );
729
  return $elements;
730
}
731

    
732
/**
733
 * Returns the default attributes and their values.
734
 */
735
function _link_default_attributes() {
736
  return array(
737
    'target' => LINK_TARGET_DEFAULT,
738
    'class' => '',
739
    'rel' => '',
740
  );
741
}
742

    
743
/**
744
 * Processes the link type element before displaying the field.
745
 *
746
 * Build the form element. When creating a form using FAPI #process,
747
 * note that $element['#value'] is already set.
748
 *
749
 * The $fields array is in $complete_form['#field_info'][$element['#field_name']].
750
 */
751
function link_field_process($element, $form_state, $complete_form) {
752
  $instance = field_widget_instance($element, $form_state);
753
  $settings = $instance['settings'];
754
  $element['url'] = array(
755
    '#type' => 'textfield',
756
    '#maxlength' => LINK_URL_MAX_LENGTH,
757
    '#title' => t('URL'),
758
    '#required' => ($element['#delta'] == 0 && $settings['url'] !== 'optional') ? $element['#required'] : FALSE,
759
    '#default_value' => isset($element['#value']['url']) ? $element['#value']['url'] : NULL,
760
  );
761
  if ($settings['title'] !== 'none' && $settings['title'] !== 'value') {
762
    $element['title'] = array(
763
      '#type' => 'textfield',
764
      '#maxlength' => $settings['title_maxlength'],
765
      '#title' => t('Title'),
766
      '#description' => t('The link title is limited to @maxlength characters maximum.', array('@maxlength' => $settings['title_maxlength'])),
767
      '#required' => ($settings['title'] == 'required' && (($element['#delta'] == 0 && $element['#required']) || !empty($element['#value']['url']))) ? TRUE : FALSE,
768
      '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
769
    );
770
  }
771

    
772
  // Initialize field attributes as an array if it is not an array yet.
773
  if (!is_array($settings['attributes'])) {
774
    $settings['attributes'] = array();
775
  }
776
  // Add default attributes.
777
  $settings['attributes'] += _link_default_attributes();
778
  $attributes = isset($element['#value']['attributes']) ? $element['#value']['attributes'] : $settings['attributes'];
779
  if (!empty($settings['attributes']['target']) && $settings['attributes']['target'] == LINK_TARGET_USER) {
780
    $element['attributes']['target'] = array(
781
      '#type' => 'checkbox',
782
      '#title' => t('Open URL in a New Window'),
783
      '#return_value' => LINK_TARGET_NEW_WINDOW,
784
      '#default_value' => isset($attributes['target']) ? $attributes['target'] : FALSE,
785
    );
786
  }
787
  if (!empty($settings['attributes']['configurable_title']) && $settings['attributes']['configurable_title'] == 1) {
788
    $element['attributes']['title'] = array(
789
      '#type' => 'textfield',
790
      '#title' => t('Link "title" attribute'),
791
      '#default_value' => isset($attributes['title']) ? $attributes['title'] : '',
792
      '#field_prefix' => 'title = "',
793
      '#field_suffix' => '"',
794
    );
795
  }
796

    
797
  // If the title field is avaliable or there are field accepts multiple values
798
  // then allow the individual field items display the required asterisk if needed.
799
  if (isset($element['title']) || isset($element['_weight'])) {
800
    // To prevent an extra required indicator, disable the required flag on the
801
    // base element since all the sub-fields are already required if desired.
802
    $element['#required'] = FALSE;
803
  }
804

    
805
  return $element;
806
}
807

    
808
/**
809
 * Implements hook_field_formatter_info().
810
 */
811
function link_field_formatter_info() {
812
  return array(
813
    'link_default' => array(
814
      'label' => t('Title, as link (default)'),
815
      'field types' => array('link_field'),
816
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
817
    ),
818
    'link_title_plain' => array(
819
      'label' => t('Title, as plain text'),
820
      'field types' => array('link_field'),
821
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
822
    ),
823
    'link_url' => array(
824
      'label' => t('URL, as link'),
825
      'field types' => array('link_field'),
826
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
827
    ),
828
    'link_plain' => array(
829
      'label' => t('URL, as plain text'),
830
      'field types' => array('link_field'),
831
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
832
    ),
833
    'link_absolute' => array(
834
      'label' => t('URL, absolute'),
835
      'field types' => array('link_field'),
836
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
837
    ),
838
    'link_domain' => array(
839
      'label' => t('Domain, as link'),
840
      'field types' => array('link_field'),
841
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
842
      'settings' => array(
843
        'strip_www' => FALSE,
844
      ),
845
    ),
846
    'link_short' => array(
847
      'label' => t('Short, as link with title "Link"'),
848
      'field types' => array('link_field'),
849
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
850
    ),
851
    'link_label' => array(
852
      'label' => t('Label, as link with label as title'),
853
      'field types' => array('link_field'),
854
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
855
    ),
856
    'link_separate' => array(
857
      'label' => t('Separate title and URL'),
858
      'field types' => array('link_field'),
859
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
860
    ),
861
  );
862
}
863

    
864
/**
865
 * Implements hook_field_formatter_settings_form().
866
 */
867
function link_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
868
  $display = $instance['display'][$view_mode];
869
  $settings = $display['settings'];
870
  $element = array();
871
  if ($display['type'] == 'link_domain') {
872
    $element['strip_www'] = array(
873
      '#title' => t('Strip www. from domain'),
874
      '#type' => 'checkbox',
875
      '#default_value' => $settings['strip_www'],
876
    );
877
  }
878
  return $element;
879
}
880

    
881
/**
882
 * Implements hook_field_formatter_settings_summary().
883
 */
884
function link_field_formatter_settings_summary($field, $instance, $view_mode) {
885
  $display = $instance['display'][$view_mode];
886
  $settings = $display['settings'];
887
  if ($display['type'] == 'link_domain') {
888
    if ($display['settings']['strip_www']) {
889
      return t('Strip www. from domain');
890
    }
891
    else {
892
      return t('Leave www. in domain');
893
    }
894
  }
895
  return '';
896
}
897

    
898
/**
899
 * Implements hook_field_formatter_view().
900
 */
901
function link_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
902
  $elements = array();
903
  foreach ($items as $delta => $item) {
904
    $elements[$delta] = array(
905
      '#theme' => 'link_formatter_' . $display['type'],
906
      '#element' => $item,
907
      '#field' => $instance,
908
      '#display' => $display,
909
    );
910
  }
911
  return $elements;
912
}
913

    
914
/**
915
 * Formats a link.
916
 */
917
function theme_link_formatter_link_default($vars) {
918
  $link_options = $vars['element'];
919
  unset($link_options['title']);
920
  unset($link_options['url']);
921

    
922
  if (isset($link_options['attributes']['class'])) {
923
    $link_options['attributes']['class'] = array($link_options['attributes']['class']);
924
  }
925

    
926
  // Display a normal link if both title and URL are available.
927
  if (!empty($vars['element']['title']) && !empty($vars['element']['url'])) {
928
    return l($vars['element']['title'], $vars['element']['url'], $link_options);
929
  }
930
  // If only a title, display the title.
931
  elseif (!empty($vars['element']['title'])) {
932
    return check_plain($vars['element']['title']);
933
  }
934
  elseif (!empty($vars['element']['url'])) {
935
    return l($vars['element']['title'], $vars['element']['url'], $link_options);
936
  }
937
}
938

    
939
/**
940
 * Formats a link (or its title) as plain text.
941
 */
942
function theme_link_formatter_link_plain($vars) {
943
  $link_options = $vars['element'];
944
  if (isset($link_options['title'])) {
945
    unset($link_options['title']);
946
  }
947
  else {
948
    $vars['element']['title'] = '';
949
  }
950
  unset($link_options['url']);
951
  return empty($vars['element']['url']) ? check_plain($vars['element']['title']) : url($vars['element']['url'], $link_options);
952
}
953

    
954
/**
955
 * Formats a link as an absolute URL
956
 */
957
function theme_link_formatter_link_absolute($vars) {
958
  $absolute = array('absolute' => TRUE);
959
  return empty($vars['element']['url']) ? '' : url($vars['element']['url'], $absolute + $vars['element']);
960
}
961

    
962
/**
963
 * Formats a link using the URL's domain for it's link text.
964
 */
965
function theme_link_formatter_link_domain($vars) {
966
  $link_options = $vars['element'];
967
  unset($link_options['title']);
968
  unset($link_options['url']);
969
  $domain = parse_url($vars['element']['display_url'], PHP_URL_HOST);
970
  if (!empty($vars['display']['settings']['strip_www'])) {
971
    $domain = str_replace('www.', '', $domain);
972
  }
973
  return $vars['element']['url'] ? l($domain, $vars['element']['url'], $link_options) : '';
974
}
975

    
976
/**
977
 * Formats a link's title as plain text.
978
 */
979
function theme_link_formatter_link_title_plain($vars) {
980
  return empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']);
981
}
982

    
983
/**
984
 * Formats a link using an alternate display URL for its link text.
985
 */
986
function theme_link_formatter_link_url($vars) {
987
  $link_options = $vars['element'];
988
  unset($link_options['title']);
989
  unset($link_options['url']);
990
  return $vars['element']['url'] ? l($vars['element']['display_url'], $vars['element']['url'], $link_options) : '';
991
}
992

    
993
/**
994
 * Formats a link using "Link" as the link text.
995
 */
996
function theme_link_formatter_link_short($vars) {
997
  $link_options = $vars['element'];
998
  unset($link_options['title']);
999
  unset($link_options['url']);
1000
  return $vars['element']['url'] ? l(t('Link'), $vars['element']['url'], $link_options) : '';
1001
}
1002

    
1003
/**
1004
 * Formats a link using the field's label as link text.
1005
 */
1006
function theme_link_formatter_link_label($vars) {
1007
  $link_options = $vars['element'];
1008
  unset($link_options['title']);
1009
  unset($link_options['url']);
1010
  return $vars['element']['url'] ? l($vars['field']['label'], $vars['element']['url'], $link_options) : '';
1011
}
1012

    
1013
/**
1014
 * Formats a link as separate title and URL elements.
1015
 */
1016
function theme_link_formatter_link_separate($vars) {
1017
  $class = empty($vars['element']['attributes']['class']) ? '' : ' ' . $vars['element']['attributes']['class'];
1018
  unset($vars['element']['attributes']['class']);
1019
  $link_options = $vars['element'];
1020
  unset($link_options['title']);
1021
  unset($link_options['url']);
1022
  $title = empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']);
1023

    
1024
  /**
1025
   * @TODO static html markup looks not very elegant
1026
   * needs smarter output solution and an optional title/url seperator
1027
   */
1028
  $url_parts = _link_parse_url($vars['element']['url']);
1029
  $output = '';
1030
  $output .= '<div class="link-item ' . $class . '">';
1031
  if (!empty($title)) {
1032
    $output .= '<div class="link-title">' . $title . '</div>';
1033
  }
1034
  $output .= '<div class="link-url">' . l($url_parts['url'], $vars['element']['url'], $link_options) . '</div>';
1035
  $output .= '</div>';
1036
  return $output;
1037
}
1038

    
1039
/**
1040
 * Implements hook_token_list().
1041
 */
1042
function link_token_list($type = 'all') {
1043
  if ($type === 'field' || $type === 'all') {
1044
    $tokens = array();
1045
    $tokens['link']['url'] = t("Link URL");
1046
    $tokens['link']['title'] = t("Link title");
1047
    $tokens['link']['view'] = t("Formatted html link");
1048
    return $tokens;
1049
  }
1050
}
1051

    
1052
function link_token_values($type, $object = NULL) {
1053
  if ($type === 'field') {
1054
    $item = $object[0];
1055

    
1056
    $tokens['url'] = $item['url'];
1057
    $tokens['title'] = $item['title'];
1058
    $tokens['view'] = isset($item['view']) ? $item['view'] : '';
1059

    
1060
    return $tokens;
1061
  }
1062
}
1063

    
1064
/**
1065
 * Implements hook_views_api().
1066
 */
1067
function link_views_api() {
1068
  return array(
1069
    'api' => 2,
1070
    'path' => drupal_get_path('module', 'link') . '/views',
1071
  );
1072
}
1073

    
1074
/**
1075
 * Forms a valid URL if possible from an entered address.
1076
 * 
1077
 * Trims whitespace and automatically adds an http:// to addresses without a
1078
 * protocol specified
1079
 *
1080
 * @param string $url
1081
 * @param string $protocol
1082
 *   The protocol to be prepended to the url if one is not specified
1083
 */
1084
function link_cleanup_url($url, $protocol = 'http') {
1085
  $url = trim($url);
1086
  $type = link_validate_url($url);
1087

    
1088
  if ($type === LINK_EXTERNAL) {
1089
    // Check if there is no protocol specified.
1090
    $protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i", $url);
1091
    if (empty($protocol_match)) {
1092
      // But should there be? Add an automatic http:// if it starts with a domain name.
1093
      $LINK_DOMAINS = _link_domains();
1094
      $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)(' . $LINK_DOMAINS . '|[a-z]{2}))/i', $url);
1095
      if (!empty($domain_match)) {
1096
        $url = $protocol . "://" . $url;
1097
      }
1098
    }
1099
  }
1100

    
1101
  return $url;
1102
}
1103

    
1104
/**
1105
 * Validates a URL.
1106
 * 
1107
 * Accepts all URLs following RFC 1738 standard for URL formation and all e-mail
1108
 * addresses following the RFC 2368 standard for mailto address formation.
1109
 *
1110
 * @param string $text
1111
 * 
1112
 * @return mixed
1113
 *   Returns boolean FALSE if the URL is not valid. On success, returns one of
1114
 *   the LINK_(linktype) constants.
1115
 */
1116
function link_validate_url($text) {
1117
  // @TODO Complete letters.
1118
  $LINK_ICHARS_DOMAIN = (string) html_entity_decode(implode("", array(
1119
    "&#x00E6;", // æ
1120
    "&#x00C6;", // Æ
1121
    "&#x00C0;", // À
1122
    "&#x00E0;", // à
1123
    "&#x00C1;", // Á
1124
    "&#x00E1;", // á
1125
    "&#x00C2;", // Â
1126
    "&#x00E2;", // â
1127
    "&#x00E5;", // å
1128
    "&#x00C5;", // Å
1129
    "&#x00E4;", // ä
1130
    "&#x00C4;", // Ä
1131
    "&#x00C7;", // Ç
1132
    "&#x00E7;", // ç
1133
    "&#x00D0;", // Ð
1134
    "&#x00F0;", // ð
1135
    "&#x00C8;", // È
1136
    "&#x00E8;", // è
1137
    "&#x00C9;", // É
1138
    "&#x00E9;", // é
1139
    "&#x00CA;", // Ê
1140
    "&#x00EA;", // ê
1141
    "&#x00CB;", // Ë
1142
    "&#x00EB;", // ë
1143
    "&#x00CE;", // Î
1144
    "&#x00EE;", // î
1145
    "&#x00CF;", // Ï
1146
    "&#x00EF;", // ï
1147
    "&#x00F8;", // ø
1148
    "&#x00D8;", // Ø
1149
    "&#x00F6;", // ö
1150
    "&#x00D6;", // Ö
1151
    "&#x00D4;", // Ô
1152
    "&#x00F4;", // ô
1153
    "&#x00D5;", // Õ
1154
    "&#x00F5;", // õ
1155
    "&#x0152;", // Œ
1156
    "&#x0153;", // œ
1157
    "&#x00FC;", // ü
1158
    "&#x00DC;", // Ü
1159
    "&#x00D9;", // Ù
1160
    "&#x00F9;", // ù
1161
    "&#x00DB;", // Û
1162
    "&#x00FB;", // û
1163
    "&#x0178;", // Ÿ
1164
    "&#x00FF;", // ÿ
1165
    "&#x00D1;", // Ñ
1166
    "&#x00F1;", // ñ
1167
    "&#x00FE;", // þ
1168
    "&#x00DE;", // Þ
1169
    "&#x00FD;", // ý
1170
    "&#x00DD;", // Ý
1171
    "&#x00BF;", // ¿
1172
  )), ENT_QUOTES, 'UTF-8');
1173

    
1174
  $LINK_ICHARS = $LINK_ICHARS_DOMAIN . (string) html_entity_decode(implode("", array(
1175
    "&#x00DF;", // ß
1176
  )), ENT_QUOTES, 'UTF-8');
1177
  $allowed_protocols = variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'));
1178
  $LINK_DOMAINS = _link_domains();
1179

    
1180
  // Starting a parenthesis group with (?: means that it is grouped, but is not captured
1181
  $protocol = '((?:' . implode("|", $allowed_protocols) . '):\/\/)';
1182
  $authentication = "(?:(?:(?:[\w\.\-\+!$&'\(\)*\+,;=" . $LINK_ICHARS . "]|%[0-9a-f]{2})+(?::(?:[\w" . $LINK_ICHARS . "\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})*)?)?@)";
1183
  $domain = '(?:(?:[a-z0-9' . $LINK_ICHARS_DOMAIN . ']([a-z0-9' . $LINK_ICHARS_DOMAIN . '\-_\[\]])*)(\.(([a-z0-9' . $LINK_ICHARS_DOMAIN . '\-_\[\]])+\.)*(' . $LINK_DOMAINS . '|[a-z]{2}))?)';
1184
  $ipv4 = '(?:[0-9]{1,3}(\.[0-9]{1,3}){3})';
1185
  $ipv6 = '(?:[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7})';
1186
  $port = '(?::([0-9]{1,5}))';
1187

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

    
1191
  // Pattern specific to internal links.
1192
  $internal_pattern = "/^(?:[a-z0-9" . $LINK_ICHARS . "_\-+\[\] ]+)";
1193
  $internal_pattern_file = "/^(?:[a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \/\(\)][a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \(\)][a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \/\(\)]+)$/i";
1194

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

    
1200
  // The rest of the path for a standard URL.
1201
  $end = $directories . '?' . $query . '?' . $anchor . '?' . '$/i';
1202

    
1203
  $message_id = '[^@].*@' . $domain;
1204
  $newsgroup_name = '(?:[0-9a-z+-]*\.)*[0-9a-z+-]*';
1205
  $news_pattern = '/^news:(' . $newsgroup_name . '|' . $message_id . ')$/i';
1206

    
1207
  $user = '[a-zA-Z0-9' . $LINK_ICHARS . '_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+';
1208
  $email_pattern = '/^mailto:' . $user . '@' . '(?:' . $domain . '|' . $ipv4 . '|' . $ipv6 . '|localhost)' . $query . '?$/';
1209

    
1210
  if (strpos($text, '<front>') === 0) {
1211
    return LINK_FRONT;
1212
  }
1213
  if (in_array('mailto', $allowed_protocols) && preg_match($email_pattern, $text)) {
1214
    return LINK_EMAIL;
1215
  }
1216
  if (in_array('news', $allowed_protocols) && preg_match($news_pattern, $text)) {
1217
    return LINK_NEWS;
1218
  }
1219
  if (preg_match($internal_pattern . $end, $text)) {
1220
    return LINK_INTERNAL;
1221
  }
1222
  if (preg_match($external_pattern . $end, $text)) {
1223
    return LINK_EXTERNAL;
1224
  }
1225
  if (preg_match($internal_pattern_file, $text)) {
1226
    return LINK_INTERNAL;
1227
  }
1228

    
1229
  return FALSE;
1230
}
1231

    
1232
/**
1233
 * Returns the list of allowed domains, including domains added by admins via variable_set/$config.
1234
 */
1235
function _link_domains() {
1236
  $link_extra_domains = variable_get('link_extra_domains', array());
1237
  return empty($link_extra_domains) ? LINK_DOMAINS : LINK_DOMAINS . '|' . implode('|', $link_extra_domains);
1238
}
1239

    
1240
/**
1241
 * Implements hook_migrate_field_alter().
1242
 */
1243
function link_content_migrate_field_alter(&$field_value, $instance_value) {
1244
  if ($field_value['type'] == 'link') {
1245
    // Adjust the field type.
1246
    $field_value['type'] = 'link_field';
1247
    // Remove settings that are now on the instance.
1248
    foreach (array('attributes', 'display', 'url', 'title', 'title_value', 'enable_tokens', 'validate_url') as $setting) {
1249
      unset($field_value['settings'][$setting]);
1250
    }
1251
  }
1252
}
1253

    
1254
/**
1255
 * Implements hook_migrate_instance_alter().
1256
 *
1257
 * Widget type also changed to link_field.
1258
 */
1259
function link_content_migrate_instance_alter(&$instance_value, $field_value) {
1260
  if ($field_value['type'] == 'link') {
1261
    // Grab settings that were previously on the field.
1262
    foreach (array('attributes', 'display', 'url', 'title', 'title_value', 'enable_tokens', 'validate_url') as $setting) {
1263
      if (isset($field_value['settings'][$setting])) {
1264
        $instance_value['settings'][$setting] = $field_value['settings'][$setting];
1265
      }
1266
    }
1267
    // Adjust widget type.
1268
    if ($instance_value['widget']['type'] == 'link') {
1269
      $instance_value['widget']['type'] = 'link_field';
1270
    }
1271
    // Adjust formatter types.
1272
    foreach ($instance_value['display'] as $context => $settings) {
1273
      if (in_array($settings['type'], array('default', 'title_plain', 'url', 'plain', 'short', 'label', 'separate'))) {
1274
        $instance_value['display'][$context]['type'] = 'link_' . $settings['type'];
1275
      }
1276
    }
1277
  }
1278
}
1279

    
1280
/**
1281
 * Implements hook_field_settings_form().
1282
 */
1283
function link_field_settings_form() {
1284
  return array();
1285
}
1286

    
1287
/**
1288
 * Additional callback to adapt the property info of link fields.
1289
 * 
1290
 * @see entity_metadata_field_entity_property_info().
1291
 */
1292
function link_field_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
1293
  $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
1294
  // Define a data structure so it's possible to deal with both the link title
1295
  // and URL.
1296
  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
1297
  $property['setter callback'] = 'entity_metadata_field_verbatim_set';
1298

    
1299
  // Auto-create the field item as soon as a property is set.
1300
  $property['auto creation'] = 'link_field_item_create';
1301

    
1302
  $property['property info'] = link_field_item_property_info();
1303
  $property['property info']['url']['required'] = !$instance['settings']['url'];
1304
  $property['property info']['title']['required'] = ($instance['settings']['title'] == 'required');
1305
  if ($instance['settings']['title'] == 'none') {
1306
    unset($property['property info']['title']);
1307
  }
1308
  unset($property['query callback']);
1309
}
1310

    
1311
/**
1312
 * Callback for creating a new, empty link field item.
1313
 *
1314
 * @see link_field_property_info_callback()
1315
 */
1316
function link_field_item_create() {
1317
  return array('title' => NULL, 'url' => NULL);
1318
}
1319

    
1320
/**
1321
 * Defines info for the properties of the link-field item data structure.
1322
 */
1323
function link_field_item_property_info() {
1324
  $properties['title'] = array(
1325
    'type' => 'text',
1326
    'label' => t('The title of the link.'),
1327
    'setter callback' => 'entity_property_verbatim_set',
1328
  );
1329
  $properties['url'] = array(
1330
    'type' => 'uri',
1331
    'label' => t('The URL of the link.'),
1332
    'setter callback' => 'entity_property_verbatim_set',
1333
  );
1334
  return $properties;
1335
}
1336

    
1337
/**
1338
 * Implements hook_field_update_instance().
1339
 */
1340
function link_field_update_instance($instance, $prior_instance) {
1341
  if (function_exists('i18n_string_update') && $instance['widget']['type'] == 'link_field' && $prior_instance['settings']['title_value'] != $instance['settings']['title_value']) {
1342
    $i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value";
1343
    i18n_string_update($i18n_string_name, $instance['settings']['title_value']);
1344
  }
1345
}
1346

    
1347
/**
1348
 * Implements hook_i18n_string_list_TEXTGROUP_alter().
1349
 */
1350
function link_i18n_string_list_field_alter(&$strings, $type = NULL, $object = NULL) {
1351
  if ($type != 'field_instance' || !is_array($object) || !isset($object['widget']['type'])) {
1352
    return;
1353
  }
1354
  if ($object['widget']['type'] == 'link_field' && isset($object['settings']['title_value'])) {
1355
    $strings['field'][$object['field_name']][$object['bundle']]['title_value']['string'] = $object['settings']['title_value'];
1356
  }
1357
}