link.module 44.6 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
100
101
    '#options' => $title_options,
    '#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 node 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.'),
  );
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
  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."),
    );
    $token_type = array(
      'theme' => 'token_tree',
      'token_types' => array($instance['entity_type']),
      '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,
      '#description' => t('Checking will allow users to enter tokens in URLs and Titles on the node edit form. This does not affect the field settings on this page.'),
    );
  }
147

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

160
161
162
163
164
165
166
167
168
169
170
171
  $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'),
172
    '#default_value' => empty($instance['settings']['attributes']['target']) ? LINK_TARGET_DEFAULT : $instance['settings']['attributes']['target'],
173
174
175
176
177
178
    '#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.'),
179
    '#default_value' => empty($instance['settings']['attributes']['rel']) ? '' : $instance['settings']['attributes']['rel'],
180
181
182
183
    '#field_prefix' => 'rel = "',
    '#field_suffix' => '"',
    '#size' => 20,
  );
184
185
186
187
188
189
190
191
192
193
194
195
  $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,
  );
196
197
198
  $form['attributes']['class'] = array(
    '#type' => 'textfield',
    '#title' => t('Additional CSS Class'),
199
    '#description' => t('When output, this link will have this class attribute. Multiple classes should be separated by spaces.'),
200
    '#default_value' => empty($instance['settings']['attributes']['class']) ? '' : $instance['settings']['attributes']['class'],
201
  );
202
203
204
205
206
207
208
209
210
211
212
213
214
215
  $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,
  );
216
217
218
219
220
221
222
  return $form;
}

/**
 * Validate the field settings form.
 */
function link_field_settings_form_validate($element, &$form_state, $complete_form) {
223
224
  if ($form_state['values']['instance']['settings']['title'] === 'value'
      && empty($form_state['values']['instance']['settings']['title_value'])) {
225
226
    form_set_error('title_value', t('A default title must be provided if the title is a static value.'));
  }
227
228
229
230
231
232
233
234
235
236
237
238
  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.'));
  }

239
240
}

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

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

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

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

275
/**
276
 * Implements hook_field_insert().
277
 */
278
279
280
281
282
283
284
285
286
287
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) {
288
289
  foreach ($items as $delta => $value) {
    _link_process($items[$delta], $delta, $field, $entity);
Nathan Haug's avatar
Nathan Haug committed
290
291
292
  }
}

293
294
295
296
297
298
299
300
301
302
303
/**
 * 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
304
/**
305
 * Implements hook_field_widget_info().
Nathan Haug's avatar
Nathan Haug committed
306
 */
307
function link_field_widget_info() {
Nathan Haug's avatar
Nathan Haug committed
308
  return array(
309
    'link_field' => array(
310
      'label' => 'Link',
311
312
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
Nathan Haug's avatar
Nathan Haug committed
313
314
315
316
317
    ),
  );
}

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

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

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

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

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

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

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

/**
416
 * Cleanup user-entered values for a link field according to field settings.
417
 *
418
419
420
421
422
423
424
425
 * @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.
 * @param $node
 *   The node containing this link.
426
 */
427
function _link_sanitize(&$item, $delta, &$field, $instance, &$node) {
428
429
430
431
432
  // Don't try to process empty links.
  if (empty($item['url']) && empty($item['title'])) {
    return;
  }

433
  // Replace URL tokens.
434
  if (isset($instance['settings']['enable_tokens']) && $instance['settings']['enable_tokens']) {
435
    global $user;
436
437
    // Load the node if necessary for nodes in views.
    $token_node = isset($node->nid) ? node_load($node->nid) : $node;
438
    $item['url'] = token_replace($item['url'], array('node' => $token_node));
439
440
441
  }

  $type = link_validate_url($item['url']);
442
443
444
445
446
  // 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;
  }
447
  $url = link_cleanup_url($item['url']);
448
  $url_parts = array();
449

450
  // Separate out the anchor if any.
451
  if (strpos($url, '#') !== FALSE) {
452
    $url_parts['fragment'] = substr($url, strpos($url, '#') + 1);
453
    $url = substr($url, 0, strpos($url, '#'));
454
  }
455
  // Separate out the query string if any.
