Projet

Général

Profil

Paste
Télécharger (60,3 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / link / link.module @ 39a181a4

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_TARGET_DEFAULT', 'default');
14
define('LINK_TARGET_NEW_WINDOW', '_blank');
15
define('LINK_TARGET_TOP', '_top');
16
define('LINK_TARGET_USER', 'user');
17

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

    
23
/**
24
 * Implements hook_help().
25
 */
26
function link_help($path, $arg) {
27
  switch ($path) {
28
    case 'admin/help#link':
29
      $output = '<p><strong>About</strong></p>';
30
      $output .= '<p>' . '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>';
31
      $output .= '<p>' . '<strong>Requirements / Dependencies</strong>' . '</p>';
32
      $output .= '<p>' . 'Fields API is provided already by core [no dependencies].' . '</p>';
33
      $output .= '<p><strong>Configuration</strong></p>';
34
      $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>';
35
      return $output;
36
  }
37
}
38

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

    
81
/**
82
 * Implements hook_field_instance_settings_form().
83
 */
84
function link_field_instance_settings_form($field, $instance) {
85
  $form = array(
86
    '#element_validate' => array('link_field_settings_form_validate'),
87
  );
88

    
89
  $form['absolute_url'] = array(
90
    '#type' => 'checkbox',
91
    '#title' => t('Absolute URL'),
92
    '#default_value' => isset($instance['settings']['absolute_url']) && ($instance['settings']['absolute_url'] !== '') ? $instance['settings']['absolute_url'] : TRUE,
93
    '#description' => t('If checked, the URL will always render as an absolute URL.'),
94
  );
95

    
96
  $form['validate_url'] = array(
97
    '#type' => 'checkbox',
98
    '#title' => t('Validate URL'),
99
    '#default_value' => isset($instance['settings']['validate_url']) && ($instance['settings']['validate_url'] !== '') ? $instance['settings']['validate_url'] : TRUE,
100
    '#description' => t('If checked, the URL field will be verified as a valid URL during validation.'),
101
  );
102

    
103
  $form['url'] = array(
104
    '#type' => 'checkbox',
105
    '#title' => t('Optional URL'),
106
    '#default_value' => isset($instance['settings']['url']) ? $instance['settings']['url'] : '',
107
    '#return_value' => 'optional',
108
    '#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.'),
109
  );
110

    
111
  $title_options = array(
112
    'optional' => t('Optional Title'),
113
    'required' => t('Required Title'),
114
    'value' => t('Static Title'),
115
    'select' => t('Selected Title'),
116
    'none' => t('No Title'),
117
  );
118

    
119
  $form['title'] = array(
120
    '#type' => 'radios',
121
    '#title' => t('Link Title'),
122
    '#default_value' => isset($instance['settings']['title']) ? $instance['settings']['title'] : 'optional',
123
    '#options' => $title_options,
124
    '#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.'),
125
  );
126

    
127
  $form['title_value'] = array(
128
    '#type' => 'textfield',
129
    '#title' => t('Static or default title'),
130
    '#default_value' => isset($instance['settings']['title_value']) ? $instance['settings']['title_value'] : '',
131
    '#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.'),
132
    '#states' => array(
133
      'visible' => array(
134
        ':input[name="instance[settings][title]"]' => array('value' => 'value'),
135
      ),
136
    ),
137
  );
138

    
139
  $form['title_allowed_values'] = array(
140
    '#type' => 'textarea',
141
    '#title' => t('Title allowed values'),
142
    '#default_value' => isset($instance['settings']['title_allowed_values']) ? $instance['settings']['title_allowed_values'] : '',
143
    '#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.'),
144
    '#states' => array(
145
      'visible' => array(
146
        ':input[name="instance[settings][title]"]' => array('value' => 'select'),
147
      ),
148
    ),
149
  );
150

    
151
  $form['title_label_use_field_label'] = array(
152
    '#type' => 'checkbox',
153
    '#title' => t('Use field label as the label for the title field'),
154
    '#default_value' => isset($instance['settings']['title_label_use_field_label']) ? $instance['settings']['title_label_use_field_label'] : FALSE,
155
    '#description' => t('If this is checked the field label will be hidden.'),
156
  );
157

    
158
  $form['title_maxlength'] = array(
159
    '#type' => 'textfield',
160
    '#title' => t('Max length of title field'),
161
    '#default_value' => isset($instance['settings']['title_maxlength']) ? $instance['settings']['title_maxlength'] : '128',
162
    '#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.'),
163
    '#maxlength' => 3,
164
    '#size' => 3,
165
  );
166

    
167
  if (module_exists('token')) {
168
    // Add token module replacements fields.
169
    $form['enable_tokens'] = array(
170
      '#type' => 'checkbox',
171
      '#title' => t('Allow user-entered tokens'),
172
      '#default_value' => isset($instance['settings']['enable_tokens']) ? $instance['settings']['enable_tokens'] : 1,
173
      '#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.'),
174
    );
175

    
176
    $entity_info = entity_get_info($instance['entity_type']);
177
    $form['tokens_help'] = array(
178
      '#theme' => 'token_tree',
179
      '#token_types' => array($entity_info['token type']),
180
      '#global_types' => TRUE,
181
      '#click_insert' => TRUE,
182
      '#dialog' => TRUE,
183
    );
184
  }
185

    
186
  $form['display'] = array(
187
    '#tree' => TRUE,
188
  );
189
  $form['display']['url_cutoff'] = array(
190
    '#type' => 'textfield',
191
    '#title' => t('URL Display Cutoff'),
192
    '#default_value' => isset($instance['settings']['display']['url_cutoff']) ? $instance['settings']['display']['url_cutoff'] : '80',
193
    '#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.'),
194
    '#maxlength' => 3,
195
    '#size' => 3,
196
  );
197

    
198
  $target_options = array(
199
    LINK_TARGET_DEFAULT => t('Default (no target attribute)'),
200
    LINK_TARGET_TOP => t('Open link in window root'),
201
    LINK_TARGET_NEW_WINDOW => t('Open link in new window'),
202
    LINK_TARGET_USER => t('Allow the user to choose'),
203
  );
204
  $form['attributes'] = array(
205
    '#tree' => TRUE,
206
  );
207
  $form['attributes']['target'] = array(
208
    '#type' => 'radios',
209
    '#title' => t('Link Target'),
210
    '#default_value' => empty($instance['settings']['attributes']['target']) ? LINK_TARGET_DEFAULT : $instance['settings']['attributes']['target'],
211
    '#options' => $target_options,
212
  );
213
  $form['attributes']['rel'] = array(
214
    '#type' => 'textfield',
215
    '#title' => t('Rel Attribute'),
216
    '#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.'),
217
    '#default_value' => empty($instance['settings']['attributes']['rel']) ? '' : $instance['settings']['attributes']['rel'],
218
    '#field_prefix' => 'rel = "',
219
    '#field_suffix' => '"',
220
    '#size' => 20,
221
  );
222
  $rel_remove_options = array(
223
    'default' => t('Keep rel as set up above (untouched/default)'),
224
    'rel_remove_external' => t('Remove rel if given link is external'),
225
    'rel_remove_internal' => t('Remove rel if given link is internal'),
226
  );
227
  $form['rel_remove'] = array(
228
    '#type' => 'radios',
229
    '#title' => t('Remove rel attribute automatically'),
230
    '#default_value' => !isset($instance['settings']['rel_remove']) ? 'default' : $instance['settings']['rel_remove'],
231
    '#description' => t('Turn on/off if rel attribute should be removed automatically, if user given link is internal/external'),
232
    '#options' => $rel_remove_options,
233
  );
234
  $form['attributes']['configurable_class'] = array(
235
    '#title' => t("Allow the user to enter a custom link class per link"),
236
    '#type' => 'checkbox',
237
    '#default_value' => empty($instance['settings']['attributes']['configurable_class']) ? '' : $instance['settings']['attributes']['configurable_class'],
238
  );
239
  $form['attributes']['class'] = array(
240
    '#type' => 'textfield',
241
    '#title' => t('Additional CSS Class'),
242
    '#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'),
243
    '#default_value' => empty($instance['settings']['attributes']['class']) ? '' : $instance['settings']['attributes']['class'],
