Projet

Général

Profil

Paste
Télécharger (64,4 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / link / link.module @ 8e7483ab

1
<?php
2

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

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

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

    
27
/**
28
 * Implements hook_help().
29
 */
30
function link_help($path, $arg) {
31
  switch ($path) {
32
    case 'admin/help#link':
33
      $output = '<p><strong>' . t('About') . '</strong></p>';
34
      $output .= '<p>' . t('The link provides a standard custom content field for links. Links can be easily added to any content types and profiles and include advanced validating and different ways of storing internal or external links and URLs. It also supports additional link text title, site wide tokens for titles and title attributes, target attributes, css class attribution, static repeating values, input conversion, and many more.') . '</p>';
35
      $output .= '<p><strong>' . t('Requirements / Dependencies') . '</strong></p>';
36
      $output .= '<p>' . 'Fields API is provided already by core [no dependencies].' . '</p>';
37
      $output .= '<p><strong>Configuration</strong></p>';
38
      $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>';
39
      return $output;
40
  }
41
}
42

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
202
  // Target options. E.g. New window = target="_blank".
203
  $target_options = array(
204
    LINK_TARGET_DEFAULT => t('Default (no target attribute)'),
205
    LINK_TARGET_TOP => t('Open link in window root'),
206
    LINK_TARGET_NEW_WINDOW => t('Open link in new window'),
207
    LINK_TARGET_USER => t('Allow the user to choose'),
208
  );
209

    
210
  $form['attributes'] = array(
211
    '#tree' => TRUE,
212
  );
213

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

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

    
296
/**
297
 * Implements hook_field_is_empty().
298
 */
299
function link_field_is_empty($item, $field) {
300
  return empty($item['title']) && empty($item['url']);
301
}
302

    
303
/**
304
 * Implements hook_field_load().
305
 */
306
function link_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
307
  foreach ($entities as $id => $entity) {
308
    foreach ($items[$id] as $delta => $item) {
309
      $items[$id][$delta]['attributes'] = _link_load($field, $item, $instances[$id]);
310
      $items[$id][$delta]['original_title'] = $item['title'];
311
      $items[$id][$delta]['original_url'] = $item['url'];
312
    }
313
  }
314
}
315

    
316
/**
317
 * Implements hook_field_validate().
318
 */
319
function link_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
320
  $optional_field_found = FALSE;
321
  if ($instance['settings']['validate_url'] !== 0 || is_null($instance['settings']['validate_url']) || !isset($instance['settings']['validate_url'])) {
322
    foreach ($items as $delta => $value) {
323
      _link_validate($items[$delta], $delta, $field, $entity, $instance, $langcode, $optional_field_found, $errors);
324
    }
325
  }
326

    
327
  foreach ($items as $delta => $value) {
328
    if (isset($value['attributes']) && is_string($value['attributes'])) {
329
      $errors[$field['field_name']][$langcode][$delta][] = array(
330
        'error' => 'link_required',
331
        'message' => t('String values are not acceptable for attributes.'),
332
        'error_element' => array('url' => TRUE, 'title' => FALSE),
333
      );
334
    }
335
  }
336

    
337
  if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] && !$optional_field_found) {
338
    $errors[$field['field_name']][$langcode][0][] = array(
339
      'error' => 'link_required',
340
      'message' => t('At least one title or URL must be entered.'),
341
      'error_element' => array('url' => FALSE, 'title' => TRUE),
342
    );
343
  }
344
}
345

    
346
/**
347
 * Implements hook_field_insert().
348
 */
349
function link_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
350
  foreach ($items as $delta => $value) {
351
    _link_process($items[$delta], $delta, $field, $entity, $instance);
352
  }
353
}
354

    
355
/**
356
 * Implements hook_field_update().
357
 */
358
function link_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
359
  foreach ($items as $delta => $value) {
360
    _link_process($items[$delta], $delta, $field, $entity, $instance);
361
  }
362
}
363

    
364
/**
365
 * Implements hook_field_prepare_view().
366
 */
367
function link_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
368
  foreach ($items as $entity_id => $entity_items) {
369
    foreach ($entity_items as $delta => $value) {
370
      _link_sanitize($items[$entity_id][$delta], $delta, $field, $instances[$entity_id], $entities[$entity_id]);
371
    }
372
  }
373
}
374

    
375
/**
376
 * Implements hook_field_widget_info().
377
 */
378
function link_field_widget_info() {
379
  return array(
380
    'link_field' => array(
381
      'label' => 'Link',
382
      'field types' => array('link_field'),
383
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
384
    ),
385
  );
386
}
387

    
388
/**
389
 * Implements hook_field_widget_form().
390
 */
391
function link_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
392
  $element += array(
393
    '#type' => $instance['widget']['type'],
394
    '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
395
  );
396
  return $element;
397
}
398

    
399
/**
400
 * Implements hook_field_widget_error().
401
 */
402
function link_field_widget_error($element, $error, $form, &$form_state) {
403
  if (!empty($error['error_element']['title'])) {
404
    form_error($element['title'], $error['message']);
405
  }
406
  elseif (!empty($error['error_element']['url'])) {
407
    form_error($element['url'], $error['message']);
408
  }
409
}
410

    
411
/**
412
 * Unpacks the item attributes for use.
413
 */
414
function _link_load($field, $item, $instance) {
415
  if (isset($item['attributes'])) {
416
    if (!is_array($item['attributes'])) {
417
      $item['attributes'] = unserialize($item['attributes']);
418
    }
419
    return $item['attributes'];
420
  }
421
  elseif (isset($instance['settings']['attributes'])) {
422
    return $instance['settings']['attributes'];
423
  }
424
  else {
425
    return $field['settings']['attributes'];
426
  }
427
}
428

    
429
/**
430
 * Prepares the item attributes and url for storage.
431
 *
432
 * @param array $item
433
 *   Link field values.
434
 * @param array $delta
435
 *   The sequence number for current values.
436
 * @param array $field
437
 *   The field structure array.
438
 * @param object $entity
439
 *   Entity object.
440
 * @param array $instance
441
 *   The instance structure for $field on $entity's bundle.
442
 *
443
 * @codingStandardsIgnoreStart
444
 */