456
  if (strpos($url, '?') !== FALSE) {
457
458
    $query = substr($url, strpos($url, '?') + 1);
    parse_str($query, $query_array);
459
460
461
462
463
464
    // See http://drupal.org/node/1710578
    foreach ($query_array as $key=> &$value) {
      if ($value === '' && FALSE === strpos($query, $key . '=')) {
        $value = NULL;
      }
    }
465
    $url_parts['query'] = $query_array;
466
467
    $url = substr($url, 0, strpos($url, '?'));
  }
468

469
  $item['url'] = url(check_plain($url), $url_parts);
470
471

  // Create a shortened URL for display.
472
473
  $display_url = $type == LINK_EMAIL ?
                  str_replace('mailto:', '', $url) :
474
475
                  url($url, array('query' => isset($url_parts['query']) ?
                                              $url_parts['query'] :
476
                                              NULL,
477
478
                                  'fragment' => isset($url_parts['fragment']) ?
                                                $url_parts['fragment'] :
479
480
481
482
                                                NULL,
                                  'absolute' => TRUE));
  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']) ."...";
483
484
485
  }
  $item['display_url'] = $display_url;

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

498
  // Replace tokens.
499
  if ($title && ($instance['settings']['title'] == 'value' || $instance['settings']['enable_tokens'])) {
500
501
    // Load the node if necessary for nodes in views.
    $token_node = isset($node->nid) ? node_load($node->nid) : $node;
502
503
    $title = filter_xss(token_replace($title, array('node' => $token_node)),
                        array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u'));
504
    $item['html'] = TRUE;
505
  }
506
  $item['title'] = empty($title) ? $item['display_url'] : $title;
507

508
509
510
511
512
513
514
515
516
517
  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.
518
519
520
521
522
523
  if (!is_array($instance['settings']['attributes'])){
    $instance['settings']['attributes'] = _link_default_attributes();
  }
  else {
    $instance['settings']['attributes'] += _link_default_attributes();
  }
524
525

  // Merge item attributes with attributes defined at the field level.
526
  $item['attributes'] += $instance['settings']['attributes'];
527
528
529

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

  // 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']);
  }

542
543
544
545
546
547
  // 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']);
    }
548
  }
549

550
551
552
553
554
555
556
557
  // Handle "title" link attribute.
  if (!empty($item['attributes']['title']) && module_exists('token')) {
    // Load the node (necessary for nodes in views).
    $token_node = isset($node->nid) ? node_load($node->nid) : $node;
    $item['attributes']['title'] = filter_xss(token_replace($item['attributes']['title'], array('node' => $token_node)),
                        array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u'));
  }
  // Remove title attribute if it's equal to link text.
558
  if (isset($item['attributes']['title']) && $item['attributes']['title'] == $item['title']) {
559
560
561
562
    unset($item['attributes']['title']);
  }
  unset($item['attributes']['configurable_title']);

563
564
  // Remove empty attributes.
  $item['attributes'] = array_filter($item['attributes']);
565

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

575
576
577
}

/**
578
 * Implements hook_theme().
579
 */
580
581
function link_theme() {
  return array(
582
    'link_formatter_link_default' => array(
583
      'variables' => array('element' => NULL),
584
    ),
585
    'link_formatter_link_plain' => array(
586
      'variables' => array('element' => NULL),
587
    ),
588
589
590
    'link_formatter_link_absolute' => array(
      'variables' => array('element' => NULL),
    ),
591
592
593
    'link_formatter_link_domain' => array(
      'variables' => array('element' => NULL),
    ),
594
595
596
    'link_formatter_link_title_plain' => array(
      'variables' => array('element' => NULL),
    ),
597
    'link_formatter_link_url' => array(
598
      'variables' => array('element' => NULL),
599
    ),
600
    'link_formatter_link_short' => array(
601
      'variables' => array('element' => NULL),
602
    ),
603
    'link_formatter_link_label' => array(
604
      'variables' => array('element' => NULL),
605
    ),
606
    'link_formatter_link_separate' => array(
607
      'variables' => array('element' => NULL),
608
    ),
609
    'link_field' => array(
610
      'render element' => 'element',
611
612
613
614
615
616
617
    ),
  );
}

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