244
  );
245
  $form['attributes']['configurable_title'] = array(
246
    '#title' => t("Allow the user to enter a link 'title' attribute"),
247
    '#type' => 'checkbox',
248
    '#default_value' => empty($instance['settings']['attributes']['configurable_title']) ? '' : $instance['settings']['attributes']['configurable_title'],
249
  );
250
  $form['attributes']['title'] = array(
251
    '#title' => t("Default link 'title' Attribute"),
252
    '#type' => 'textfield',
253
    '#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.'),
254
    '#default_value' => empty($instance['settings']['attributes']['title']) ? '' : $instance['settings']['attributes']['title'],
255
    '#field_prefix' => 'title = "',
256
    '#field_suffix' => '"',
257
    '#size' => 20,
258
  );
259
  return $form;
260
}
261

    
262
/**
263
 * Form validate.
264
 *
265
 * #element_validate handler for link_field_instance_settings_form().
266
 */
267
function link_field_settings_form_validate($element, &$form_state, $complete_form) {
268
  if ($form_state['values']['instance']['settings']['title'] === 'value' && empty($form_state['values']['instance']['settings']['title_value'])) {
269
    form_set_error('instance][settings][title_value', t('A default title must be provided if the title is a static value.'));
270
  }
271
  if ($form_state['values']['instance']['settings']['title'] === 'select'
272
    && empty($form_state['values']['instance']['settings']['title_allowed_values'])) {
273
    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.'));
274
  }
275
  if (!empty($form_state['values']['instance']['settings']['display']['url_cutoff']) && !is_numeric($form_state['values']['instance']['settings']['display']['url_cutoff'])) {
276
    form_set_error('display', t('URL Display Cutoff value must be numeric.'));
277
  }
278
  if (empty($form_state['values']['instance']['settings']['title_maxlength'])) {
279
    form_set_value($element['title_maxlength'], '128', $form_state);
280
  }
281
  elseif (!is_numeric($form_state['values']['instance']['settings']['title_maxlength'])) {
282
    form_set_error('title_maxlength', t('The max length of the link title must be numeric.'));
283
  }
284
  elseif ($form_state['values']['instance']['settings']['title_maxlength'] > 255) {
285
    form_set_error('title_maxlength', t('The max length of the link title cannot be greater than 255 characters.'));
286
  }
287
}
288

    
289
/**
290
 * Implements hook_field_is_empty().
291
 */
292
function link_field_is_empty($item, $field) {
293
  return empty($item['title']) && empty($item['url']);
294
}
295

    
296
/**
297
 * Implements hook_field_load().
298
 */
299
function link_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
300
  foreach ($entities as $id => $entity) {
301
    foreach ($items[$id] as $delta => $item) {
302
      $items[$id][$delta]['attributes'] = _link_load($field, $item, $instances[$id]);
303
    }
304
  }
305
}
306

    
307
/**
308
 * Implements hook_field_validate().
309
 */
310
function link_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
311
  $optional_field_found = FALSE;
312
  if ($instance['settings']['validate_url'] !== 0 || is_null($instance['settings']['validate_url']) || !isset($instance['settings']['validate_url'])) {
313
    foreach ($items as $delta => $value) {
314
      _link_validate($items[$delta], $delta, $field, $entity, $instance, $langcode, $optional_field_found, $errors);
315
    }
316
  }
317

    
318
  if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] && !$optional_field_found) {
319
    $errors[$field['field_name']][$langcode][0][] = array(
320
      'error' => 'link_required',
321
      'message' => t('At least one title or URL must be entered.'),
322
      'error_element' => array('url' => FALSE, 'title' => TRUE),
323
    );
324
  }
325
}
326

    
327
/**
328
 * Implements hook_field_insert().
329
 */
330
function link_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
331
  foreach ($items as $delta => $value) {
332
    _link_process($items[$delta], $delta, $field, $entity, $instance);
333
  }
334
}
335

    
336
/**
337
 * Implements hook_field_update().
338
 */
339
function link_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
340
  foreach ($items as $delta => $value) {
341
    _link_process($items[$delta], $delta, $field, $entity, $instance);
342
  }
343
}
344

    
345
/**
346
 * Implements hook_field_prepare_view().
347
 */
348
function link_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
349
  foreach ($items as $entity_id => $entity_items) {
350
    foreach ($entity_items as $delta => $value) {
351
      _link_sanitize($items[$entity_id][$delta], $delta, $field, $instances[$entity_id], $entities[$entity_id]);
352
    }
353
  }
354
}
355

    
356
/**
357
 * Implements hook_field_widget_info().
358
 */
359
function link_field_widget_info() {
360
  return array(
361
    'link_field' => array(
362
      'label' => 'Link',
363
      'field types' => array('link_field'),
364
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
365
    ),
366
  );
367
}
368

    
369
/**
370
 * Implements hook_field_widget_form().
371
 */
372
function link_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
373
  $element += array(
374
    '#type' => $instance['widget']['type'],
375
    '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
376
  );
377
  return $element;
378
}
379

    
380
/**
381
 * Implements hook_field_widget_error().
382
 */
383
function link_field_widget_error($element, $error, $form, &$form_state) {
384
  if (!empty($error['error_element']['title'])) {
385
    form_error($element['title'], $error['message']);
386
  }
387
  elseif (!empty($error['error_element']['url'])) {
388
    form_error($element['url'], $error['message']);
389
  }
390
}
391

    
392
/**
393
 * Unpacks the item attributes for use.
394
 */
395
function _link_load($field, $item, $instance) {
396
  if (isset($item['attributes'])) {
397
    if (!is_array($item['attributes'])) {
398
      $item['attributes'] = unserialize($item['attributes']);
399
    }
400
    return $item['attributes'];
401
  }
402
  elseif (isset($instance['settings']['attributes'])) {
403
    return $instance['settings']['attributes'];
404
  }
405
  else {
406
    return $field['settings']['attributes'];
407
  }
408
}
409

    
410
/**
411
 * Prepares the item attributes and url for storage.
412
 *
413
 * @param array $item
414
 *   Link field values.
415
 * @param array $delta
416
 *   The sequence number for current values.
417
 * @param array $field
418
 *   The field structure array.
419
 * @param object $entity
420
 *   Entity object.
421
 * @param array $instance
422
 *   The instance structure for $field on $entity's bundle.
423
 *
424
 * @codingStandardsIgnoreStart
425
 */
426
function _link_process(&$item, $delta, $field, $entity, $instance) {
427
  // @codingStandardsIgnoreEnd
428
  // Trim whitespace from URL.
429
  if (!empty($item['url'])) {
430
    $item['url'] = trim($item['url']);
431
  }
432

    
433
  // If no attributes are set then make sure $item['attributes'] is an empty
434
  // array, so $field['attributes'] can override it.
435
  if (empty($item['attributes'])) {
436
    $item['attributes'] = array();
437
  }
438

    
439
  // Serialize the attributes array.
440
  if (!is_string($item['attributes'])) {
441
    $item['attributes'] = serialize($item['attributes']);
442
  }
443

    
444
  // Don't save an invalid default value (e.g. 'http://').
445
  if ((isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url']) && is_object($entity)) {
446
    $langcode = !empty($entity) ? field_language($instance['entity_type'], $entity, $instance['field_name']) : LANGUAGE_NONE;
447
    if (!link_validate_url($item['url'], $langcode)) {
448
      unset($item['url']);
449
    }
450
  }
451
}
452

    
453
/**
454
 * Validates that the link field has been entered properly.
455
 */
456
function _link_validate(&$item, $delta, $field, $entity, $instance, $langcode, &$optional_field_found, &$errors) {
457
  if ($item['url'] && !(isset($instance['default_value'][$delta]['url']) && $item['url'] === $instance['default_value'][$delta]['url'] && !$instance['required'])) {
458
    // Validate the link.
459
    if (!link_validate_url(trim($item['url']), $langcode)) {
460
      $errors[$field['field_name']][$langcode][$delta][] = array(
461
        'error' => 'link_required',
462
        'message' => t('The value %value provided for %field is not a valid URL.', array(
463
          '%value' => trim($item['url']),
464
          '%field' => $instance['label'],
465
        )),
466
        'error_element' => array('url' => TRUE, 'title' => FALSE),
467
      );
468
    }
469
    // Require a title for the link if necessary.
470
    if ($instance['settings']['title'] == 'required' && strlen(trim($item['title'])) == 0) {
471
      $errors[$field['field_name']][$langcode][$delta][] = array(
472
        'error' => 'link_required',
473
        'message' => t('Titles are required for all links.'),
474
        'error_element' => array('url' => FALSE, 'title' => TRUE),
475
      );
476
    }
477
  }
478
  // Require a link if we have a title.
479
  if ($instance['settings']['url'] !== 'optional' && strlen(isset($item['title']) ? $item['title'] : NULL) > 0 && strlen(trim($item['url'])) == 0) {
480
    $errors[$field['field_name']][$langcode][$delta][] = array(
481
      'error' => 'link_required',
482
      'message' => t('You cannot enter a title without a link url.'),
483
      'error_element' => array('url' => TRUE, 'title' => FALSE),
484
    );
485
  }
486
  // In a totally bizzaro case, where URLs and titles are optional but the field
487
  // is required, ensure there is at least one link.
488
  if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional'
489
    && (strlen(trim($item['url'])) !== 0 || strlen(trim($item['title'])) !== 0)) {
490
    $optional_field_found = TRUE;
491
  }
492
  // Require entire field.
493
  if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] == 1 && !$optional_field_found && isset($instance['id'])) {
494
    $errors[$field['field_name']][$langcode][$delta][] = array(
495
      'error' => 'link_required',
496
      'message' => t('At least one title or URL must be entered.'),
497
      'error_element' => array('url' => FALSE, 'title' => TRUE),
498
    );
499
  }