445
function _link_process(&$item, $delta, $field, $entity, $instance) {
446
  // @codingStandardsIgnoreEnd
447
  // Trim whitespace from URL.
448
  if (!empty($item['url'])) {
449
    $item['url'] = trim($item['url']);
450
  }
451

    
452
  // If no attributes are set then make sure $item['attributes'] is an empty
453
  // array, so $field['attributes'] can override it.
454
  if (empty($item['attributes'])) {
455
    $item['attributes'] = array();
456
  }
457

    
458
  // Serialize the attributes array.
459
  if (!is_string($item['attributes'])) {
460
    $item['attributes'] = serialize($item['attributes']);
461
  }
462

    
463
  // Don't save an invalid default value (e.g. 'http://').
464
  if ((isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url']) && is_object($entity)) {
465
    $langcode = !empty($entity) ? field_language($instance['entity_type'], $entity, $instance['field_name']) : LANGUAGE_NONE;
466
    if (!link_validate_url($item['url'], $langcode)) {
467
      unset($item['url']);
468
    }
469
  }
470
}
471

    
472
/**
473
 * Validates that the link field has been entered properly.
474
 */
475
function _link_validate(&$item, $delta, $field, $entity, $instance, $langcode, &$optional_field_found, &$errors) {
476
  if ($item['url'] && !(isset($instance['default_value'][$delta]['url']) && $item['url'] === $instance['default_value'][$delta]['url'] && !$instance['required'])) {
477
    // Validate the link.
478
    if (!link_validate_url(trim($item['url']), $langcode)) {
479
      $errors[$field['field_name']][$langcode][$delta][] = array(
480
        'error' => 'link_required',
481
        'message' => t('The value %value provided for %field is not a valid URL.', array(
482
          '%value' => trim($item['url']),
483
          '%field' => $instance['label'],
484
        )),
485
        'error_element' => array('url' => TRUE, 'title' => FALSE),
486
      );
487
    }
488
    // Require a title for the link if necessary.
489
    if ($instance['settings']['title'] == 'required' && strlen(trim($item['title'])) == 0) {
490
      $errors[$field['field_name']][$langcode][$delta][] = array(
491
        'error' => 'link_required',
492
        'message' => t('Titles are required for all links.'),
493
        'error_element' => array('url' => FALSE, 'title' => TRUE),
494
      );
495
    }
496
  }
497
  // Require a link if we have a title.
498
  if ($instance['settings']['url'] !== 'optional' && strlen(isset($item['title']) ? $item['title'] : NULL) > 0 && strlen(trim($item['url'])) == 0) {
499
    $errors[$field['field_name']][$langcode][$delta][] = array(
500
      'error' => 'link_required',
501
      'message' => t('You cannot enter a title without a link url.'),
502
      'error_element' => array('url' => TRUE, 'title' => FALSE),
503
    );
504
  }
505
  // In a totally bizzaro case, where URLs and titles are optional but the field
506
  // is required, ensure there is at least one link.
507
  if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional'
508
    && (strlen(trim($item['url'])) !== 0 || strlen(trim($item['title'])) !== 0)) {
509
    $optional_field_found = TRUE;
510
  }
511
  // Require entire field.
512
  if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] == 1 && !$optional_field_found && isset($instance['id'])) {
513
    $errors[$field['field_name']][$langcode][$delta][] = array(
514
      'error' => 'link_required',
515
      'message' => t('At least one title or URL must be entered.'),
516
      'error_element' => array('url' => FALSE, 'title' => TRUE),
517
    );
518
  }
519
}
520

    
521
/**
522
 * Clean up user-entered values for a link field according to field settings.
523
 *
524
 * @param array $item
525
 *   A single link item, usually containing url, title, and attributes.
526
 * @param int $delta
527
 *   The delta value if this field is one of multiple fields.
528
 * @param array $field
529
 *   The CCK field definition.
530
 * @param object $entity
531
 *   The entity containing this link.
532
 *
533
 * @codingStandardsIgnoreStart
534
 */
535
function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) {
536
  // @codingStandardsIgnoreEnd
537
  // As this function can be called multiple times and the item is changed by
538
  // reference we need to ensure that there's always the original data to
539
  // process otherwise processed data are processed again which might leads to
540
  // unexpected results.
541
  if (isset($item['_link_sanitized'])) {
542
    return;
543
  }
544

    
545
  // Store a flag to check in case of a second call.
546
  $item['_link_sanitized'] = TRUE;
547

    
548
  // Don't try to process empty links.
549
  if (empty($item['url']) && empty($item['title'])) {
550
    return;
551
  }
552
  if (empty($item['html'])) {
553
    $item['html'] = FALSE;
554
  }
555

    
556
  // Replace URL tokens.
557
  $entity_type = $instance['entity_type'];
558
  $entity_info = entity_get_info($entity_type);
559
  $property_id = $entity_info['entity keys']['id'];
560
  $entity_token_type = isset($entity_info['token type']) ? $entity_info['token type'] : (
561
  $entity_type == 'taxonomy_term' || $entity_type == 'taxonomy_vocabulary' ? str_replace('taxonomy_', '', $entity_type) : $entity_type
562
  );
563
  if (isset($instance['settings']['enable_tokens']) && $instance['settings']['enable_tokens']) {
564
    $text_tokens = token_scan($item['url']);
565
    if (!empty($text_tokens)) {
566
      // Load the entity if necessary for entities in views.
567
      if (isset($entity->{$property_id})) {
568
        $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
569
        $entity_loaded = array_pop($entity_loaded);
570
      }
571
      else {
572
        $entity_loaded = $entity;
573
      }
574
      $item['url'] = token_replace($item['url'], array($entity_token_type => $entity_loaded));
575
    }
576
  }
577

    
578
  $type = link_url_type($item['url']);
579
  // If the type of the URL cannot be determined and URL validation is disabled,
580
  // then assume LINK_EXTERNAL for later processing.
581
  if ($type == FALSE && $instance['settings']['validate_url'] === 0) {
582
    $type = LINK_EXTERNAL;
583
  }
584
  elseif ($type == LINK_FRAGMENT || $type == LINK_QUERY) {
585
    // If type is a fragment or query, then use the current URL.
586
    $item['url'] = $_GET['q'] . $item['url'];
587
  }
588
  $url = link_cleanup_url($item['url']);
589
  $url_parts = _link_parse_url($url);
590

    
591
  if (!empty($url_parts['url'])) {
592
    $item = array(
593
      'url' => $url_parts['url'],
594
      'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
595
      'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
596
      'absolute' => !empty($instance['settings']['absolute_url']),
597
      'html' => TRUE,
598
    ) + $item;
599
  }
600

    
601
  // Create a shortened URL for display.
602
  if ($type == LINK_EMAIL) {
603
    $display_url = str_replace('mailto:', '', $url);
604
  }
605
  elseif ($type === LINK_EXTERNAL) {
606
    $display_url = $item['url'];
607
  }
608
  elseif ($type == LINK_TEL) {
609
    $display_url = str_replace('tel:', '', $url);
610
  }
611
  else {
612
    $display_url = url($url_parts['url'],
613
      array(
614
        'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
615
        'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
616
        'absolute' => !empty($instance['settings']['absolute_url']),
617
      )
618
    );
619
  }
620
  if ($instance['settings']['display']['url_cutoff'] && strlen($display_url) > $instance['settings']['display']['url_cutoff']) {
621
    $display_url = substr($display_url, 0, $instance['settings']['display']['url_cutoff']) . "…";
622
  }
623
  $item['display_url'] = $display_url;
624

    
625
  // Use the title defined at the instance level.
626
  if ($instance['settings']['title'] == 'value' && strlen(trim($instance['settings']['title_value']))) {
627
    $title = $instance['settings']['title_value'];
628
    if (function_exists('i18n_string_translate')) {
629
      $i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value";
630
      $title = i18n_string_translate($i18n_string_name, $title);
631
    }
632
  }
633
  // Use the title defined by the user at the widget level.
634
  elseif (isset($item['title']) && drupal_strlen(trim($item['title']))) {
635
    $title = $item['title'];
636
  }
637
  // Use the static title if a user-defined title is optional and a static title
638
  // has been defined.
639
  elseif ($instance['settings']['title'] == 'optional' && drupal_strlen(trim($instance['settings']['title_value']))) {
640
    $title = $instance['settings']['title_value'];
641
  }
642
  else {
643
    $title = '';
644
  }
645

    
646
  // Replace title tokens.
647
  if ($title && $instance['settings']['enable_tokens']) {
648
    $text_tokens = token_scan($title);
649
    if (!empty($text_tokens)) {
650
      // Load the entity if necessary for entities in views.
651
      if (isset($entity->{$property_id})) {
652
        $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
653
        $entity_loaded = array_pop($entity_loaded);
654
      }
655
      else {
656
        $entity_loaded = $entity;
657
      }
658
      $title = token_replace($title, array($entity_token_type => $entity_loaded));
659
    }
660
  }
661
  if ($title && ($instance['settings']['title'] == 'value' || $instance['settings']['enable_tokens'])) {
662
    $title = filter_xss($title, array(
663
      'b',
664
      'br',
665
      'code',
666
      'em',
667
      'i',
668
      'img',
669
      'span',
670
      'strong',
671
      'sub',
672
      'sup',
673
      'tt',
674
      'u',
675
    ));
676
    $item['html'] = TRUE;
677
  }
678
  $item['title'] = empty($title) && $title !== '0' ? $item['display_url'] : $title;
679

    
680
  if (!isset($item['attributes'])) {
681
    $item['attributes'] = array();
682
  }
683

    
684
  // Unserialize attributtes array if it has not been unserialized yet.
685
  if (!is_array($item['attributes'])) {
686
    $item['attributes'] = (array) unserialize($item['attributes']);
687
  }
688

    
689
  // Add default attributes.
690
  if (!is_array($instance['settings']['attributes'])) {
691
    $instance['settings']['attributes'] = _link_default_attributes();
692
  }
693
  else {
694
    $instance['settings']['attributes'] += _link_default_attributes();
695
  }
696

    
697
  // Merge item attributes with attributes defined at the field level.
698
  $item['attributes'] += $instance['settings']['attributes'];
699

    
700
  // If user is not allowed to choose target attribute, use default defined at
701
  // field level.
702
  if ($instance['settings']['attributes']['target'] != LINK_TARGET_USER) {
703
    $item['attributes']['target'] = $instance['settings']['attributes']['target'];
704
  }
705
  elseif ($item['attributes']['target'] == LINK_TARGET_USER) {
706
    $item['attributes']['target'] = LINK_TARGET_DEFAULT;
707
  }
708

    
709
  // Remove the target attribute if the default (no target) is selected.
710
  if (empty($item['attributes']) || (isset($item['attributes']['target']) && $item['attributes']['target'] == LINK_TARGET_DEFAULT)) {
711
    unset($item['attributes']['target']);
712
  }
713

    
714
  // Remove rel attribute for internal or external links if selected.
715
  if (isset($item['attributes']['rel']) && isset($instance['settings']['rel_remove']) && $instance['settings']['rel_remove'] != 'default') {
716
    if (($instance['settings']['rel_remove'] != 'rel_remove_internal' && $type != LINK_INTERNAL) ||
717
      ($instance['settings']['rel_remove'] != 'rel_remove_external' && $type != LINK_EXTERNAL)) {
718
      unset($item['attributes']['rel']);
719
    }
720
  }
721

    
722
  // Handle "title" link attribute.
723
  if (!empty($item['attributes']['title']) && module_exists('token')) {
724
    $text_tokens = token_scan($item['attributes']['title']);
725
    if (!empty($text_tokens)) {
726
      // Load the entity (necessary for entities in views).
727
      if (isset($entity->{$property_id})) {
728
        $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
729
        $entity_loaded = array_pop($entity_loaded);
730
      }
731
      else {
732
        $entity_loaded = $entity;
733
      }
734
      $item['attributes']['title'] = token_replace($item['attributes']['title'], array($entity_token_type => $entity_loaded), array('clear' => TRUE));
735
    }
736
    $item['attributes']['title'] = filter_xss($item['attributes']['title'], array(
737
      'b',
738
      'br',
739
      'code',
740
      'em',
741
      'i',
742
      'img',
743
      'span',
744
      'strong',
745
      'sub',
746
      'sup',
747
      'tt',
748
      'u',
749
    ));
750
  }
751
  // Handle attribute classes.
752
  if (!empty($item['attributes']['class'])) {
753
    $classes = explode(' ', $item['attributes']['class']);
754
    foreach ($classes as &$class) {
755
      $class = drupal_clean_css_identifier($class);
756
    }
757
    $item['attributes']['class'] = implode(' ', $classes);
758
  }
759
  unset($item['attributes']['configurable_class']);
760

    
761
  // Remove title attribute if it's equal to link text.
762
  if (isset($item['attributes']['title']) && $item['attributes']['title'] == $item['title']) {
763
    unset($item['attributes']['title']);
764
  }
765
  unset($item['attributes']['configurable_title']);
766

    
767
  // Remove empty attributes.
768
  $item['attributes'] = array_filter($item['attributes']);
769
}
770

    
771
/**
772
 * Because parse_url doesn't work with relative urls.
773
 *
774
 * @param string $url
775
 *   URL to parse.
776
 *
777
 * @return array
778
 *   Array of url pieces - only 'url', 'query', and 'fragment'.
779
 */
