link.module 45.9 KB
Newer Older
Nathan Haug's avatar
Nathan Haug committed
1
2
3
4
5
6
7
<?php

/**
 * @file
 * Defines simple link field types.
 */

8
9
10
11
define('LINK_EXTERNAL', 'external');
define('LINK_INTERNAL', 'internal');
define('LINK_FRONT', 'front');
define('LINK_EMAIL', 'email');
12
define('LINK_NEWS', 'news');
13
14
15
16
17
18
define('LINK_DOMAINS', 'aero|arpa|asia|biz|com|cat|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|mobi|local');

define('LINK_TARGET_DEFAULT', 'default');
define('LINK_TARGET_NEW_WINDOW', '_blank');
define('LINK_TARGET_TOP', '_top');
define('LINK_TARGET_USER', 'user');
19

Nathan Haug's avatar
Nathan Haug committed
20
/**
21
 * Maximum URLs length - needs to match value in link.install.
22
23
24
25
26
 */
define('LINK_URL_MAX_LENGTH', 2048);

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

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

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

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

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

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

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

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

119
120
121
122
123
124
125
126
127
  if (module_exists('token')) {
    // Add token module replacements fields
    $form['tokens'] = array(
      '#type' => 'fieldset',
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#title' => t('Placeholder tokens'),
      '#description' => t("The following placeholder tokens can be used in both paths and titles. When used in a path or title, they will be replaced with the appropriate values."),
    );
128
    $entity_info = entity_get_info($instance['entity_type']);
129
130
    $token_type = array(
      'theme' => 'token_tree',
131
      'token_types' => array($entity_info['token type']),
132
133
134
135
136
137
138
139
140
141
142
143
144
      'global_types' => TRUE,
      'click_insert' => TRUE,
      'recursion_limit' => 2,
    );
    $form['tokens']['help'] = array(
      '#type' => 'markup',
      '#markup' => theme('token_tree', $token_type),
    );

    $form['enable_tokens'] = array(
      '#type' => 'checkbox',
      '#title' => t('Allow user-entered tokens'),
      '#default_value' => isset($instance['settings']['enable_tokens']) ? $instance['settings']['enable_tokens'] : 1,
145
      '#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.'),
146
147
    );
  }
148

149
150
151
152
153
154
  $form['display'] = array(
    '#tree' => TRUE,
  );
  $form['display']['url_cutoff'] = array(
    '#type' => 'textfield',
    '#title' => t('URL Display Cutoff'),
155
    '#default_value' => isset($instance['settings']['display']['url_cutoff']) ? $instance['settings']['display']['url_cutoff'] : '80',
156
157
158
159
    '#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.'),
    '#maxlength' => 3,
   '#size' => 3,
  );
160

161
162
163
164
165
166
167
168
169
170
171
172
  $target_options = array(
    LINK_TARGET_DEFAULT => t('Default (no target attribute)'),
    LINK_TARGET_TOP => t('Open link in window root'),
    LINK_TARGET_NEW_WINDOW => t('Open link in new window'),
    LINK_TARGET_USER => t('Allow the user to choose'),
  );
  $form['attributes'] = array(
    '#tree' => TRUE,
  );
  $form['attributes']['target'] = array(
    '#type' => 'radios',
    '#title' => t('Link Target'),
173
    '#default_value' => empty($instance['settings']['attributes']['target']) ? LINK_TARGET_DEFAULT : $instance['settings']['attributes']['target'],
174
175
176
177
178
179
    '#options' => $target_options,
  );
  $form['attributes']['rel'] = array(
    '#type' => 'textfield',
    '#title' => t('Rel Attribute'),
    '#description' => t('When output, this link will have this rel attribute. The most common usage is <a href="http://en.wikipedia.org/wiki/Nofollow">rel=&quot;nofollow&quot;</a> which prevents some search engines from spidering entered links.'),
180
    '#default_value' => empty($instance['settings']['attributes']['rel']) ? '' : $instance['settings']['attributes']['rel'],
181
182
183
184
    '#field_prefix' => 'rel = "',
    '#field_suffix' => '"',
    '#size' => 20,
  );