500
}
501

    
502
/**
503
 * Clean up user-entered values for a link field according to field settings.
504
 *
505
 * @param array $item
506
 *   A single link item, usually containing url, title, and attributes.
507
 * @param int $delta
508
 *   The delta value if this field is one of multiple fields.
509
 * @param array $field
510
 *   The CCK field definition.
511
 * @param object $entity
512
 *   The entity containing this link.
513
 *
514
 * @codingStandardsIgnoreStart
515
 */
516
function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) {
517
  // @codingStandardsIgnoreEnd
518
  // Don't try to process empty links.
519
  if (empty($item['url']) && empty($item['title'])) {
520
    return;
521
  }
522
  if (empty($item['html'])) {
523
    $item['html'] = FALSE;
524
  }
525

    
526
  // Replace URL tokens.
527
  $entity_type = $instance['entity_type'];
528
  $entity_info = entity_get_info($entity_type);
529
  $property_id = $entity_info['entity keys']['id'];
530
  $entity_token_type = isset($entity_info['token type']) ? $entity_info['token type'] : (
531
  $entity_type == 'taxonomy_term' || $entity_type == 'taxonomy_vocabulary' ? str_replace('taxonomy_', '', $entity_type) : $entity_type
532
  );
533
  if (isset($instance['settings']['enable_tokens']) && $instance['settings']['enable_tokens']) {
534
    $text_tokens = token_scan($item['url']);
535
    if (!empty($text_tokens)) {
536
      // Load the entity if necessary for entities in views.
537
      if (isset($entity->{$property_id})) {
538
        $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
539
        $entity_loaded = array_pop($entity_loaded);
540
      }
541
      else {
542
        $entity_loaded = $entity;
543
      }
544
      $item['url'] = token_replace($item['url'], array($entity_token_type => $entity_loaded));
545
    }
546
  }
547

    
548
  $type = link_url_type($item['url']);
549
  // If the type of the URL cannot be determined and URL validation is disabled,
550
  // then assume LINK_EXTERNAL for later processing.
551
  if ($type == FALSE && $instance['settings']['validate_url'] === 0) {
552
    $type = LINK_EXTERNAL;
553
  }
554
  $url = link_cleanup_url($item['url']);
555
  $url_parts = _link_parse_url($url);
556

    
557
  if (!empty($url_parts['url'])) {
558
    $item['url'] = url($url_parts['url'],
559
      array(
560
        'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
561
        'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
562
        'absolute' => !empty($instance['settings']['absolute_url']),
563
        'html' => TRUE,
564
      )
565
    );
566
  }
567

    
568
  // Create a shortened URL for display.
569
  if ($type == LINK_EMAIL) {
570
    $display_url = str_replace('mailto:', '', $url);
571
  }
572
  else {
573
    $display_url = url($url_parts['url'],
574
      array(
575
        'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
576
        'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
577
        'absolute' => !empty($instance['settings']['absolute_url']),
578
      )
579
    );
580
  }
581
  if ($instance['settings']['display']['url_cutoff'] && strlen($display_url) > $instance['settings']['display']['url_cutoff']) {
582
    $display_url = substr($display_url, 0, $instance['settings']['display']['url_cutoff']) . "...";
583
  }
584
  $item['display_url'] = $display_url;
585

    
586
  // Use the title defined at the instance level.
587
  if ($instance['settings']['title'] == 'value' && strlen(trim($instance['settings']['title_value']))) {
588
    $title = $instance['settings']['title_value'];
589
    if (function_exists('i18n_string_translate')) {
590
      $i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value";
591
      $title = i18n_string_translate($i18n_string_name, $title);
592
    }
593
  }
594
  // Use the title defined by the user at the widget level.
595
  elseif (drupal_strlen(trim($item['title']))) {
596
    $title = $item['title'];
597
  }
598
  // Use the static title if a user-defined title is optional and a static title
599
  // has been defined.
600
  elseif ($instance['settings']['title'] == 'optional' && drupal_strlen(trim($instance['settings']['title_value']))) {
601
    $title = $instance['settings']['title_value'];
602
  }
603
  else {
604
    $title = '';
605
  }
606

    
607
  // Replace title tokens.
608
  if ($title && $instance['settings']['enable_tokens']) {
609
    $text_tokens = token_scan($title);
610
    if (!empty($text_tokens)) {
611
      // Load the entity if necessary for entities in views.
612
      if (isset($entity->{$property_id})) {
613
        $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
614
        $entity_loaded = array_pop($entity_loaded);
615
      }
616
      else {
617
        $entity_loaded = $entity;
618
      }
619
      $title = token_replace($title, array($entity_token_type => $entity_loaded));
620
    }
621
  }
622
  if ($title && ($instance['settings']['title'] == 'value' || $instance['settings']['enable_tokens'])) {
623
    $title = filter_xss($title, array(
624
      'b',
625
      'br',
626
      'code',
627
      'em',
628
      'i',
629
      'img',
630
      'span',
631
      'strong',
632
      'sub',
633
      'sup',
634
      'tt',
635
      'u',
636
    ));
637
    $item['html'] = TRUE;
638
  }
639
  $item['title'] = empty($title) && $title !== '0' ? $item['display_url'] : $title;
640

    
641
  if (!isset($item['attributes'])) {
642
    $item['attributes'] = array();
643
  }
644

    
645
  // Unserialize attributtes array if it has not been unserialized yet.
646
  if (!is_array($item['attributes'])) {
647
    $item['attributes'] = (array) unserialize($item['attributes']);
648
  }
649

    
650
  // Add default attributes.
651
  if (!is_array($instance['settings']['attributes'])) {
652
    $instance['settings']['attributes'] = _link_default_attributes();
653
  }
654
  else {
655
    $instance['settings']['attributes'] += _link_default_attributes();
656
  }
657

    
658
  // Merge item attributes with attributes defined at the field level.
659
  $item['attributes'] += $instance['settings']['attributes'];
660

    
661
  // If user is not allowed to choose target attribute, use default defined at
662
  // field level.
663
  if ($instance['settings']['attributes']['target'] != LINK_TARGET_USER) {
664
    $item['attributes']['target'] = $instance['settings']['attributes']['target'];
665
  }
666
  elseif ($item['attributes']['target'] == LINK_TARGET_USER) {
667
    $item['attributes']['target'] = LINK_TARGET_DEFAULT;
668
  }
669

    
670
  // Remove the target attribute if the default (no target) is selected.
671
  if (empty($item['attributes']) || (isset($item['attributes']['target']) && $item['attributes']['target'] == LINK_TARGET_DEFAULT)) {
672
    unset($item['attributes']['target']);
673
  }
674

    
675
  // Remove rel attribute for internal or external links if selected.
676
  if (isset($item['attributes']['rel']) && isset($instance['settings']['rel_remove']) && $instance['settings']['rel_remove'] != 'default') {
677
    if (($instance['settings']['rel_remove'] != 'rel_remove_internal' && $type != LINK_INTERNAL) ||
678
      ($instance['settings']['rel_remove'] != 'rel_remove_external' && $type != LINK_EXTERNAL)) {
679
      unset($item['attributes']['rel']);
680
    }
681
  }
682

    
683
  // Handle "title" link attribute.
684
  if (!empty($item['attributes']['title']) && module_exists('token')) {
685
    $text_tokens = token_scan($item['attributes']['title']);
686
    if (!empty($text_tokens)) {
687
      // Load the entity (necessary for entities in views).
688
      if (isset($entity->{$property_id})) {
689
        $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
690
        $entity_loaded = array_pop($entity_loaded);
691
      }
692
      else {
693
        $entity_loaded = $entity;
694
      }
695
      $item['attributes']['title'] = token_replace($item['attributes']['title'], array($entity_token_type => $entity_loaded));
696
    }
697
    $item['attributes']['title'] = filter_xss($item['attributes']['title'], array(
698
      'b',
699
      'br',
700
      'code',
701
      'em',
702
      'i',
703
      'img',
704
      'span',
705
      'strong',
706
      'sub',
707
      'sup',
708
      'tt',
709
      'u',
710
    ));
711
  }
712
  // Handle attribute classes.
713
  if (!empty($item['attributes']['class'])) {
714
    $classes = explode(' ', $item['attributes']['class']);
715
    foreach ($classes as &$class) {
716
      $class = drupal_clean_css_identifier($class);
717
    }
718
    $item['attributes']['class'] = implode(' ', $classes);
719
  }
720
  unset($item['attributes']['configurable_class']);
721

    
722
  // Remove title attribute if it's equal to link text.
723
  if (isset($item['attributes']['title']) && $item['attributes']['title'] == $item['title']) {
724
    unset($item['attributes']['title']);
725
  }
726
  unset($item['attributes']['configurable_title']);
727

    
728
  // Remove empty attributes.
729
  $item['attributes'] = array_filter($item['attributes']);
730
}
731

    
732
/**
733
 * Because parse_url doesn't work with relative urls.
734
 *
735
 * @param string $url
736
 *   URL to parse.
737
 *
738
 * @return array
739
 *   Array of url pieces - only 'url', 'query', and 'fragment'.
740
 */