780
function _link_parse_url($url) {
781
  $url_parts = array();
782
  // Separate out the anchor, if any.
783
  if (strpos($url, '#') !== FALSE) {
784
    $url_parts['fragment'] = substr($url, strpos($url, '#') + 1);
785
    $url = substr($url, 0, strpos($url, '#'));
786
  }
787
  // Separate out the query string, if any.
788
  if (strpos($url, '?') !== FALSE) {
789
    $query = substr($url, strpos($url, '?') + 1);
790
    $url_parts['query'] = _link_parse_str($query);
791
    $url = substr($url, 0, strpos($url, '?'));
792
  }
793
  $url_parts['url'] = $url;
794
  return $url_parts;
795
}
796

    
797
/**
798
 * Replaces the PHP parse_str() function.
799
 *
800
 * Because parse_str replaces the following characters in query parameters name
801
 * in order to maintain compatibility with deprecated register_globals
802
 * directive:
803
 *
804
 *   - chr(32) ( ) (space)
805
 *   - chr(46) (.) (dot)
806
 *   - chr(91) ([) (open square bracket)
807
 *   - chr(128) - chr(159) (various)
808
 *
809
 * @param string $query
810
 *   Query to parse.
811
 *
812
 * @return array
813
 *   Array of query parameters.
814
 *
815
 * @see http://php.net/manual/en/language.variables.external.php#81080
816
 */
817
function _link_parse_str($query) {
818
  $query_array = array();
819

    
820
  $pairs = explode('&', $query);
821
  foreach ($pairs as $pair) {
822
    $name_value = explode('=', $pair, 2);
823
    $name = urldecode($name_value[0]);
824
    $value = isset($name_value[1]) ? urldecode($name_value[1]) : NULL;
825
    $query_array[$name] = $value;
826
  }
827

    
828
  return $query_array;
829
}
830

    
831
/**
832
 * Implements hook_theme().
833
 */
834
function link_theme() {
835
  return array(
836
    'link_formatter_link_default' => array(
837
      'variables' => array('element' => NULL, 'field' => NULL),
838
    ),
839
    'link_formatter_link_plain' => array(
840
      'variables' => array('element' => NULL, 'field' => NULL),
841
    ),
842
    'link_formatter_link_host' => array(
843
      'variables' => array('element' => NULL),
844
    ),
845
    'link_formatter_link_absolute' => array(
846
      'variables' => array('element' => NULL, 'field' => NULL),
847
    ),
848
    'link_formatter_link_domain' => array(
849
      'variables' => array(
850
        'element' => NULL,
851
        'display' => NULL,
852
        'field' => NULL,
853
      ),
854
    ),
855
    'link_formatter_link_no_protocol' => array(
856
      'variables' => array('element' => NULL, 'field' => NULL),
857
    ),
858
    'link_formatter_link_title_plain' => array(
859
      'variables' => array('element' => NULL, 'field' => NULL),
860
    ),
861
    'link_formatter_link_url' => array(
862
      'variables' => array('element' => NULL, 'field' => NULL),
863
    ),
864
    'link_formatter_link_short' => array(
865
      'variables' => array('element' => NULL, 'field' => NULL),
866
    ),
867
    'link_formatter_link_label' => array(
868
      'variables' => array('element' => NULL, 'field' => NULL),
869
    ),
870
    'link_formatter_link_separate' => array(
871
      'variables' => array('element' => NULL, 'field' => NULL),
872
    ),
873
    'link_field' => array(
874
      'render element' => 'element',
875
    ),
876
  );
877
}
878

    
879
/**
880
 * Formats a link field widget.
881
 */