185
186
187
188
189
190
191
192
193
194
195
196
  $rel_remove_options = array(
    'default' => t('Keep rel as set up above (untouched/default)'),
    'rel_remove_external' => t('Remove rel if given link is external'),
    'rel_remove_internal' => t('Remove rel if given link is internal'),
  );
  $form['rel_remove'] = array(
    '#type' => 'radios',
    '#title' => t('Remove rel attribute automaticly'),
    '#default_value' => !isset($instance['settings']['rel_remove']) ? 'default' : $instance['settings']['rel_remove'],
    '#description' => t('Turn on/off if rel attribute should be removed automaticly, if user given link is internal/external'),
    '#options' => $rel_remove_options,
  );
197
198
199
  $form['attributes']['class'] = array(
    '#type' => 'textfield',
    '#title' => t('Additional CSS Class'),
200
    '#description' => t('When output, this link will have this class attribute. Multiple classes should be separated by spaces.'),
201
    '#default_value' => empty($instance['settings']['attributes']['class']) ? '' : $instance['settings']['attributes']['class'],
202
  );
203
204
205
206
207
208
209
210
211
212
213
214
215
216
  $form['attributes']['configurable_title'] = array(
    '#title' => t("Allow the user to enter a link 'title' attribute"),
    '#type' => 'checkbox',
    '#default_value' => empty($instance['settings']['attributes']['configurable_title']) ? '' : $instance['settings']['attributes']['configurable_title'],
  );
  $form['attributes']['title'] = array(
    '#title' => t("Default link 'title' Attribute"),
    '#type' => 'textfield',
    '#description' => t('When output, links will use this "title" attribute if the user does not provide one and when different from the link text. Read <a href="http://www.w3.org/TR/WCAG10-HTML-TECHS/#links">WCAG 1.0 Guidelines</a> for links comformances. Tokens values will be evaluated.'),
    '#default_value' => empty($instance['settings']['attributes']['title']) ? '' : $instance['settings']['attributes']['title'],
    '#field_prefix' => 'title = "',
    '#field_suffix' => '"',
    '#size' => 20,
  );
217
218
219
220
221
222
223
  return $form;
}

/**
 * Validate the field settings form.
 */
function link_field_settings_form_validate($element, &$form_state, $complete_form) {
224
225
  if ($form_state['values']['instance']['settings']['title'] === 'value'
      && empty($form_state['values']['instance']['settings']['title_value'])) {
226
227
    form_set_error('title_value', t('A default title must be provided if the title is a static value.'));
  }
228
229
230
231
232
233
234
235
236
237
238
239
  if (!empty($form_state['values']['instance']['settings']['display']['url_cutoff'])  // patch #1307788 from nmc
      && !is_numeric($form_state['values']['instance']['settings']['display']['url_cutoff'])) {
    form_set_error('display', t('URL Display Cutoff value must be numeric.'));
  }
  if (empty($form_state['values']['instance']['settings']['title_maxlength'])) {     // patch #1307788 from nmc
    form_set_value($element['title_maxlength'], '128', $form_state);
  } elseif (!is_numeric($form_state['values']['instance']['settings']['title_maxlength'])) {
    form_set_error('title_maxlength', t('The max length of the link title must be numeric.'));
  } elseif ($form_state['values']['instance']['settings']['title_maxlength'] > 255) {
    form_set_error('title_maxlength', t('The max length of the link title cannot be greater than 255 characters.'));
  }

240
241
}

242
/**
243
 * Implement hook_field_is_empty().
244
 */
245
function link_field_is_empty($item, $field) {
246
247
248
  return empty($item['title']) && empty($item['url']);
}

249
250
251
/**
 * Implements hook_field_load().
 */
252
253
254
function link_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
  foreach ($entities as $id => $entity) {
    foreach ($items[$id] as $delta => $item) {
255
      $items[$id][$delta]['attributes'] = _link_load($field, $item, $instances[$id]);
256
    }
257
258
259
  }
}

Nathan Haug's avatar
Nathan Haug committed
260
/**
261
 * Implements hook_field_validate().
Nathan Haug's avatar
Nathan Haug committed
262
 */