621
  $element = $vars['element'];
622
623
  // Prefix single value link fields with the name of the field.
  if (empty($element['#field']['multiple'])) {
624
625
    if (isset($element['url']) && !isset($element['title'])) {
      unset($element['url']['#title']);
626
    }
627
628
  }

629
  $output = '';
630
  $output .= '<div class="link-field-subrow clearfix">';
631
  if (isset($element['title'])) {
632
    $output .= '<div class="link-field-title link-field-column">'. drupal_render($element['title']) .'</div>';
633
  }
634
  $output .= '<div class="link-field-url'. (isset($element['title']) ? ' link-field-column' : '') .'">'. drupal_render($element['url']) .'</div>';
635
  $output .= '</div>';
636
  if (!empty($element['attributes']['target'])) {
637
    $output .= '<div class="link-attributes">'. drupal_render($element['attributes']['target']) .'</div>';
638
  }
639
640
641
  if (!empty($element['attributes']['title'])) {
    $output .= '<div class="link-attributes">'. drupal_render($element['attributes']['title']) .'</div>';
  }
642
  return $output;
643
644
}

645
/**
646
 * Implements hook_element_info().
647
 */
648
function link_element_info() {
649
  $elements = array();
650
  $elements['link_field'] =  array(
651
    '#input' => TRUE,
652
    '#process' => array('link_field_process'),
653
654
    '#theme' => 'link_field',
    '#theme_wrappers' => array('form_element'),
655
656
657
658
  );
  return $elements;
}

659
660
661
662
663
664
665
666
function _link_default_attributes() {
  return array(
    'target' => LINK_TARGET_DEFAULT,
    'class' => '',
    'rel' => '',
  );
}

667
668
669
670
671
672
/**
 * 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.
 *
673
 * The $fields array is in $complete_form['#field_info'][$element['#field_name']].
674
 */
675
function link_field_process($element, $form_state, $complete_form) {
676
677
  $instance = field_widget_instance($element, $form_state);
  $settings = $instance['settings'];
678
679
680
681
682
683
684
685
686
687
  $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',
688
      '#maxlength' => $settings['title_maxlength'],  // patch #1307788 from nmc
689
      '#title' => t('Title'),
690
      '#description' => t('The link title is limited to @maxlength characters maximum.', array('@maxlength' => $settings['title_maxlength'])),
691
      '#required' => ($settings['title'] == 'required' && (($element['#delta'] == 0 && $element['#required']) || !empty($element['#value']['url']))) ? TRUE : FALSE, // davereids patch from jan 2011
692
693
694
695
696
697
698
699
      '#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();
  }
700
  // Add default attributes.
701
702
703
704
705
706
707
  $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,
708
      '#default_value' => isset($attributes['target']) ? $attributes['target'] : FALSE,
709
710
    );
  }
711
712
713
714
715
716
717
718
719
  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' => '"',
    );
  }
720

721
722
723
724
725
726
727
  // 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;
  }
728

729
  return $element;
730
731
}

Nathan Haug's avatar
Nathan Haug committed
732
/**
733
734
735
736
 * Implementation of hook_field_formatter_info().
 */
function link_field_formatter_info() {
  return array(
737
    'link_default' => array(
738
      'label' => t('Title, as link (default)'),
739
740
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
741
    ),
742
743
744
745
746
    'link_title_plain' => array(
      'label' => t('Title, as plain text'),
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
    ),
747
    'link_url' => array(
748
      'label' => t('URL, as link'),
749
750
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
751
    ),
752
    'link_plain' => array(
753
      'label' => t('URL, as plain text'),
754
755
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
756
    ),
757
758
759
760
761
    'link_absolute' => array(
      'label' => t('URL, absolute'),
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
    ),
762
763
764
765
766
767
768
769
    'link_domain' => array(
      'label' => t('Domain, as link'),
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
      'settings' => array(
        'strip_www' => FALSE,
      ),
    ),
770
    'link_short' => array(
771
      'label' => t('Short, as link with title "Link"'),
772
773
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
774
    ),
775
    'link_label' => array(
776
      'label' => t('Label, as link with label as title'),
777
778
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
779
    ),
780
    'link_separate' => array(
781
      'label' => t('Separate title and URL'),
782
783
      'field types' => array('link_field'),
      'multiple values' => FIELD_BEHAVIOR_DEFAULT,
784
    ),
785
786
787
  );
}