882
function theme_link_field($vars) {
883
  drupal_add_css(drupal_get_path('module', 'link') . '/css/link.css');
884
  $element = $vars['element'];
885
  // Prefix single value link fields with the name of the field.
886
  if (empty($element['#field']['multiple'])) {
887
    if (isset($element['url']) && !isset($element['title'])) {
888
      $element['url']['#title_display'] = 'invisible';
889
    }
890
  }
891

    
892
  $output = '';
893
  $output .= '<div class="link-field-subrow clearfix">';
894
  if (isset($element['title'])) {
895
    $output .= '<div class="link-field-title link-field-column">' . drupal_render($element['title']) . '</div>';
896
  }
897
  $output .= '<div class="link-field-url' . (isset($element['title']) ? ' link-field-column' : '') . '">' . drupal_render($element['url']) . '</div>';
898
  $output .= '</div>';
899
  if (!empty($element['attributes']['target'])) {
900
    $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['target']) . '</div>';
901
  }
902
  if (!empty($element['attributes']['title'])) {
903
    $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['title']) . '</div>';
904
  }
905
  if (!empty($element['attributes']['class'])) {
906
    $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['class']) . '</div>';
907
  }
908
  $output .= drupal_render_children($element);
909
  return $output;
910
}
911

    
912
/**
913
 * Implements hook_element_info().
914
 */
915
function link_element_info() {
916
  $elements = array();
917
  $elements['link_field'] = array(
918
    '#input' => TRUE,
919
    '#process' => array('link_field_process'),
920
    '#theme' => 'link_field',
921
    '#theme_wrappers' => array('form_element'),
922
  );
923
  return $elements;
924
}
925

    
926
/**
927
 * Returns the default attributes and their values.
928
 */
929
function _link_default_attributes() {
930
  return array(
931
    'target' => LINK_TARGET_DEFAULT,
932
    'class' => '',
933
    'rel' => '',
934
  );
935
}
936

    
937
/**
938
 * Processes the link type element before displaying the field.
939
 *
940
 * Build the form element. When creating a form using FAPI #process,
941
 * note that $element['#value'] is already set.
942
 *
943
 * The $fields array is in
944
 * $complete_form['#field_info'][$element['#field_name']].
945
 */
946
function link_field_process($element, $form_state, $complete_form) {
947
  $instance = field_widget_instance($element, $form_state);
948
  if (!$instance) {
949
    // The element comes from a custom form, we have to manually create the
950
    // $instance settings.
951
    $instance['settings'] = array(
952
      'title_maxlength' => isset($element['#title_maxlength']) ? $element['#title_maxlength'] : 128,
953
      'title' => isset($element['#title_mode']) ? $element['#title_mode'] : 'optional',
954
      'title_label_use_field_label' => isset($element['#title_label_use_field_label']) ? $element['#title_label_use_field_label'] : FALSE,
955
      'url' => isset($element['#url']) ? $element['#url'] : 'optional',
956
    );
957
    if (isset($element['#attributes'])) {
958
      $instance['settings']['attributes'] = $element['#attributes'];
959
    }
960
  }
961
  $settings = $instance['settings'];
962
  $element['url'] = array(
963
    '#type' => 'textfield',
964
    '#maxlength' => LINK_URL_MAX_LENGTH,
965
    '#title' => t('URL'),
966
    '#required' => ($element['#delta'] == 0 && $settings['url'] !== 'optional') ? $element['#required'] : FALSE,
967
    '#default_value' => isset($element['#value']['url']) ? $element['#value']['url'] : NULL,
968
  );
969
  if (in_array($settings['title'], array('optional', 'required'))) {
970
    // Figure out the label of the title field.
971
    if (!empty($settings['title_label_use_field_label'])) {
972
      // Use the element label as the title field label.
973
      $title_label = $element['#title'];
974
      // Hide the field label because there is no need for the duplicate labels.
975
      $element['#title_display'] = 'invisible';
976
    }
977
    else {
978
      $title_label = t('Title');
979
    }
980

    
981
    // Default value.
982
    $title_maxlength = 128;
983
    if (!empty($settings['title_maxlength'])) {
984
      $title_maxlength = $settings['title_maxlength'];
985
    }
986

    
987
    $element['title'] = array(
988
      '#type' => 'textfield',
989
      '#maxlength' => $title_maxlength,
990
      '#title' => $title_label,
991
      '#description' => t('The link title is limited to @maxlength characters maximum.', array('@maxlength' => $title_maxlength)),
992
      '#required' => ($settings['title'] == 'required' && (($element['#delta'] == 0 && $element['#required']) || !empty($element['#value']['url']))) ? TRUE : FALSE,
993
      '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
994
    );
995
  }
996
  elseif ($settings['title'] == 'select') {
997
    $options = drupal_map_assoc(array_filter(explode("\n", str_replace("\r", "\n", trim($settings['title_allowed_values'])))));
998
    $element['title'] = array(
999
      '#type' => 'select',
1000
      '#title' => t('Title'),
1001
      '#description' => t('Select the a title for this link.'),
1002
      '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
1003
      '#options' => $options,
1004
    );
1005
  }
1006

    
1007
  // Initialize field attributes as an array if it is not an array yet.
1008
  if (!is_array($settings['attributes'])) {
1009
    $settings['attributes'] = array();
1010
  }
1011
  // Add default attributes.
1012
  $settings['attributes'] += _link_default_attributes();
1013
  $attributes = isset($element['#value']['attributes']) ? $element['#value']['attributes'] : $settings['attributes'];
1014
  if (!empty($settings['attributes']['target']) && $settings['attributes']['target'] == LINK_TARGET_USER) {
1015
    $element['attributes']['target'] = array(
1016
      '#type' => 'checkbox',
1017
      '#title' => t('Open URL in a New Window'),
1018
      '#return_value' => LINK_TARGET_NEW_WINDOW,
1019
      '#default_value' => isset($attributes['target']) ? $attributes['target'] : FALSE,
1020
    );
1021
  }
1022
  if (!empty($settings['attributes']['configurable_title']) && $settings['attributes']['configurable_title'] == 1) {
1023
    $element['attributes']['title'] = array(
1024
      '#type' => 'textfield',
1025
      '#title' => t('Link "title" attribute'),
1026
      '#default_value' => isset($attributes['title']) ? $attributes['title'] : '',
1027
      '#field_prefix' => 'title = "',
1028
      '#field_suffix' => '"',
1029
    );
1030
  }
1031
  if (!empty($settings['attributes']['configurable_class']) && $settings['attributes']['configurable_class'] == 1) {
1032
    $element['attributes']['class'] = array(
1033
      '#type' => 'textfield',
1034
      '#title' => t('Custom link class'),
1035
      '#default_value' => isset($attributes['class']) ? $attributes['class'] : '',
1036
      '#field_prefix' => 'class = "',
1037
      '#field_suffix' => '"',
1038
    );
1039
  }
1040

    
1041
  // If the title field is available or there are field accepts multiple values
1042
  // then allow the individual field items display the required asterisk if
1043
  // needed.
1044
  if (isset($element['title']) || isset($element['_weight'])) {
1045
    // To prevent an extra required indicator, disable the required flag on the
1046
    // base element since all the sub-fields are already required if desired.
1047
    $element['#required'] = FALSE;
1048
  }
1049

    
1050
  return $element;
1051
}
1052

    
1053
/**
1054
 * Implements hook_field_formatter_info().
1055
 */