741
function _link_parse_url($url) {
742
  $url_parts = array();
743
  // Separate out the anchor, if any.
744
  if (strpos($url, '#') !== FALSE) {
745
    $url_parts['fragment'] = substr($url, strpos($url, '#') + 1);
746
    $url = substr($url, 0, strpos($url, '#'));
747
  }
748
  // Separate out the query string, if any.
749
  if (strpos($url, '?') !== FALSE) {
750
    $query = substr($url, strpos($url, '?') + 1);
751
    $url_parts['query'] = _link_parse_str($query);
752
    $url = substr($url, 0, strpos($url, '?'));
753
  }
754
  $url_parts['url'] = $url;
755
  return $url_parts;
756
}
757

    
758
/**
759
 * Replaces the PHP parse_str() function.
760
 *
761
 * Because parse_str replaces the following characters in query parameters name
762
 * in order to maintain compatibility with deprecated register_globals
763
 * directive:
764
 *
765
 *   - chr(32) ( ) (space)
766
 *   - chr(46) (.) (dot)
767
 *   - chr(91) ([) (open square bracket)
768
 *   - chr(128) - chr(159) (various)
769
 *
770
 * @param string $query
771
 *   Query to parse.
772
 *
773
 * @return array
774
 *   Array of query parameters.
775
 *
776
 * @see http://php.net/manual/en/language.variables.external.php#81080
777
 */
778
function _link_parse_str($query) {
779
  $query_array = array();
780

    
781
  $pairs = explode('&', $query);
782
  foreach ($pairs as $pair) {
783
    $name_value = explode('=', $pair, 2);
784
    $name = urldecode($name_value[0]);
785
    $value = isset($name_value[1]) ? urldecode($name_value[1]) : NULL;
786
    $query_array[$name] = $value;
787
  }
788

    
789
  return $query_array;
790
}
791

    
792
/**
793
 * Implements hook_theme().
794
 */
795
function link_theme() {
796
  return array(
797
    'link_formatter_link_default' => array(
798
      'variables' => array('element' => NULL, 'field' => NULL),
799
    ),
800
    'link_formatter_link_plain' => array(
801
      'variables' => array('element' => NULL, 'field' => NULL),
802
    ),
803
    'link_formatter_link_host' => array(
804
      'variables' => array('element' => NULL),
805
    ),
806
    'link_formatter_link_absolute' => array(
807
      'variables' => array('element' => NULL, 'field' => NULL),
808
    ),
809
    'link_formatter_link_domain' => array(
810
      'variables' => array(
811
        'element' => NULL,
812
        'display' => NULL,
813
        'field' => NULL,
814
      ),
815
    ),
816
    'link_formatter_link_no_protocol' => array(
817
      'variables' => array('element' => NULL, 'field' => NULL),
818
    ),
819
    'link_formatter_link_title_plain' => array(
820
      'variables' => array('element' => NULL, 'field' => NULL),
821
    ),
822
    'link_formatter_link_url' => array(
823
      'variables' => array('element' => NULL, 'field' => NULL),
824
    ),
825
    'link_formatter_link_short' => array(
826
      'variables' => array('element' => NULL, 'field' => NULL),
827
    ),
828
    'link_formatter_link_label' => array(
829
      'variables' => array('element' => NULL, 'field' => NULL),
830
    ),
831
    'link_formatter_link_separate' => array(
832
      'variables' => array('element' => NULL, 'field' => NULL),
833
    ),
834
    'link_field' => array(
835
      'render element' => 'element',
836
    ),
837
  );
838
}
839

    
840
/**
841
 * Formats a link field widget.
842
 */
843
function theme_link_field($vars) {
844
  drupal_add_css(drupal_get_path('module', 'link') . '/link.css');
845
  $element = $vars['element'];
846
  // Prefix single value link fields with the name of the field.
847
  if (empty($element['#field']['multiple'])) {
848
    if (isset($element['url']) && !isset($element['title'])) {
849
      $element['url']['#title_display'] = 'invisible';
850
    }
851
  }
852

    
853
  $output = '';
854
  $output .= '<div class="link-field-subrow clearfix">';
855
  if (isset($element['title'])) {
856
    $output .= '<div class="link-field-title link-field-column">' . drupal_render($element['title']) . '</div>';
857
  }
858
  $output .= '<div class="link-field-url' . (isset($element['title']) ? ' link-field-column' : '') . '">' . drupal_render($element['url']) . '</div>';
859
  $output .= '</div>';
860
  if (!empty($element['attributes']['target'])) {
861
    $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['target']) . '</div>';
862
  }
863
  if (!empty($element['attributes']['title'])) {
864
    $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['title']) . '</div>';
865
  }
866
  if (!empty($element['attributes']['class'])) {
867
    $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['class']) . '</div>';
868
  }
869
  $output .= drupal_render_children($element);
870
  return $output;
871
}
872

    
873
/**
874
 * Implements hook_element_info().
875
 */
876
function link_element_info() {
877
  $elements = array();
878
  $elements['link_field'] = array(
879
    '#input' => TRUE,
880
    '#process' => array('link_field_process'),
881
    '#theme' => 'link_field',
882
    '#theme_wrappers' => array('form_element'),
883
  );
884
  return $elements;
885
}
886

    
887
/**
888
 * Returns the default attributes and their values.
889
 */
890
function _link_default_attributes() {
891
  return array(
892
    'target' => LINK_TARGET_DEFAULT,
893
    'class' => '',
894
    'rel' => '',
895
  );
896
}
897

    
898
/**
899
 * Processes the link type element before displaying the field.
900
 *
901
 * Build the form element. When creating a form using FAPI #process,
902
 * note that $element['#value'] is already set.
903
 *
904
 * The $fields array is in
905
 * $complete_form['#field_info'][$element['#field_name']].
906
 */