263
264
function link_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  $optional_field_found = FALSE;
265
266
  if ($instance['settings']['validate_url'] !== 0 || is_null($instance['settings']['validate_url']) || !isset($instance['settings']['validate_url'])) {
    foreach ($items as $delta => $value) {
267
      _link_validate($items[$delta], $delta, $field, $entity, $instance, $langcode, $optional_field_found);
268
269
270
    }
  }

271
  if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] && !$optional_field_found) {
272
    form_set_error($field['field_name'] . '][' . $langcode . '][0][title', t('At least one title or URL must be entered.'));
273
274
  }
}
275

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

/**
 * Implements hook_field_update().
 */
function link_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
289
290
  foreach ($items as $delta => $value) {
    _link_process($items[$delta], $delta, $field, $entity);
Nathan Haug's avatar
Nathan Haug committed
291
292
293
  }
}

294
295
296
297
298
299
300
301
302
303
304
/**
 * Implements hook_field_prepare_view().
 */
function link_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
  foreach ($items as $entity_id => $entity_items) {
    foreach ($entity_items as $delta => $value) {
      _link_sanitize($items[$entity_id][$delta], $delta, $field, $instances[$entity_id], $entities[$entity_id]);
    }
  }
}

Nathan Haug's avatar
Nathan Haug committed
305
/**
306
 * Implements hook_field_widget_info().
Nathan Haug's avatar
Nathan Haug committed
307
 */
308
function link_field_widget_info() {
Nathan Haug's avatar
Nathan Haug committed
309
  return array(
310
    'link_field' => array(
311
      'label' => 'Link',
312
313
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
Nathan Haug's avatar
Nathan Haug committed
314
315
316
317
318
    ),
  );
}

/**
319
 * Implements hook_field_widget_form().
Nathan Haug's avatar
Nathan Haug committed
320
 */
321
322
323
function link_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $element += array(
    '#type' => $instance['widget']['type'],
324
    '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
325
  );
326
  return $element;
327
328
}

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

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

  // if no attributes are set then make sure $item['attributes'] is an empty array - this lets $field['attributes'] override it.
  if (empty($item['attributes'])) {
359
    $item['attributes'] = array();
360
361
  }

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

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

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

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

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

  $type = link_validate_url($item['url']);
449
450
451
452
453
  // If we can't determine the type of url, and we've been told not to validate it,
  // then we assume it's a LINK_EXTERNAL type for later processing. #357604
  if ($type == FALSE && $instance['settings']['validate_url'] === 0) {
    $type = LINK_EXTERNAL;
  }
454
  $url = link_cleanup_url($item['url']);
455
  $url_parts = _link_parse_url($url);
456

457
458
459
460
461
462
463
464
465
466
467
468
469
  // Get rid of '<front>' for proper paths.
  if ($type == LINK_FRONT) {
    $url_parts['url'] = '';
  }

  $item['url'] = url(check_plain($url_parts['url']),
    array(
      'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
      'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
      'absolute' => TRUE,
      'html' => TRUE,
    )
  );
470
471

  // Create a shortened URL for display.
472
473
474
475
476
477
478
479
480
481
482
483
  if ($type == LINK_EMAIL) {
    $display_url = str_replace('mailto:', '', $url);
  }
  else {
    $display_url = url(check_plain($url_parts['url']),
      array(
        'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
        'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
        'absolute' => TRUE,
      )
    );
  }
484
485
  if ($instance['settings']['display']['url_cutoff'] && strlen($display_url) > $instance['settings']['display']['url_cutoff']) {
    $display_url = substr($display_url, 0, $instance['settings']['display']['url_cutoff']) ."...";
486
487
488
  }
  $item['display_url'] = $display_url;

489
490
491
  // Use the title defined at the instance level.
  if ($instance['settings']['title'] == 'value' && strlen(trim($instance['settings']['title_value']))) {
    $title = $instance['settings']['title_value'];
492
493
  }
  // Use the title defined by the user at the widget level.
494
  else if (isset($item['title'])) {
495
496
    $title = $item['title'];
  }
497
498
499
  else {
    $title = '';
  }
500

501
  // Replace tokens.