1056
function link_field_formatter_info() {
1057
  return array(
1058
    'link_default' => array(
1059
      'label' => t('Title, as link (default)'),
1060
      'field types' => array('link_field'),
1061
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1062
      'settings' => array(
1063
        'custom_title' => '',
1064
      ),
1065
    ),
1066
    'link_title_plain' => array(
1067
      'label' => t('Title, as plain text'),
1068
      'field types' => array('link_field'),
1069
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1070
    ),
1071
    'link_host' => array(
1072
      'label' => t('Host, as plain text'),
1073
      'field types' => array('link_field'),
1074
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1075
    ),
1076
    'link_url' => array(
1077
      'label' => t('URL, as link'),
1078
      'field types' => array('link_field'),
1079
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1080
    ),
1081
    'link_plain' => array(
1082
      'label' => t('URL, as plain text'),
1083
      'field types' => array('link_field'),
1084
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1085
    ),
1086
    'link_absolute' => array(
1087
      'label' => t('URL, absolute'),
1088
      'field types' => array('link_field'),
1089
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1090
    ),
1091
    'link_domain' => array(
1092
      'label' => t('Domain, as link'),
1093
      'field types' => array('link_field'),
1094
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1095
      'settings' => array(
1096
        'strip_www' => FALSE,
1097
      ),
1098
    ),
1099
    'link_no_protocol' => array(
1100
      'label' => t('URL with the protocol removed'),
1101
      'field types' => array('link_field'),
1102
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1103
    ),
1104
    'link_short' => array(
1105
      'label' => t('Short, as link with title "Link"'),
1106
      'field types' => array('link_field'),
1107
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1108
    ),
1109
    'link_label' => array(
1110
      'label' => t('Label, as link with label as title'),
1111
      'field types' => array('link_field'),
1112
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1113
    ),
1114
    'link_separate' => array(
1115
      'label' => t('Separate title and URL'),
1116
      'field types' => array('link_field'),
1117
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1118
    ),
1119
  );
1120
}
1121

    
1122
/**
1123
 * Implements hook_field_formatter_settings_form().
1124
 */
1125
function link_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
1126
  $display = $instance['display'][$view_mode];
1127
  $settings = $display['settings'];
1128
  $element = array();
1129
  if ($display['type'] == 'link_domain') {
1130
    $element['strip_www'] = array(
1131
      '#title' => t('Strip www. from domain'),
1132
      '#type' => 'checkbox',
1133
      '#default_value' => $settings['strip_www'],
1134
    );
1135
  }
1136
  if ($display['type'] == 'link_default') {
1137
    $element['custom_title'] = array(
1138
      '#title' => t('Override title'),
1139
      '#description' => t('Optionally override the title for the link(s).'),
1140
      '#type' => 'textfield',
1141
      '#default_value' => $settings['custom_title'],
1142
    );
1143
  }
1144
  return $element;
1145
}
1146

    
1147
/**
1148
 * Implements hook_field_formatter_settings_summary().
1149
 */
1150
function link_field_formatter_settings_summary($field, $instance, $view_mode) {
1151

    
1152
  $display = $instance['display'][$view_mode];
1153

    
1154
  if ($display['type'] == 'link_domain') {
1155
    if ($display['settings']['strip_www']) {
1156
      return t('Strip www. from domain');
1157
    }
1158
    else {
1159
      return t('Leave www. in domain');
1160
    }
1161
  }
1162
  if ($display['type'] == 'link_default') {
1163
    if ($display['settings']['custom_title']) {
1164
      return t('Title: %title', array('%title' => $display['settings']['custom_title']));
1165
    }
1166
  }
1167
  return '';
1168
}
1169

    
1170
/**
1171
 * Implements hook_field_formatter_view().
1172
 */
1173
function link_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
1174
  $elements = array();
1175
  foreach ($items as $delta => $item) {
1176
    if (!empty($display['settings']['custom_title'])) {
1177
      $item['title'] = $display['settings']['custom_title'];
1178
    }
1179
    $elements[$delta] = array(
1180
      '#theme' => 'link_formatter_' . $display['type'],
1181
      '#element' => $item,
1182
      '#field' => $instance,
1183
      '#display' => array(
1184
        'settings' => $display['settings'],
1185
      ),
1186
    );
1187
  }
1188
  return $elements;
1189
}
1190

    
1191
/**
1192
 * Formats a link.
1193
 */
1194
function theme_link_formatter_link_default($vars) {
1195
  $link_options = $vars['element'];
1196
  unset($link_options['title']);
1197
  unset($link_options['url']);
1198

    
1199
  if (isset($link_options['attributes']['class'])) {
1200
    $link_options['attributes']['class'] = array($link_options['attributes']['class']);
1201
  }
1202
  // Display a normal link if both title and URL are available.
1203
  if (!empty($vars['element']['title']) && !empty($vars['element']['url'])) {
1204
    return l($vars['element']['title'], rawurldecode($vars['element']['url']), $link_options);
1205
  }
1206
  // If only a title, display the title.
1207
  elseif (!empty($vars['element']['title'])) {
1208
    return !empty($link_options['html']) ? $vars['element']['title'] : check_plain($vars['element']['title']);
1209
  }
1210
  elseif (!empty($vars['element']['url'])) {
1211
    return l($vars['element']['title'], rawurldecode($vars['element']['url']), $link_options);
1212
  }
1213
}
1214

    
1215
/**
1216
 * Formats a link (or its title) as plain text.
1217
 */
1218
function theme_link_formatter_link_plain($vars) {
1219
  $link_options = $vars['element'];
1220
  if (isset($link_options['title'])) {
1221
    unset($link_options['title']);
1222
  }
1223
  else {
1224
    $vars['element']['title'] = '';
1225
  }
1226
  unset($link_options['url']);
1227
  return empty($vars['element']['url']) ? check_plain($vars['element']['title']) : url($vars['element']['url'], $link_options);
1228
}
1229

    
1230
/**
1231
 * Theme function for 'host' text field formatter.
1232
 */
1233
function theme_link_formatter_link_host($vars) {
1234
  $host = @parse_url($vars['element']['url']);
1235
  return isset($host['host']) ? check_plain($host['host']) : '';
1236
}
1237

    
1238
/**
1239
 * Formats a link as an absolute URL.
1240
 */
1241
function theme_link_formatter_link_absolute($vars) {
1242
  $absolute = array('absolute' => TRUE);
1243
  return empty($vars['element']['url']) ? '' : url($vars['element']['url'], $absolute + $vars['element']);
1244
}
1245

    
1246
/**
1247
 * Formats a link using the URL's domain for it's link text.
1248
 */
1249
function theme_link_formatter_link_domain($vars) {
1250
  $link_options = $vars['element'];
1251
  unset($link_options['title']);
1252
  unset($link_options['url']);
1253
  $domain = parse_url($vars['element']['display_url'], PHP_URL_HOST);
1254
  if (!empty($vars['display']['settings']['strip_www'])) {
1255
    $domain = str_replace('www.', '', $domain);
1256
  }
1257
  return $vars['element']['url'] ? l($domain, $vars['element']['url'], $link_options) : '';
1258
}
1259

    
1260
/**
1261
 * Formats a link without the http:// or https://.
1262
 */
1263
function theme_link_formatter_link_no_protocol($vars) {
1264
  $link_options = $vars['element'];
1265
  unset($link_options['title']);
1266
  unset($link_options['url']);
1267
  // We drop any scheme of the url.
1268
  $scheme = parse_url($vars['element']['url']);
1269
  $search = '/' . preg_quote($scheme['scheme'] . '://', '/') . '/';
1270
  $replace = '';
1271
  $display_url = preg_replace($search, $replace, $vars['element']['url'], 1);
1272

    
1273
  return $vars['element']['url'] ? l($display_url, $vars['element']['url'], $link_options) : '';
1274
}
1275

    
1276
/**
1277
 * Formats a link's title as plain text.
1278
 */