907
function link_field_process($element, $form_state, $complete_form) {
908
  $instance = field_widget_instance($element, $form_state);
909
  $settings = $instance['settings'];
910
  $element['url'] = array(
911
    '#type' => 'textfield',
912
    '#maxlength' => LINK_URL_MAX_LENGTH,
913
    '#title' => t('URL'),
914
    '#required' => ($element['#delta'] == 0 && $settings['url'] !== 'optional') ? $element['#required'] : FALSE,
915
    '#default_value' => isset($element['#value']['url']) ? $element['#value']['url'] : NULL,
916
  );
917
  if (in_array($settings['title'], array('optional', 'required'))) {
918
    // Figure out the label of the title field.
919
    if (!empty($settings['title_label_use_field_label'])) {
920
      // Use the element label as the title field label.
921
      $title_label = $element['#title'];
922
      // Hide the field label because there is no need for the duplicate labels.
923
      $element['#title_display'] = 'invisible';
924
    }
925
    else {
926
      $title_label = t('Title');
927
    }
928

    
929
    // Default value.
930
    $title_maxlength = 128;
931
    if (!empty($settings['title_maxlength'])) {
932
      $title_maxlength = $settings['title_maxlength'];
933
    }
934

    
935
    $element['title'] = array(
936
      '#type' => 'textfield',
937
      '#maxlength' => $title_maxlength,
938
      '#title' => $title_label,
939
      '#description' => t('The link title is limited to @maxlength characters maximum.', array('@maxlength' => $title_maxlength)),
940
      '#required' => ($settings['title'] == 'required' && (($element['#delta'] == 0 && $element['#required']) || !empty($element['#value']['url']))) ? TRUE : FALSE,
941
      '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
942
    );
943
  }
944
  elseif ($settings['title'] == 'select') {
945
    $options = drupal_map_assoc(array_filter(explode("\n", str_replace("\r", "\n", trim($settings['title_allowed_values'])))));
946
    $element['title'] = array(
947
      '#type' => 'select',
948
      '#title' => t('Title'),
949
      '#description' => t('Select the a title for this link.'),
950
      '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
951
      '#options' => $options,
952
    );
953
  }
954

    
955
  // Initialize field attributes as an array if it is not an array yet.
956
  if (!is_array($settings['attributes'])) {
957
    $settings['attributes'] = array();
958
  }
959
  // Add default attributes.
960
  $settings['attributes'] += _link_default_attributes();
961
  $attributes = isset($element['#value']['attributes']) ? $element['#value']['attributes'] : $settings['attributes'];
962
  if (!empty($settings['attributes']['target']) && $settings['attributes']['target'] == LINK_TARGET_USER) {
963
    $element['attributes']['target'] = array(
964
      '#type' => 'checkbox',
965
      '#title' => t('Open URL in a New Window'),
966
      '#return_value' => LINK_TARGET_NEW_WINDOW,
967
      '#default_value' => isset($attributes['target']) ? $attributes['target'] : FALSE,
968
    );
969
  }
970
  if (!empty($settings['attributes']['configurable_title']) && $settings['attributes']['configurable_title'] == 1) {
971
    $element['attributes']['title'] = array(
972
      '#type' => 'textfield',
973
      '#title' => t('Link "title" attribute'),
974
      '#default_value' => isset($attributes['title']) ? $attributes['title'] : '',
975
      '#field_prefix' => 'title = "',
976
      '#field_suffix' => '"',
977
    );
978
  }
979
  if (!empty($settings['attributes']['configurable_class']) && $settings['attributes']['configurable_class'] == 1) {
980
    $element['attributes']['class'] = array(
981
      '#type' => 'textfield',
982
      '#title' => t('Custom link class'),
983
      '#default_value' => isset($attributes['class']) ? $attributes['class'] : '',
984
      '#field_prefix' => 'class = "',
985
      '#field_suffix' => '"',
986
    );
987
  }
988

    
989
  // If the title field is available or there are field accepts multiple values
990
  // then allow the individual field items display the required asterisk if
991
  // needed.
992
  if (isset($element['title']) || isset($element['_weight'])) {
993
    // To prevent an extra required indicator, disable the required flag on the
994
    // base element since all the sub-fields are already required if desired.
995
    $element['#required'] = FALSE;
996
  }
997

    
998
  return $element;
999
}
1000

    
1001
/**
1002
 * Implements hook_field_formatter_info().
1003
 */
1004
function link_field_formatter_info() {
1005
  return array(
1006
    'link_default' => array(
1007
      'label' => t('Title, as link (default)'),
1008
      'field types' => array('link_field'),
1009
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1010
    ),
1011
    'link_title_plain' => array(
1012
      'label' => t('Title, as plain text'),
1013
      'field types' => array('link_field'),
1014
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1015
    ),
1016
    'link_host' => array(
1017
      'label' => t('Host, as plain text'),
1018
      'field types' => array('link_field'),
1019
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1020
    ),
1021
    'link_url' => array(
1022
      'label' => t('URL, as link'),
1023
      'field types' => array('link_field'),
1024
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1025
    ),
1026
    'link_plain' => array(
1027
      'label' => t('URL, as plain text'),
1028
      'field types' => array('link_field'),
1029
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1030
    ),
1031
    'link_absolute' => array(
1032
      'label' => t('URL, absolute'),
1033
      'field types' => array('link_field'),
1034
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1035
    ),
1036
    'link_domain' => array(
1037
      'label' => t('Domain, as link'),
1038
      'field types' => array('link_field'),
1039
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1040
      'settings' => array(
1041
        'strip_www' => FALSE,
1042
      ),
1043
    ),
1044
    'link_no_protocol' => array(
1045
      'label' => t('URL with the protocol removed'),
1046
      'field types' => array('link_field'),
1047
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1048
    ),
1049
    'link_short' => array(
1050
      'label' => t('Short, as link with title "Link"'),
1051
      'field types' => array('link_field'),
1052
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1053
    ),
1054
    'link_label' => array(
1055
      'label' => t('Label, as link with label as title'),
1056
      'field types' => array('link_field'),
1057
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1058
    ),
1059
    'link_separate' => array(
1060
      'label' => t('Separate title and URL'),
1061
      'field types' => array('link_field'),
1062
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1063
    ),
1064
  );
1065
}
1066

    
1067
/**
1068
 * Implements hook_field_formatter_settings_form().
1069
 */
1070
function link_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
1071
  $display = $instance['display'][$view_mode];
1072
  $settings = $display['settings'];
1073
  $element = array();
1074
  if ($display['type'] == 'link_domain') {
1075
    $element['strip_www'] = array(
1076
      '#title' => t('Strip www. from domain'),
1077
      '#type' => 'checkbox',
1078
      '#default_value' => $settings['strip_www'],
1079
    );
1080
  }
1081
  return $element;
1082
}
1083

    
1084
/**
1085
 * Implements hook_field_formatter_settings_summary().
1086
 */
1087
function link_field_formatter_settings_summary($field, $instance, $view_mode) {
1088

    
1089
  $display = $instance['display'][$view_mode];
1090

    
1091
  if ($display['type'] == 'link_domain') {
1092
    if ($display['settings']['strip_www']) {
1093
      return t('Strip www. from domain');
1094
    }
1095
    else {
1096
      return t('Leave www. in domain');
1097
    }
1098
  }
1099
  return '';
1100
}
1101

    
1102
/**
1103
 * Implements hook_field_formatter_view().
1104
 */
1105
function link_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
1106
  $elements = array();
1107
  foreach ($items as $delta => $item) {
1108
    $elements[$delta] = array(
1109
      '#theme' => 'link_formatter_' . $display['type'],
1110
      '#element' => $item,
1111
      '#field' => $instance,
1112
      '#display' => $display,
1113
    );
1114
  }
1115
  return $elements;
1116
}
1117

    
1118
/**
1119
 * Formats a link.
1120
 */