502
  if ($title && ($instance['settings']['title'] == 'value' || $instance['settings']['enable_tokens'])) {
503
    // Load the entity if necessary for entities in views.
504
505
    $entity_loaded = isset($entity->{$property_id}) ? array_pop(entity_load($entity_type, array($entity->{$property_id}))) : $entity;
    $title = filter_xss(token_replace($title, array($entity_token_type => $entity_loaded)),
506
                        array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u'));
507
    $item['html'] = TRUE;
508
  }
509
  $item['title'] = empty($title) ? $item['display_url'] : $title;
510

511
512
513
514
515
516
517
518
519
520
  if (!isset($item['attributes'])) {
    $item['attributes'] = array();
  }

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

  // Add default attributes.
521
522
523
524
525
526
  if (!is_array($instance['settings']['attributes'])){
    $instance['settings']['attributes'] = _link_default_attributes();
  }
  else {
    $instance['settings']['attributes'] += _link_default_attributes();
  }
527
528

  // Merge item attributes with attributes defined at the field level.
529
  $item['attributes'] += $instance['settings']['attributes'];
530
531
532

  // If user is not allowed to choose target attribute, use default defined at
  // field level.
533
534
  if ($instance['settings']['attributes']['target'] != LINK_TARGET_USER) {
    $item['attributes']['target'] = $instance['settings']['attributes']['target'];
535
  }
536
537
538
  elseif ($item['attributes']['target'] == LINK_TARGET_USER) {
    $item['attributes']['target'] = LINK_TARGET_DEFAULT;
  }
539
540
541
542
543
544

  // Remove the target attribute if the default (no target) is selected.
  if (empty($item['attributes']) || $item['attributes']['target'] == LINK_TARGET_DEFAULT) {
    unset($item['attributes']['target']);
  }

545
546
547
548
549
550
  // Remove rel attribute for internal or external links if selected.
  if (isset($item['attributes']['rel']) && isset($instance['settings']['rel_remove']) && $instance['settings']['rel_remove'] != 'default') {
    if (($instance['settings']['rel_remove'] != 'rel_remove_internal' && $type != LINK_INTERNAL) ||
            ($instance['settings']['rel_remove'] != 'rel_remove_external' && $type != LINK_EXTERNAL)) {
    unset($item['attributes']['rel']);
    }
551
  }
552

553
554
  // Handle "title" link attribute.
  if (!empty($item['attributes']['title']) && module_exists('token')) {
555
    // Load the entity (necessary for entities in views).
556
557
    $entity_loaded = isset($entity->{$property_id}) ? array_pop(entity_load($entity_type, array($entity->{$property_id}))) : $entity;
    $item['attributes']['title'] = filter_xss(token_replace($title, array($entity_token_type => $entity_loaded)),
558
559
560
                        array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u'));
  }
  // Remove title attribute if it's equal to link text.
561
  if (isset($item['attributes']['title']) && $item['attributes']['title'] == $item['title']) {
562
563
564
565
    unset($item['attributes']['title']);
  }
  unset($item['attributes']['configurable_title']);

566
567
  // Remove empty attributes.
  $item['attributes'] = array_filter($item['attributes']);
568

569
  // Sets title to trimmed url if one exists
570
  // @TODO: Do we need this?  It seems not.
571
  /*if(!empty($item['display_url']) && empty($item['title'])) {
572
573
574
575
    $item['title'] = $item['display_url'];
  }
  elseif(!isset($item['title'])) {
    $item['title'] = $item['url'];
576
  }*/
577

578
579
}

580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
/**
 * Because parse_url doesn't work with relative urls.
 *
 * @param string $url
 *   URL to parse.
 *
 * @return Array
 *   Array of url pieces - only 'url', 'query', and 'fragment'.
 */
function _link_parse_url($url) {
  $url_parts = array();
  // Separate out the anchor if any.
  if (strpos($url, '#') !== FALSE) {
    $url_parts['fragment'] = substr($url, strpos($url, '#') + 1);
    $url = substr($url, 0, strpos($url, '#'));
  }
  // Separate out the query string if any.
  if (strpos($url, '?') !== FALSE) {
    $query = substr($url, strpos($url, '?') + 1);
    parse_str($query, $query_array);
    // See http://drupal.org/node/1710578
    foreach ($query_array as $key=> &$value) {
      if ($value === '' && FALSE === strpos($query, $key . '=')) {
        $value = NULL;
      }
    }
    $url_parts['query'] = $query_array;
    $url = substr($url, 0, strpos($url, '?'));
  }
  $url_parts['url'] = $url;
  return $url_parts;
}

613
/**
614
 * Implements hook_theme().
615
 */
616
617
function link_theme() {
  return array(
618
    'link_formatter_link_default' => array(
619
      'variables' => array('element' => NULL),
620
    ),
621
    'link_formatter_link_plain' => array(
622
      'variables' => array('element' => NULL),
623
    ),
624
625
626
    'link_formatter_link_absolute' => array(
      'variables' => array('element' => NULL),
    ),
627
628
629
    'link_formatter_link_domain' => array(
      'variables' => array('element' => NULL),
    ),
630
631
632
    'link_formatter_link_title_plain' => array(
      'variables' => array('element' => NULL),
    ),
633
    'link_formatter_link_url' => array(
634
      'variables' => array('element' => NULL),
635
    ),
636
    'link_formatter_link_short' => array(
637
      'variables' => array('element' => NULL),
638
    ),
639
    'link_formatter_link_label' => array(
640
      'variables' => array('element' => NULL),
641
    ),
642
    'link_formatter_link_separate' => array(
643
      'variables' => array('element' => NULL),
644
    ),
645
    'link_field' => array(
646
      'render element' => 'element',
647
648
649
650
651
652
653
    ),
  );
}

/**
 * FAPI theme for an individual text elements.
 */
654
function theme_link_field($vars) {
655
656
  drupal_add_css(drupal_get_path('module', 'link') .'/link.css');

657
  $element = $vars['element'];
658
659
  // Prefix single value link fields with the name of the field.
  if (empty($element['#field']['multiple'])) {
660
661
    if (isset($element['url']) && !isset($element['title'])) {
      unset($element['url']['#title']);
662
    }
663
664
  }

665
  $output = '';
666
  $output .= '<div class="link-field-subrow clearfix">';
667
  if (isset($element['title'])) {
668
    $output .= '<div class="link-field-title link-field-column">'. drupal_render($element['title']) .'</div>';
669
  }
670
  $output .= '<div class="link-field-url'. (isset($element['title']) ? ' link-field-column' : '') .'">'. drupal_render($element['url']) .'</div>';
671
  $output .= '</div>';
672
  if (!empty($element['attributes']['target'])) {
673
    $output .= '<div class="link-attributes">'. drupal_render($element['attributes']['target']) .'</div>';
674
  }
675
676
677
  if (!empty($element['attributes']['title'])) {
    $output .= '<div class="link-attributes">'. drupal_render($element['attributes']['title']) .'</div>';
  }
678
  return $output;
679
680
}

681
/**
682
 * Implements hook_element_info().
683
 */
684
function link_element_info() {
685
  $elements = array();
686
  $elements['link_field'] =  array(
687
    '#input' => TRUE,
688
    '#process' => array('link_field_process'),
689
690
    '#theme' => 'link_field',
    '#theme_wrappers' => array('form_element'),
691
692
693
694
  );
  return $elements;
}

695
696
697
698
699
700
701
702
function _link_default_attributes() {
  return array(
    'target' => LINK_TARGET_DEFAULT,
    'class' => '',
    'rel' => '',
  );
}

703
704
705
706
707
708
/**
 * Process the link type element before displaying the field.
 *
 * Build the form element. When creating a form using FAPI #process,
 * note that $element['#value'] is already set.
 *
709
 * The $fields array is in $complete_form['#field_info'][$element['#field_name']].
710
 */
711
function link_field_process($element, $form_state, $complete_form) {
712
713
  $instance = field_widget_instance($element, $form_state);
  $settings = $instance['settings'];
714
715
716
717
718
719
720
721
722
723
  $element['url'] = array(
    '#type' => 'textfield',
    '#maxlength' => LINK_URL_MAX_LENGTH,
    '#title' => t('URL'),
    '#required' => ($element['#delta'] == 0 && $settings['url'] !== 'optional') ? $element['#required'] : FALSE,
    '#default_value' => isset($element['#value']['url']) ? $element['#value']['url'] : NULL,
  );
  if ($settings['title'] !== 'none' && $settings['title'] !== 'value') {
    $element['title'] = array(
      '#type' => 'textfield',
724
      '#maxlength' => $settings['title_maxlength'],  // patch #1307788 from nmc
725
      '#title' => t('Title'),
726
      '#description' => t('The link title is limited to @maxlength characters maximum.', array('@maxlength' => $settings['title_maxlength'])),
727
      '#required' => ($settings['title'] == 'required' && (($element['#delta'] == 0 && $element['#required']) || !empty($element['#value']['url']))) ? TRUE : FALSE, // davereids patch from jan 2011
728
729
730
731
732
733
734
735
      '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
    );
  }

  // Initialize field attributes as an array if it is not an array yet.
  if (!is_array($settings['attributes'])) {
    $settings['attributes'] = array();
  }
736
  // Add default attributes.
737
738
739
740
741
742
743
  $settings['attributes'] += _link_default_attributes();
  $attributes = isset($element['#value']['attributes']) ? $element['#value']['attributes'] : $settings['attributes'];
  if (!empty($settings['attributes']['target']) && $settings['attributes']['target'] == LINK_TARGET_USER) {
    $element['attributes']['target'] = array(
      '#type' => 'checkbox',
      '#title' => t('Open URL in a New Window'),
      '#return_value' => LINK_TARGET_NEW_WINDOW,
744
      '#default_value' => isset($attributes['target']) ? $attributes['target'] : FALSE,
745
746
    );
  }
747
748
749
750
751
752
753
754
755
  if (!empty($settings['attributes']['configurable_title']) && $settings['attributes']['configurable_title'] == 1) {
    $element['attributes']['title'] = array(
      '#type' => 'textfield',
      '#title' => t('Link "title" attribute'),
      '#default_value' => isset($attributes['title']) ? $attributes['title'] : '',
      '#field_prefix' => 'title = "',
      '#field_suffix' => '"',
    );
  }
756

757
758
759
760
761
762
763
  // If the title field is avaliable or there are field accepts multiple values
  // then allow the individual field items display the required asterisk if needed.
  if (isset($element['title']) || isset($element['_weight'])) {
    // To prevent an extra required indicator, disable the required flag on the
    // base element since all the sub-fields are already required if desired.
    $element['#required'] = FALSE;
  }
764

765
  return $element;
766
767
}

Nathan Haug's avatar
Nathan Haug committed
768
/**
769
770
771
772
 * Implementation of hook_field_formatter_info().
 */
function link_field_formatter_info() {
  return array(
773
    'link_default' => array(
774
      'label' => t('Title, as link (default)'),
775
776
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
777
    ),
778
779
780
781
782
    'link_title_plain' => array(
      'label' => t('Title, as plain text'),
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
    ),
783
    'link_url' => array(
784
      'label' => t('URL, as link'),
785
786
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
787
    ),
788
    'link_plain' => array(
789
      'label' => t('URL, as plain text'),
790
791
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
792
    ),
793
794
795
796
797
    'link_absolute' => array(
      'label' => t('URL, absolute'),
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
    ),
798
799
800
801
802
803
804
805
    'link_domain' => array(
      'label' => t('Domain, as link'),
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
      'settings' => array(
        'strip_www' => FALSE,
      ),
    ),
806
    'link_short' => array(
807
      'label' => t('Short, as link with title "Link"'),
808
809
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
810
    ),
811
    'link_label' => array(
812
      'label' => t('Label, as link with label as title'),
813
814
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
815
    ),
816
    'link_separate' => array(
817
      'label' => t('Separate title and URL'),
818
819
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
820
    ),
821
822
823
  );
}

824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
/**
 * Implements hook_field_formatter_settings_form().
 */
function link_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  $element = array();
  if ($display['type'] == 'link_domain') {
    $element['strip_www'] = array(
      '#title' => t('Strip www. from domain'),
      '#type' => 'checkbox',
      '#default_value' => $settings['strip_www'],
    );
  }
  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function link_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  if ($display['type'] == 'link_domain') {
    if ($display['settings']['strip_www']) {
      return t('Strip www. from domain');
    }
    else {
      return t('Leave www. in domain');
    }
  }
  return '';
}