1279
function theme_link_formatter_link_title_plain($vars) {
1280
  return empty($vars['element']['title']) ? '' : check_plain(decode_entities($vars['element']['title']));
1281
}
1282

    
1283
/**
1284
 * Formats a link using an alternate display URL for its link text.
1285
 */
1286
function theme_link_formatter_link_url($vars) {
1287
  $link_options = $vars['element'];
1288
  if (isset($link_options['attributes']['class'])) {
1289
    $link_options['attributes']['class'] = array($link_options['attributes']['class']);
1290
  }
1291
  unset($link_options['title']);
1292
  unset($link_options['url']);
1293
  return $vars['element']['url'] ? l($vars['element']['display_url'], $vars['element']['url'], $link_options) : '';
1294
}
1295

    
1296
/**
1297
 * Formats a link using "Link" as the link text.
1298
 */
1299
function theme_link_formatter_link_short($vars) {
1300
  $link_options = $vars['element'];
1301
  unset($link_options['title']);
1302
  unset($link_options['url']);
1303
  return $vars['element']['url'] ? l(t('Link'), $vars['element']['url'], $link_options) : '';
1304
}
1305

    
1306
/**
1307
 * Formats a link using the field's label as link text.
1308
 */
1309
function theme_link_formatter_link_label($vars) {
1310
  $link_options = $vars['element'];
1311
  unset($link_options['title']);
1312
  unset($link_options['url']);
1313
  $label = $vars['field']['label'];
1314
  if (function_exists('i18n_string_translate')) {
1315
    $i18n_string_name = "field:{$vars['field']['field_name']}:{$vars['field']['bundle']}:label";
1316
    $label = i18n_string_translate($i18n_string_name, $label);
1317
  }
1318
  return $vars['element']['url'] ? l($label, $vars['element']['url'], $link_options) : '';
1319
}
1320

    
1321
/**
1322
 * Formats a link as separate title and URL elements.
1323
 */
1324
function theme_link_formatter_link_separate($vars) {
1325
  $class = empty($vars['element']['attributes']['class']) ? '' : ' ' . $vars['element']['attributes']['class'];
1326
  unset($vars['element']['attributes']['class']);
1327
  $link_options = $vars['element'];
1328
  unset($link_options['title']);
1329
  unset($link_options['url']);
1330
  $title = empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']);
1331

    
1332
  // @TODO static html markup looks not very elegant
1333
  // needs smarter output solution and an optional title/url seperator
1334
  $url_parts = _link_parse_url($vars['element']['url']);
1335
  $output = '';
1336
  $output .= '<div class="link-item ' . $class . '">';
1337
  if (!empty($title)) {
1338
    $output .= '<div class="link-title">' . $title . '</div>';
1339
  }
1340
  $output .= '<div class="link-url">' . l($url_parts['url'], $vars['element']['url'], $link_options) . '</div>';
1341
  $output .= '</div>';
1342
  return $output;
1343
}
1344

    
1345
/**
1346
 * Implements hook_token_list().
1347
 *
1348
 * @TODO: hook_token_list no longer exists - this should change to
1349
 *   hook_token_info().
1350
 */
1351
function link_token_list($type = 'all') {
1352
  if ($type === 'field' || $type === 'all') {
1353
    $tokens = array();
1354
    $tokens['link']['url'] = t("Link URL");
1355
    $tokens['link']['title'] = t("Link title");
1356
    $tokens['link']['view'] = t("Formatted html link");
1357
    return $tokens;
1358
  }
1359
}
1360

    
1361
/**
1362
 * Implements hook_token_values().
1363
 *
1364
 * @TODO: hook_token_values no longer exists - this should change to
1365
 *   hook_tokens().
1366
 */
1367
function link_token_values($type, $object = NULL) {
1368
  if ($type === 'field') {
1369
    $item = $object[0];
1370

    
1371
    $tokens['url'] = $item['url'];
1372
    $tokens['title'] = $item['title'];
1373
    $tokens['view'] = isset($item['view']) ? $item['view'] : '';
1374

    
1375
    return $tokens;
1376
  }
1377
}
1378

    
1379
/**
1380
 * Implements hook_views_api().
1381
 */
1382
function link_views_api() {
1383
  return array(
1384
    'api' => 2,
1385
    'path' => drupal_get_path('module', 'link') . '/views',
1386
  );
1387
}
1388

    
1389
/**
1390
 * Forms a valid URL if possible from an entered address.
1391
 *
1392
 * Trims whitespace and automatically adds an http:// to addresses without a
1393
 * protocol specified.
1394
 *
1395
 * @param string $url
1396
 *   The url entered by the user.
1397
 * @param string $protocol
1398
 *   The protocol to be prepended to the url if one is not specified.
1399
 */
1400
function link_cleanup_url($url, $protocol = 'http') {
1401
  $url = trim($url);
1402
  $type = link_url_type($url);
1403

    
1404
  if ($type === LINK_EXTERNAL) {
1405
    // Check if there is no protocol specified.
1406
    $protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i", $url);
1407
    if (empty($protocol_match)) {
1408
      // But should there be? Add an automatic http:// if it starts with a
1409
      // domain name.
1410
      $link_domains = _link_domains();
1411
      $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)(' . $link_domains . '|[a-z]{2}))/i', $url);
1412
      if (!empty($domain_match)) {
1413
        $url = $protocol . "://" . $url;
1414
      }
1415
    }
1416
  }
1417

    
1418
  return $url;
1419
}
1420

    
1421
/**
1422
 * Validates a URL.
1423
 *
1424
 * @param string $text
1425
 *   Url to be validated.
1426
 * @param string $langcode
1427
 *   An optional language code to look up the path in.
1428
 *
1429
 * @return bool
1430
 *   True if a valid link, FALSE otherwise.
1431
 */
1432
function link_validate_url($text, $langcode = NULL) {
1433

    
1434
  $text = _link_clean_relative($text);
1435
  $text = link_cleanup_url($text);
1436
  $type = link_url_type($text);
1437

    
1438
  if ($type && ($type == LINK_INTERNAL || $type == LINK_EXTERNAL)) {
1439
    $flag = valid_url($text, $type == LINK_EXTERNAL);
1440
    if (!$flag) {
1441
      $normal_path = drupal_get_normal_path($text, $langcode);
1442
      $parsed_link = parse_url($normal_path, PHP_URL_PATH);
1443
      if ($normal_path != $parsed_link) {
1444
        $normal_path = $parsed_link;
1445
      }
1446
      $flag = drupal_valid_path($normal_path);
1447
    }
1448
    if (!$flag) {
1449
      $flag = file_exists($normal_path);
1450
    }
1451
    if (!$flag) {
1452
      $uri = file_build_uri($normal_path);
1453
      $flag = file_exists($uri);
1454
    }
1455
  }
1456
  else {
1457
    $flag = (bool) $type;
1458
  }
1459

    
1460
  return $flag;
1461
}
1462

    
1463
/**
1464
 * Cleaner of relatives urls.
1465
 *
1466
 * @param string $url
1467
 *   The url to clean up the relative protocol.
1468
 */
1469
function _link_clean_relative($url) {
1470
  $check = substr($url, 0, 2);
1471
  if (isset($_SERVER['HTTPS']) &&
1472
    ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1) ||
1473
    isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
1474
    $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
1475
    $protocol = 'https://';
1476
  }