1121
function theme_link_formatter_link_default($vars) {
1122
  $link_options = $vars['element'];
1123
  unset($link_options['title']);
1124
  unset($link_options['url']);
1125

    
1126
  if (isset($link_options['attributes']['class'])) {
1127
    $link_options['attributes']['class'] = array($link_options['attributes']['class']);
1128
  }
1129
  // Display a normal link if both title and URL are available.
1130
  if (!empty($vars['element']['title']) && !empty($vars['element']['url'])) {
1131
    return l($vars['element']['title'], $vars['element']['url'], $link_options);
1132
  }
1133
  // If only a title, display the title.
1134
  elseif (!empty($vars['element']['title'])) {
1135
    return !empty($link_options['html']) ? $vars['element']['title'] : check_plain($vars['element']['title']);
1136
  }
1137
  elseif (!empty($vars['element']['url'])) {
1138
    return l($vars['element']['title'], $vars['element']['url'], $link_options);
1139
  }
1140
}
1141

    
1142
/**
1143
 * Formats a link (or its title) as plain text.
1144
 */
1145
function theme_link_formatter_link_plain($vars) {
1146
  $link_options = $vars['element'];
1147
  if (isset($link_options['title'])) {
1148
    unset($link_options['title']);
1149
  }
1150
  else {
1151
    $vars['element']['title'] = '';
1152
  }
1153
  unset($link_options['url']);
1154
  return empty($vars['element']['url']) ? check_plain($vars['element']['title']) : url($vars['element']['url'], $link_options);
1155
}
1156

    
1157
/**
1158
 * Theme function for 'host' text field formatter.
1159
 */
1160
function theme_link_formatter_link_host($vars) {
1161
  $host = @parse_url($vars['element']['url']);
1162
  return isset($host['host']) ? check_plain($host['host']) : '';
1163
}
1164

    
1165
/**
1166
 * Formats a link as an absolute URL.
1167
 */
1168
function theme_link_formatter_link_absolute($vars) {
1169
  $absolute = array('absolute' => TRUE);
1170
  return empty($vars['element']['url']) ? '' : url($vars['element']['url'], $absolute + $vars['element']);
1171
}
1172

    
1173
/**
1174
 * Formats a link using the URL's domain for it's link text.
1175
 */
1176
function theme_link_formatter_link_domain($vars) {
1177
  $link_options = $vars['element'];
1178
  unset($link_options['title']);
1179
  unset($link_options['url']);
1180
  $domain = parse_url($vars['element']['display_url'], PHP_URL_HOST);
1181
  if (!empty($vars['display']['settings']['strip_www'])) {
1182
    $domain = str_replace('www.', '', $domain);
1183
  }
1184
  return $vars['element']['url'] ? l($domain, $vars['element']['url'], $link_options) : '';
1185
}
1186

    
1187
/**
1188
 * Formats a link without the http:// or https://.
1189
 */
1190
function theme_link_formatter_link_no_protocol($vars) {
1191
  $link_options = $vars['element'];
1192
  unset($link_options['title']);
1193
  unset($link_options['url']);
1194
  // We drop any scheme of the url.
1195
  $scheme = parse_url($vars['element']['url']);
1196
  $search = '/' . preg_quote($scheme['scheme'] . '://', '/') . '/';
1197
  $replace = '';
1198
  $display_url = preg_replace($search, $replace, $vars['element']['url'], 1);
1199

    
1200
  return $vars['element']['url'] ? l($display_url, $vars['element']['url'], $link_options) : '';
1201
}
1202

    
1203
/**
1204
 * Formats a link's title as plain text.
1205
 */
1206
function theme_link_formatter_link_title_plain($vars) {
1207
  return empty($vars['element']['title']) ? '' : check_plain(decode_entities($vars['element']['title']));
1208
}
1209

    
1210
/**
1211
 * Formats a link using an alternate display URL for its link text.
1212
 */
1213
function theme_link_formatter_link_url($vars) {
1214
  $link_options = $vars['element'];
1215
  unset($link_options['title']);
1216
  unset($link_options['url']);
1217
  return $vars['element']['url'] ? l($vars['element']['display_url'], $vars['element']['url'], $link_options) : '';
1218
}
1219

    
1220
/**
1221
 * Formats a link using "Link" as the link text.
1222
 */
1223
function theme_link_formatter_link_short($vars) {
1224
  $link_options = $vars['element'];
1225
  unset($link_options['title']);
1226
  unset($link_options['url']);
1227
  return $vars['element']['url'] ? l(t('Link'), $vars['element']['url'], $link_options) : '';
1228
}
1229

    
1230
/**
1231
 * Formats a link using the field's label as link text.
1232
 */
1233
function theme_link_formatter_link_label($vars) {
1234
  $link_options = $vars['element'];
1235
  unset($link_options['title']);
1236
  unset($link_options['url']);
1237
  return $vars['element']['url'] ? l($vars['field']['label'], $vars['element']['url'], $link_options) : '';
1238
}
1239

    
1240
/**
1241
 * Formats a link as separate title and URL elements.
1242
 */
1243
function theme_link_formatter_link_separate($vars) {
1244
  $class = empty($vars['element']['attributes']['class']) ? '' : ' ' . $vars['element']['attributes']['class'];
1245
  unset($vars['element']['attributes']['class']);
1246
  $link_options = $vars['element'];
1247
  unset($link_options['title']);
1248
  unset($link_options['url']);
1249
  $title = empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']);
1250

    
1251
  // @TODO static html markup looks not very elegant
1252
  // needs smarter output solution and an optional title/url seperator
1253
  $url_parts = _link_parse_url($vars['element']['url']);
1254
  $output = '';
1255
  $output .= '<div class="link-item ' . $class . '">';
1256
  if (!empty($title)) {
1257
    $output .= '<div class="link-title">' . $title . '</div>';
1258
  }
1259
  $output .= '<div class="link-url">' . l($url_parts['url'], $vars['element']['url'], $link_options) . '</div>';
1260
  $output .= '</div>';
1261
  return $output;
1262
}
1263

    
1264
/**
1265
 * Implements hook_token_list().
1266
 *
1267
 * @TODO: hook_token_list no longer exists - this should change to
1268
 *   hook_token_info().
1269
 */
1270
function link_token_list($type = 'all') {
1271
  if ($type === 'field' || $type === 'all') {
1272
    $tokens = array();
1273
    $tokens['link']['url'] = t("Link URL");
1274
    $tokens['link']['title'] = t("Link title");
1275
    $tokens['link']['view'] = t("Formatted html link");
1276
    return $tokens;
1277
  }
1278
}
1279

    
1280
/**
1281
 * Implements hook_token_values().
1282
 *
1283
 * @TODO: hook_token_values no longer exists - this should change to
1284
 *   hook_tokens().
1285
 */
1286
function link_token_values($type, $object = NULL) {
1287
  if ($type === 'field') {
1288
    $item = $object[0];
1289

    
1290
    $tokens['url'] = $item['url'];
1291
    $tokens['title'] = $item['title'];
1292
    $tokens['view'] = isset($item['view']) ? $item['view'] : '';
1293

    
1294
    return $tokens;
1295
  }
1296
}
1297

    
1298
/**
1299
 * Implements hook_views_api().
1300
 */
1301
function link_views_api() {
1302
  return array(
1303
    'api' => 2,
1304
    'path' => drupal_get_path('module', 'link') . '/views',
1305
  );
1306
}
1307

    
1308
/**
1309
 * Forms a valid URL if possible from an entered address.
1310
 *
1311
 * Trims whitespace and automatically adds an http:// to addresses without a
1312
 * protocol specified.
1313
 *
1314
 * @param string $url
1315
 *   The url entered by the user.
1316
 * @param string $protocol
1317
 *   The protocol to be prepended to the url if one is not specified.
1318
 */
1319
function link_cleanup_url($url, $protocol = 'http') {
1320
  $url = trim($url);
1321
  $type = link_url_type($url);
1322

    
1323
  if ($type === LINK_EXTERNAL) {
1324
    // Check if there is no protocol specified.
1325
    $protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i", $url);
1326
    if (empty($protocol_match)) {
1327
      // But should there be? Add an automatic http:// if it starts with a
1328
      // domain name.
1329
      $link_domains = _link_domains();
1330
      $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)(' . $link_domains . '|[a-z]{2}))/i', $url);
1331
      if (!empty($domain_match)) {
1332
        $url = $protocol . "://" . $url;
1333
      }
1334
    }