858
859
860
/**
 * Implements hook_field_formatter_view().
 */
861
862
863
864
function link_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $elements = array();
  foreach ($items as $delta => $item) {
    $elements[$delta] = array(
865
      '#markup' => theme('link_formatter_'. $display['type'], array('element' => $item, 'field' => $instance, 'display' => $display)),
866
867
868
869
870
    );
  }
  return $elements;
}

871
/**
872
 * Theme function for 'default' text field formatter.
Nathan Haug's avatar
Nathan Haug committed
873
 */
874
function theme_link_formatter_link_default($vars) {
875
  $link_options = $vars['element'];
876
877
  unset($link_options['title']);
  unset($link_options['url']);
878

879
880
881
882
  // Issue #1199806 by ss81: Fixes fatal error when the link URl is equal to page URL
  if (isset($link_options['attributes']['class'])) {
    $link_options['attributes']['class'] = array($link_options['attributes']['class']);
  }
883

884
  // Display a normal link if both title and URL are available.
885
  if (!empty($vars['element']['title']) && !empty($vars['element']['url'])) {
886
    return l($vars['element']['title'], $vars['element']['url'], $link_options);
Nathan Haug's avatar
Nathan Haug committed
887
  }
888
  // If only a title, display the title.
889
890
  elseif (!empty($vars['element']['title'])) {
    return check_plain($vars['element']['title']);
891
  }
892
  elseif (!empty($vars['element']['url'])) {
893
    return l($vars['element']['title'], $vars['element']['url'], $link_options);
894
  }
895
}
896