788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
/**
 * 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 '';
}

822
823
824
/**
 * Implements hook_field_formatter_view().
 */
825
826
827
828
function link_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $elements = array();
  foreach ($items as $delta => $item) {
    $elements[$delta] = array(
829
      '#markup' => theme('link_formatter_'. $display['type'], array('element' => $item, 'field' => $instance, 'display' => $display)),
830
831
832
833
834
    );
  }
  return $elements;
}

835
/**
836
 * Theme function for 'default' text field formatter.
Nathan Haug's avatar
Nathan Haug committed
837
 */
838
function theme_link_formatter_link_default($vars) {
839
  $link_options = $vars['element'];
840
841
  unset($link_options['title']);
  unset($link_options['url']);
842

843
844
845
846
  // 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']);
  }
847

848
  // Display a normal link if both title and URL are available.
849
  if (!empty($vars['element']['title']) && !empty($vars['element']['url'])) {
850
    return l($vars['element']['title'], $vars['element']['url'], $link_options);
Nathan Haug's avatar
Nathan Haug committed
851
  }
852
  // If only a title, display the title.
853
854
  elseif (!empty($vars['element']['title'])) {
    return check_plain($vars['element']['title']);
855
  }
856
  elseif (!empty($vars['element']['url'])) {
857
    return l($vars['element']['title'], $vars['element']['url'], $link_options);
858
  }
859
}
860

861
862
863
/**
 * Theme function for 'plain' text field formatter.
 */
864
function theme_link_formatter_link_plain($vars) {
865
  $link_options = $vars['element'];
866
867
868
869
870
871
872
  if (isset($link_options['title'])) {
    unset($link_options['title']);
  }
  else {
    $vars['element']['title'] = '';
  }
  unset($link_options['url']);
873
874
875
  return empty($vars['element']['url']) ?
    check_plain($vars['element']['title']) :
    url($vars['element']['url'], $link_options);
876
877
}

878
879
880
881
882
883
884
885
/**
 * 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']);
}

886
887
888
889
890
891
892
893
894
895
896
897
898
899
/**
 * 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) : '';
}

900
901
902
903
904
905
906
/**
 * 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']);
}

907
908
909
/**
 * Theme function for 'url' text field formatter.
 */
910
function theme_link_formatter_link_url($vars) {
911
  $link_options = $vars['element'];
912
913
  unset($link_options['title']);
  unset($link_options['url']);
914
  return $vars['element']['url'] ? l($vars['element']['display_url'], $vars['element']['url'], $link_options) : '';
915
}
916

917
918
919
/**
 * Theme function for 'short' text field formatter.
 */
920
function theme_link_formatter_link_short($vars) {
921
  $link_options = $vars['element'];
922
923
  unset($link_options['title']);
  unset($link_options['url']);
924
  return $vars['element']['url'] ? l(t('Link'), $vars['element']['url'], $link_options) : '';
Nathan Haug's avatar
Nathan Haug committed
925
926
}

927
/**
928
 * Theme function for 'label' text field formatter.
929
 */
930
function theme_link_formatter_link_label($vars) {
931
  $link_options = $vars['element'];
932
933
  unset($link_options['title']);
  unset($link_options['url']);
934
  return $vars['element']['url'] ? l($vars['field']['label'], $vars['element']['url'], $link_options) : '';
935
}
936

937
/**
938
 * Theme function for 'separate' text field formatter.
939
 */
940

941
function theme_link_formatter_link_separate($vars) {
942
943
  $class = empty($vars['element']['attributes']['class']) ? '' : ' '. $vars['element']['attributes']['class'];
  unset($vars['element']['attributes']['class']);
944
  $link_options = $vars['element'];
945
946
  unset($link_options['title']);
  unset($link_options['url']);
947
  $title = empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']);
948
949

  /**
950
951
   * @TODO static html markup looks not very elegant
   * needs smarter output solution and an optional title/url seperator
952
   */