1477
  else {
1478
    $protocol = 'http://';
1479
  }
1480

    
1481
  if ($check == '//') {
1482
    $url = str_replace('//', $protocol, $url);
1483
  }
1484

    
1485
  return $url;
1486
}
1487

    
1488
/**
1489
 * Type check a URL.
1490
 *
1491
 * Accepts all URLs following RFC 1738 standard for URL formation and all e-mail
1492
 * addresses following the RFC 2368 standard for mailto address formation.
1493
 *
1494
 * @param string $text
1495
 *   Url to be checked.
1496
 *
1497
 * @return mixed
1498
 *   Returns boolean FALSE if the URL is not valid. On success, returns one of
1499
 *   the LINK_(linktype) constants.
1500
 */
1501
function link_url_type($text) {
1502
  // @TODO Complete letters.
1503
  // @codingStandardsIgnoreStart
1504
  $link_ichars_domain = (string) html_entity_decode(implode("", array(
1505
    "&#x00BF;", // ¿
1506
    "&#x00C0;", // À
1507
    "&#x00C1;", // Á
1508
    "&#x00C2;", // Â
1509
    "&#x00C3;", // Ã
1510
    "&#x00C4;", // Ä
1511
    "&#x00C5;", // Å
1512
    "&#x00C6;", // Æ
1513
    "&#x00C7;", // Ç
1514
    "&#x00C8;", // È
1515
    "&#x00C9;", // É
1516
    "&#x00CA;", // Ê
1517
    "&#x00CB;", // Ë
1518
    "&#x00CC;", // Ì
1519
    "&#x00CD;", // Í
1520
    "&#x00CE;", // Î
1521
    "&#x00CF;", // Ï
1522
    "&#x00D0;", // Ð
1523
    "&#x00D1;", // Ñ
1524
    "&#x00D2;", // Ò
1525
    "&#x00D3;", // Ó
1526
    "&#x00D4;", // Ô
1527
    "&#x00D5;", // Õ
1528
    "&#x00D6;", // Ö
1529
    // ×
1530
    "&#x00D8;", // Ø
1531
    "&#x00D9;", // Ù
1532
    "&#x00DA;", // Ú
1533
    "&#x00DB;", // Û
1534
    "&#x00DC;", // Ü
1535
    "&#x00DD;", // Ý
1536
    "&#x00DE;", // Þ
1537
    // ß (see LINK_ICHARS)
1538
    "&#x00E0;", // à
1539
    "&#x00E1;", // á
1540
    "&#x00E2;", // â
1541
    "&#x00E3;", // ã
1542
    "&#x00E4;", // ä
1543
    "&#x00E5;", // å
1544
    "&#x00E6;", // æ
1545
    "&#x00E7;", // ç
1546
    "&#x00E8;", // è
1547
    "&#x00E9;", // é
1548
    "&#x00EA;", // ê
1549
    "&#x00EB;", // ë
1550
    "&#x00EC;", // ì
1551
    "&#x00ED;", // í
1552
    "&#x00EE;", // î
1553
    "&#x00EF;", // ï
1554
    "&#x00F0;", // ð
1555
    "&#x00F1;", // ñ
1556
    "&#x00F2;", // ò
1557
    "&#x00F3;", // ó
1558
    "&#x00F4;", // ô
1559
    "&#x00F5;", // õ
1560
    "&#x00F6;", // ö
1561
    // ÷
1562
    "&#x00F8;", // ø
1563
    "&#x00F9;", // ù
1564
    "&#x00FA;", // ú
1565
    "&#x00FB;", // û
1566
    "&#x00FC;", // ü
1567
    "&#x00FD;", // ý
1568
    "&#x00FE;", // þ
1569
    "&#x00FF;", // ÿ
1570
    "&#x0152;", // Œ
1571
    "&#x0153;", // œ
1572
    "&#x0178;", // Ÿ
1573
  )), ENT_QUOTES, 'UTF-8');
1574
  // @codingStandardsIgnoreEnd
1575

    
1576
  $link_ichars = $link_ichars_domain . (string) html_entity_decode(implode("", array(
1577
      // ß.
1578
    "&#x00DF;",
1579
  )), ENT_QUOTES, 'UTF-8');
1580
  $allowed_protocols = variable_get('filter_allowed_protocols', array(
1581
    'http',
1582
    'https',
1583
    'ftp',
1584
    'file',
1585
    'news',
1586
    'nntp',
1587
    'telnet',
1588
    'mailto',
1589
    'irc',
1590
    'ssh',
1591
    'sftp',
1592
    'webcal',
1593
    'tel',
1594
  ));
1595
  $link_domains = _link_domains();
1596

    
1597
  // Starting a parenthesis group with (?: means that it is grouped, but is not
1598
  // captured.
1599
  $protocol = '((?:' . implode("|", $allowed_protocols) . '):\/\/)';
1600
  $authentication = "(?:(?:(?:[\w\.\-\+!$&'\(\)*\+,;=" . $link_ichars . "]|%[0-9a-f]{2})+(?::(?:[\w" . $link_ichars . "\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})*)?)?@)";
1601
  $domain = '(?:(?:[a-z0-9' . $link_ichars_domain . ']([a-z0-9' . $link_ichars_domain . '\-_\[\]])*)(\.(([a-z0-9' . $link_ichars_domain . '\-_\[\]])+\.)*(' . $link_domains . '|[a-z]{2}))?)';
1602
  $ipv4 = '(?:[0-9]{1,3}(\.[0-9]{1,3}){3})';
1603
  $ipv6 = '(?:[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7})';
1604
  $port = '(?::([0-9]{1,5}))';
1605
  // Pattern specific to external links.
1606
  $external_pattern = '/^' . $protocol . '?' . $authentication . '?(' . $domain . '|' . $ipv4 . '|' . $ipv6 . ' |localhost)' . $port . '?';
1607

    
1608
  // Pattern specific to internal links.
1609
  $internal_pattern = "/^(?:[a-z0-9" . $link_ichars . "_\-+\[\] ]+)";
1610
  $internal_pattern_file = "/^(?:[a-z0-9" . $link_ichars . "_\-+\[\]\. \/\(\)][a-z0-9" . $link_ichars . "_\-+\[\]\. \(\)][a-z0-9" . $link_ichars . "_\-+\[\]\. \/\(\)]+)$/i";
1611

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

    
1617
  // The rest of the path for a standard URL.
1618
  // @codingStandardsIgnoreLine
1619
  $end = $directories . '?' . $query . '?' . $anchor . '?' . '$/i';
1620

    
1621
  $message_id = '[^@].*@' . $domain;
1622
  $newsgroup_name = '(?:[0-9a-z+-]*\.)*[0-9a-z+-]*';
1623
  $news_pattern = '/^news:(' . $newsgroup_name . '|' . $message_id . ')$/i';
1624

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

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

    
1631
  if (strpos($text, '<front>') === 0) {
1632
    return LINK_FRONT;
1633
  }
1634
  if (in_array('mailto', $allowed_protocols) && preg_match($email_pattern, $text)) {
1635
    return LINK_EMAIL;
1636
  }
1637
  if (strpos($text, '#') === 0) {
1638
    return LINK_FRAGMENT;
1639
  }
1640
  if (strpos($text, '?') === 0) {
1641
    return LINK_QUERY;
1642
  }
1643
  if (in_array('tel', $allowed_protocols) && strpos($text, 'tel:') === 0) {
1644
    if (preg_match($tel_pattern, $text)) {
1645
      // Based on our tel pattern this is a 'valid' phone number so return tel
1646
      // type.
1647
      return LINK_TEL;
1648
    }
1649
    else {
1650
      // Based on our tel pattern this is using the tel protocol, but is not a
1651
      // 'valid' phone number. If we don't return false here $text will match
1652
      // LINK_EXTERNAL which is incorrect.
1653
      return FALSE;
1654
    }
1655
  }
1656
  if (in_array('news', $allowed_protocols) && preg_match($news_pattern, $text)) {
1657
    return LINK_NEWS;
1658
  }
1659
  if (in_array('file', $allowed_protocols) && preg_match($file_pattern, $text, $as)) {
1660
    return LINK_FILE;
1661
  }
1662
  if (preg_match($internal_pattern . $end, $text)) {
1663
    return LINK_INTERNAL;
1664
  }
1665
  if (drupal_valid_path($text) && url_is_external($text) == FALSE) {
1666
    return LINK_INTERNAL;
1667
  }
1668
  if (preg_match($external_pattern . $end, $text)) {
1669
    return LINK_EXTERNAL;
1670
  }
1671
  if (preg_match($internal_pattern_file, $text)) {
1672
    return LINK_INTERNAL;
1673
  }
1674

    
1675
  return FALSE;
1676
}
1677

    
1678
/**
1679
 * Returns the list of allowed domains.
1680
 *
1681
 * If the variable link_allowed_domains is set, restrict allowed domains to the
1682
 * strings in that array. If the variable link_allowed_domains is not set, allow
1683
 * all domains between 2 and 63 characters in length.
1684
 * See https://tools.ietf.org/html/rfc1034.
1685
 */