1335
  }
1336

    
1337
  return $url;
1338
}
1339

    
1340
/**
1341
 * Validates a URL.
1342
 *
1343
 * @param string $text
1344
 *   Url to be validated.
1345
 * @param string $langcode
1346
 *   An optional language code to look up the path in.
1347
 *
1348
 * @return bool
1349
 *   True if a valid link, FALSE otherwise.
1350
 */
1351
function link_validate_url($text, $langcode = NULL) {
1352

    
1353
  $text = _link_clean_relative($text);
1354
  $text = link_cleanup_url($text);
1355
  $type = link_url_type($text);
1356

    
1357
  if ($type && ($type == LINK_INTERNAL || $type == LINK_EXTERNAL)) {
1358
    $flag = valid_url($text, TRUE);
1359
    if (!$flag) {
1360
      $normal_path = drupal_get_normal_path($text, $langcode);
1361
      $parsed_link = parse_url($normal_path, PHP_URL_PATH);
1362
      if ($normal_path != $parsed_link) {
1363
        $normal_path = $parsed_link;
1364
      }
1365
      $flag = drupal_valid_path($normal_path);
1366
    }
1367
    if (!$flag) {
1368
      $flag = file_exists($normal_path);
1369
    }
1370
    if (!$flag) {
1371
      $uri = file_build_uri($normal_path);
1372
      $flag = file_exists($uri);
1373
    }
1374
  }
1375
  else {
1376
    $flag = (bool) $type;
1377
  }
1378

    
1379
  return $flag;
1380
}
1381

    
1382
/**
1383
 * Cleaner of relatives urls.
1384
 *
1385
 * @param string $url
1386
 *   The url to clean up the relative protocol.
1387
 */
1388
function _link_clean_relative($url) {
1389
  $check = substr($url, 0, 2);
1390
  if (isset($_SERVER['HTTPS']) &&
1391
    ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1) ||
1392
    isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
1393
    $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
1394
    $protocol = 'https://';
1395
  }
1396
  else {
1397
    $protocol = 'http://';
1398
  }
1399

    
1400
  if ($check == '//') {
1401
    $url = str_replace('//', $protocol, $url);
1402
  }
1403

    
1404
  return $url;
1405
}
1406

    
1407
/**
1408
 * Type check a URL.
1409
 *
1410
 * Accepts all URLs following RFC 1738 standard for URL formation and all e-mail
1411
 * addresses following the RFC 2368 standard for mailto address formation.
1412
 *
1413
 * @param string $text
1414
 *   Url to be checked.
1415
 *
1416
 * @return mixed
1417
 *   Returns boolean FALSE if the URL is not valid. On success, returns one of
1418
 *   the LINK_(linktype) constants.
1419
 */
1420
function link_url_type($text) {
1421
  // @TODO Complete letters.
1422
  // @codingStandardsIgnoreStart
1423
  $link_ichars_domain = (string) html_entity_decode(implode("", array(
1424
    "&#x00BF;", // ¿
1425
    "&#x00C0;", // À
1426
    "&#x00C1;", // Á
1427
    "&#x00C2;", // Â
1428
    "&#x00C3;", // Ã
1429
    "&#x00C4;", // Ä
1430
    "&#x00C5;", // Å
1431
    "&#x00C6;", // Æ
1432
    "&#x00C7;", // Ç
1433
    "&#x00C8;", // È
1434
    "&#x00C9;", // É
1435
    "&#x00CA;", // Ê
1436
    "&#x00CB;", // Ë
1437
    "&#x00CC;", // Ì
1438
    "&#x00CD;", // Í
1439
    "&#x00CE;", // Î
1440
    "&#x00CF;", // Ï
1441
    "&#x00D0;", // Ð
1442
    "&#x00D1;", // Ñ
1443
    "&#x00D2;", // Ò
1444
    "&#x00D3;", // Ó
1445
    "&#x00D4;", // Ô
1446
    "&#x00D5;", // Õ
1447
    "&#x00D6;", // Ö
1448
    // ×
1449
    "&#x00D8;", // Ø
1450
    "&#x00D9;", // Ù
1451
    "&#x00DA;", // Ú
1452
    "&#x00DB;", // Û
1453
    "&#x00DC;", // Ü
1454
    "&#x00DD;", // Ý
1455
    "&#x00DE;", // Þ
1456
    // ß (see LINK_ICHARS)
1457
    "&#x00E0;", // à
1458
    "&#x00E1;", // á
1459
    "&#x00E2;", // â
1460
    "&#x00E3;", // ã
1461
    "&#x00E4;", // ä
1462
    "&#x00E5;", // å
1463
    "&#x00E6;", // æ
1464
    "&#x00E7;", // ç
1465
    "&#x00E8;", // è
1466
    "&#x00E9;", // é
1467
    "&#x00EA;", // ê
1468
    "&#x00EB;", // ë
1469
    "&#x00EC;", // ì
1470
    "&#x00ED;", // í
1471
    "&#x00EE;", // î
1472
    "&#x00EF;", // ï
1473
    "&#x00F0;", // ð
1474
    "&#x00F1;", // ñ
1475
    "&#x00F2;", // ò
1476
    "&#x00F3;", // ó
1477
    "&#x00F4;", // ô
1478
    "&#x00F5;", // õ
1479
    "&#x00F6;", // ö
1480
    // ÷
1481
    "&#x00F8;", // ø
1482
    "&#x00F9;", // ù
1483
    "&#x00FA;", // ú
1484
    "&#x00FB;", // û
1485
    "&#x00FC;", // ü
1486
    "&#x00FD;", // ý
1487
    "&#x00FE;", // þ
1488
    "&#x00FF;", // ÿ
1489
    "&#x0152;", // Œ
1490
    "&#x0153;", // œ
1491
    "&#x0178;", // Ÿ
1492
  )), ENT_QUOTES, 'UTF-8');
1493
  // @codingStandardsIgnoreEnd
1494

    
1495
  $link_ichars = $link_ichars_domain . (string) html_entity_decode(implode("", array(
1496
      // ß.
1497
      "&#x00DF;",
1498
    )), ENT_QUOTES, 'UTF-8');
1499
  $allowed_protocols = variable_get('filter_allowed_protocols', array(
1500
    'http',
1501
    'https',
1502
    'ftp',
1503
    'news',
1504
    'nntp',
1505
    'telnet',
1506
    'mailto',
1507
    'irc',
1508
    'ssh',
1509
    'sftp',
1510
    'webcal',
1511
  ));
1512
  $link_domains = _link_domains();
1513

    
1514
  // Starting a parenthesis group with (?: means that it is grouped, but is not
1515
  // captured.
1516
  $protocol = '((?:' . implode("|", $allowed_protocols) . '):\/\/)';
1517
  $authentication = "(?:(?:(?:[\w\.\-\+!$&'\(\)*\+,;=" . $link_ichars . "]|%[0-9a-f]{2})+(?::(?:[\w" . $link_ichars . "\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})*)?)?@)";
1518
  $domain = '(?:(?:[a-z0-9' . $link_ichars_domain . ']([a-z0-9' . $link_ichars_domain . '\-_\[\]])*)(\.(([a-z0-9' . $link_ichars_domain . '\-_\[\]])+\.)*(' . $link_domains . '|[a-z]{2}))?)';
1519
  $ipv4 = '(?:[0-9]{1,3}(\.[0-9]{1,3}){3})';
1520
  $ipv6 = '(?:[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7})';
1521
  $port = '(?::([0-9]{1,5}))';
1522
  // Pattern specific to external links.
1523
  $external_pattern = '/^' . $protocol . '?' . $authentication . '?(' . $domain . '|' . $ipv4 . '|' . $ipv6 . ' |localhost)' . $port . '?';
1524

    
1525
  // Pattern specific to internal links.
1526
  $internal_pattern = "/^(?:[a-z0-9" . $link_ichars . "_\-+\[\] ]+)";
1527
  $internal_pattern_file = "/^(?:[a-z0-9" . $link_ichars . "_\-+\[\]\. \/\(\)][a-z0-9" . $link_ichars . "_\-+\[\]\. \(\)][a-z0-9" . $link_ichars . "_\-+\[\]\. \/\(\)]+)$/i";
1528

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

    
1534
  // The rest of the path for a standard URL.
1535
  // @codingStandardsIgnoreLine
1536
  $end = $directories . '?' . $query . '?' . $anchor . '?' . '$/i';
1537

    
1538
  $message_id = '[^@].*@' . $domain;
1539
  $newsgroup_name = '(?:[0-9a-z+-]*\.)*[0-9a-z+-]*';
1540
  $news_pattern = '/^news:(' . $newsgroup_name . '|' . $message_id . ')$/i';
1541

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

    
1545
  if (strpos($text, '<front>') === 0) {
1546
    return LINK_FRONT;
1547
  }
1548
  if (in_array('mailto', $allowed_protocols) && preg_match($email_pattern, $text)) {
1549
    return LINK_EMAIL;
1550
  }
1551
  if (in_array('news', $allowed_protocols) && preg_match($news_pattern, $text)) {
1552
    return LINK_NEWS;
1553
  }
1554
  if (preg_match($internal_pattern . $end, $text)) {
1555
    return LINK_INTERNAL;
1556
  }
1557
  if (drupal_valid_path($text) && url_is_external($text) == FALSE) {
1558
    return LINK_INTERNAL;
1559
  }
1560
  if (preg_match($external_pattern . $end, $text)) {
1561
    return LINK_EXTERNAL;
1562
  }
1563
  if (preg_match($internal_pattern_file, $text)) {
1564
    return LINK_INTERNAL;
1565
  }
1566

    
1567
  return FALSE;
1568
}
1569

    
1570
/**
1571
 * Returns the list of allowed domains.
1572
 *
1573
 * If the variable link_allowed_domains is set, restrict allowed domains to the
1574
 * strings in that array. If the variable link_allowed_domains is not set, allow
1575
 * all domains between 2 and 63 characters in length.
1576
 * See https://tools.ietf.org/html/rfc1034.
1577
 */