897
898
899
/**
 * Theme function for 'plain' text field formatter.
 */
900
function theme_link_formatter_link_plain($vars) {
901
  $link_options = $vars['element'];
902
903
904
905
906
907
908
  if (isset($link_options['title'])) {
    unset($link_options['title']);
  }
  else {
    $vars['element']['title'] = '';
  }
  unset($link_options['url']);
909
910
911
  return empty($vars['element']['url']) ?
    check_plain($vars['element']['title']) :
    url($vars['element']['url'], $link_options);
912
913
}

914
915
916
917
918
919
920
921
/**
 * Theme function for 'absolute' text field formatter.
 */
function theme_link_formatter_link_absolute($vars) {
  $absolute = array('absolute' => TRUE);
  return empty($vars['element']['url']) ? '' : url($vars['element']['url'], $absolute + $vars['element']);
}

922
923
924
925
926
927
928
929
930
931
932
933
934
935
/**
 * Theme function for 'domain' text field formatter.
 */
function theme_link_formatter_link_domain($vars) {
  $link_options = $vars['element'];
  unset($link_options['title']);
  unset($link_options['url']);
  $domain = parse_url($vars['element']['display_url'], PHP_URL_HOST);
  if (!empty($vars['display']['settings']['strip_www'])) {
    $domain = str_replace('www.', '', $domain);
  }
  return $vars['element']['url'] ? l($domain, $vars['element']['url'], $link_options) : '';
}