1686
function _link_domains() {
1687
  $link_allowed_domains = variable_get('link_allowed_domains', array());
1688
  return empty($link_allowed_domains) ? '[a-z][a-z0-9-]{1,62}' : implode('|', $link_allowed_domains);
1689
}
1690

    
1691
/**
1692
 * Implements hook_migrate_field_alter().
1693
 */
1694
function link_content_migrate_field_alter(&$field_value, $instance_value) {
1695
  if ($field_value['type'] == 'link') {
1696
    // Adjust the field type.
1697
    $field_value['type'] = 'link_field';
1698
    // Remove settings that are now on the instance.
1699
    foreach (array(
1700
      'attributes',
1701
      'display',
1702
      'url',
1703
      'title',
1704
      'title_value',
1705
      'enable_tokens',
1706
      'validate_url',
1707
    ) as $setting) {
1708
      unset($field_value['settings'][$setting]);
1709
    }
1710
  }
1711
}
1712

    
1713
/**
1714
 * Implements hook_migrate_instance_alter().
1715
 *
1716
 * Widget type also changed to link_field.
1717
 */
1718
function link_content_migrate_instance_alter(&$instance_value, $field_value) {
1719
  if ($field_value['type'] == 'link') {
1720
    // Grab settings that were previously on the field.
1721
    foreach (array(
1722
      'attributes',
1723
      'display',
1724
      'url',
1725
      'title',
1726
      'title_value',
1727
      'enable_tokens',
1728
      'validate_url',
1729
    ) as $setting) {
1730
      if (isset($field_value['settings'][$setting])) {
1731
        $instance_value['settings'][$setting] = $field_value['settings'][$setting];
1732
      }
1733
    }
1734
    // Adjust widget type.
1735
    if ($instance_value['widget']['type'] == 'link') {
1736
      $instance_value['widget']['type'] = 'link_field';
1737
    }
1738
    // Adjust formatter types.
1739
    foreach ($instance_value['display'] as $context => $settings) {
1740
      if (in_array($settings['type'], array(
1741
        'default',
1742
        'title_plain',
1743
        'url',
1744
        'plain',
1745
        'short',
1746
        'label',
1747
        'separate',
1748
      ))) {
1749
        $instance_value['display'][$context]['type'] = 'link_' . $settings['type'];
1750
      }
1751
    }
1752
  }
1753
}
1754

    
1755
/**
1756
 * Implements hook_field_settings_form().
1757
 */
1758
function link_field_settings_form() {
1759
  return array();
1760
}
1761

    
1762
/**
1763
 * Additional callback to adapt the property info of link fields.
1764
 *
1765
 * @see entity_metadata_field_entity_property_info()
1766
 */
1767
function link_field_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
1768
  $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
1769
  // Define a data structure so it's possible to deal with both the link title
1770
  // and URL.
1771
  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
1772
  $property['setter callback'] = 'entity_metadata_field_verbatim_set';
1773

    
1774
  // Auto-create the field item as soon as a property is set.
1775
  $property['auto creation'] = 'link_field_item_create';
1776

    
1777
  $property['property info'] = link_field_item_property_info();
1778
  $property['property info']['url']['required'] = $instance['required'] && !$instance['settings']['url'];
1779
  $property['property info']['title']['required'] = $instance['required'] && ($instance['settings']['title'] == 'required');
1780
  if ($instance['settings']['title'] == 'none') {
1781
    unset($property['property info']['title']);
1782
  }
1783
  unset($property['query callback']);
1784
}
1785

    
1786
/**
1787
 * Callback for creating a new, empty link field item.
1788
 *
1789
 * @see link_field_property_info_callback()
1790
 */
1791
function link_field_item_create() {
1792
  return array('title' => NULL, 'url' => NULL, 'display_url' => NULL);
1793
}
1794

    
1795
/**
1796
 * Defines info for the properties of the link-field item data structure.
1797
 */
1798
function link_field_item_property_info() {
1799
  $properties['title'] = array(
1800
    'type' => 'text',
1801
    'label' => t('The title of the link.'),
1802
    'setter callback' => 'entity_property_verbatim_set',
1803
  );
1804
  $properties['url'] = array(
1805
    'type' => 'text',
1806
    'label' => t('The URL of the link.'),
1807
    'setter callback' => 'entity_property_verbatim_set',
1808
  );
1809
  $properties['attributes'] = array(
1810
    'type' => 'struct',
1811
    'label' => t('The attributes of the link.'),
1812
    'setter callback' => 'entity_property_verbatim_set',
1813
    'getter callback' => 'link_attribute_property_get',
1814
  );
1815
  $properties['display_url'] = array(
1816
    'type' => 'uri',
1817
    'label' => t('The full URL of the link.'),
1818
    'setter callback' => 'entity_property_verbatim_set',
1819
  );
1820
  return $properties;
1821
}
1822

    
1823
/**
1824
 * Entity property info getter callback for link attributes.
1825
 */
1826
function link_attribute_property_get($data, array $options, $name, $type, $info) {
1827
  return isset($data[$name]) ? array_filter($data[$name]) : array();
1828
}
1829

    
1830
/**
1831
 * Implements hook_field_update_instance().
1832
 */
1833
function link_field_update_instance($instance, $prior_instance) {
1834
  if (function_exists('i18n_string_update') && isset($instance['widget']) && $instance['widget']['type'] == 'link_field' && $prior_instance['settings']['title_value'] != $instance['settings']['title_value']) {
1835
    $i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value";
1836
    i18n_string_update($i18n_string_name, $instance['settings']['title_value']);
1837
  }
1838
}
1839

    
1840
/**
1841
 * Implements hook_i18n_string_list_TEXTGROUP_alter().
1842
 */
1843
function link_i18n_string_list_field_alter(&$strings, $type = NULL, $object = NULL) {
1844
  if ($type != 'field_instance' || !is_array($object) || !isset($object['widget']['type'])) {
1845
    return;
1846
  }
1847
  if ($object['widget']['type'] == 'link_field' && isset($object['settings']['title_value'])) {
1848
    $strings['field'][$object['field_name']][$object['bundle']]['title_value']['string'] = $object['settings']['title_value'];
1849
  }
1850
}