1578
function _link_domains() {
1579
  $link_allowed_domains = variable_get('link_allowed_domains', array());
1580
  return empty($link_allowed_domains) ? '[a-z][a-z0-9-]{1,62}' : implode('|', $link_allowed_domains);
1581
}
1582

    
1583
/**
1584
 * Implements hook_migrate_field_alter().
1585
 */
1586
function link_content_migrate_field_alter(&$field_value, $instance_value) {
1587
  if ($field_value['type'] == 'link') {
1588
    // Adjust the field type.
1589
    $field_value['type'] = 'link_field';
1590
    // Remove settings that are now on the instance.
1591
    foreach (array(
1592
               'attributes',
1593
               'display',
1594
               'url',
1595
               'title',
1596
               'title_value',
1597
               'enable_tokens',
1598
               'validate_url',
1599
             ) as $setting) {
1600
      unset($field_value['settings'][$setting]);
1601
    }
1602
  }
1603
}
1604

    
1605
/**
1606
 * Implements hook_migrate_instance_alter().
1607
 *
1608
 * Widget type also changed to link_field.
1609
 */
1610
function link_content_migrate_instance_alter(&$instance_value, $field_value) {
1611
  if ($field_value['type'] == 'link') {
1612
    // Grab settings that were previously on the field.
1613
    foreach (array(
1614
               'attributes',
1615
               'display',
1616
               'url',
1617
               'title',
1618
               'title_value',
1619
               'enable_tokens',
1620
               'validate_url',
1621
             ) as $setting) {
1622
      if (isset($field_value['settings'][$setting])) {
1623
        $instance_value['settings'][$setting] = $field_value['settings'][$setting];
1624
      }
1625
    }
1626
    // Adjust widget type.
1627
    if ($instance_value['widget']['type'] == 'link') {
1628
      $instance_value['widget']['type'] = 'link_field';
1629
    }
1630
    // Adjust formatter types.
1631
    foreach ($instance_value['display'] as $context => $settings) {
1632
      if (in_array($settings['type'], array(
1633
        'default',
1634
        'title_plain',
1635
        'url',
1636
        'plain',
1637
        'short',
1638
        'label',
1639
        'separate',
1640
      ))) {
1641
        $instance_value['display'][$context]['type'] = 'link_' . $settings['type'];
1642
      }
1643
    }
1644
  }
1645
}
1646

    
1647
/**
1648
 * Implements hook_field_settings_form().
1649
 */
1650
function link_field_settings_form() {
1651
  return array();
1652
}
1653

    
1654
/**
1655
 * Additional callback to adapt the property info of link fields.
1656
 *
1657
 * @see entity_metadata_field_entity_property_info()
1658
 */
1659
function link_field_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
1660
  $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
1661
  // Define a data structure so it's possible to deal with both the link title
1662
  // and URL.
1663
  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
1664
  $property['setter callback'] = 'entity_metadata_field_verbatim_set';
1665

    
1666
  // Auto-create the field item as soon as a property is set.
1667
  $property['auto creation'] = 'link_field_item_create';
1668

    
1669
  $property['property info'] = link_field_item_property_info();
1670
  $property['property info']['url']['required'] = !$instance['settings']['url'];
1671
  $property['property info']['title']['required'] = ($instance['settings']['title'] == 'required');
1672
  if ($instance['settings']['title'] == 'none') {
1673
    unset($property['property info']['title']);
1674
  }
1675
  unset($property['query callback']);
1676
}
1677

    
1678
/**
1679
 * Callback for creating a new, empty link field item.
1680
 *
1681
 * @see link_field_property_info_callback()
1682
 */
1683
function link_field_item_create() {
1684
  return array('title' => NULL, 'url' => NULL, 'display_url' => NULL);
1685
}
1686

    
1687
/**
1688
 * Defines info for the properties of the link-field item data structure.
1689
 */
1690
function link_field_item_property_info() {
1691
  $properties['title'] = array(
1692
    'type' => 'text',
1693
    'label' => t('The title of the link.'),
1694
    'setter callback' => 'entity_property_verbatim_set',
1695
  );
1696
  $properties['url'] = array(
1697
    'type' => 'text',
1698
    'label' => t('The URL of the link.'),
1699
    'setter callback' => 'entity_property_verbatim_set',
1700
  );
1701
  $properties['attributes'] = array(
1702
    'type' => 'struct',
1703
    'label' => t('The attributes of the link.'),
1704
    'setter callback' => 'entity_property_verbatim_set',
1705
    'getter callback' => 'link_attribute_property_get',
1706
  );
1707
  $properties['display_url'] = array(
1708
    'type' => 'uri',
1709
    'label' => t('The full URL of the link.'),
1710
    'setter callback' => 'entity_property_verbatim_set',
1711
  );
1712
  return $properties;
1713
}
1714

    
1715
/**
1716
 * Entity property info getter callback for link attributes.
1717
 */
1718
function link_attribute_property_get($data, array $options, $name, $type, $info) {
1719
  return isset($data[$name]) ? array_filter($data[$name]) : array();
1720
}
1721

    
1722
/**
1723
 * Implements hook_field_update_instance().
1724
 */
1725
function link_field_update_instance($instance, $prior_instance) {
1726
  if (function_exists('i18n_string_update') && isset($instance['widget']) && $instance['widget']['type'] == 'link_field' && $prior_instance['settings']['title_value'] != $instance['settings']['title_value']) {
1727
    $i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value";
1728
    i18n_string_update($i18n_string_name, $instance['settings']['title_value']);
1729
  }
1730
}
1731

    
1732
/**
1733
 * Implements hook_i18n_string_list_TEXTGROUP_alter().
1734
 */
1735
function link_i18n_string_list_field_alter(&$strings, $type = NULL, $object = NULL) {
1736
  if ($type != 'field_instance' || !is_array($object) || !isset($object['widget']['type'])) {
1737
    return;
1738
  }
1739
  if ($object['widget']['type'] == 'link_field' && isset($object['settings']['title_value'])) {
1740
    $strings['field'][$object['field_name']][$object['bundle']]['title_value']['string'] = $object['settings']['title_value'];
1741
  }
1742
}