936
937
938
939
940
941
942
/**
 * Theme function for 'title_plain' text field formatter.
 */
function theme_link_formatter_link_title_plain($vars) {
  return empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']);
}

943
944
945
/**
 * Theme function for 'url' text field formatter.
 */
946
function theme_link_formatter_link_url($vars) {
947
  $link_options = $vars['element'];
948
949
  unset($link_options['title']);
  unset($link_options['url']);
950
  return $vars['element']['url'] ? l($vars['element']['display_url'], $vars['element']['url'], $link_options) : '';
951
}
952

953
954
955
/**
 * Theme function for 'short' text field formatter.
 */
956
function theme_link_formatter_link_short($vars) {
957
  $link_options = $vars['element'];
958
959
  unset($link_options['title']);
  unset($link_options['url']);
960
  return $vars['element']['url'] ? l(t('Link'), $vars['element']['url'], $link_options) : '';
Nathan Haug's avatar
Nathan Haug committed
961
962
}

963
/**
964
 * Theme function for 'label' text field formatter.
965
 */
966
function theme_link_formatter_link_label($vars) {
967
  $link_options = $vars['element'];
968
969
  unset($link_options['title']);
  unset($link_options['url']);
970
  return $vars['element']['url'] ? l($vars['field']['label'], $vars['element']['url'], $link_options) : '';
971
}
972

973
/**
974
 * Theme function for 'separate' text field formatter.
975
 */