link.module 29.1 KB
Newer Older
Nathan Haug's avatar
Nathan Haug committed
1
2
3
4
5
6
7
8
<?php
// $Id$

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

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

Nathan Haug's avatar
Nathan Haug committed
15
16
17
18
19
20
/**
 * Implementation of hook_help().
 */
function link_help($section) {
  switch ($section) {
    case 'admin/modules#description':
21
      return t('<strong>CCK:</strong> Defines simple link field types. <em>Note: Requires content.module.</em>');
Nathan Haug's avatar
Nathan Haug committed
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
  }
}

/**
 * Implementation of hook_field_info().
 */
function link_field_info() {
  return array(
    'link' => array('label' => 'Link'),
  );
}

/**
 * Implementation of hook_field_settings().
 */
function link_field_settings($op, $field) {
  switch ($op) {
    case 'form':
40
41
42
      $form = array(
        '#theme' => 'link_field_settings',
      );
43
      
44
      $title_options = array(
45
46
        'optional' => t('Optional Title'),
        'required' => t('Required Title'),
47
        'value' => t('Static Title: '),
48
49
50
51
52
53
54
        'none' => t('No Title'),
      );
      
      $form['title'] = array(
        '#type' => 'radios',
        '#title' => t('Link Title'),
        '#default_value' => isset($field['title']) ? $field['title'] : 'optional',
55
        '#options' => $title_options,
56
57
58
59
60
61
62
        '#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.'),
      );
      
      $form['title_value'] = array(
        '#type' => 'textfield',
        '#default_value' => $field['title_value'],
        '#size' => '46',
63
64
      );
      
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
      // Add token module replacements if available
      if (module_exist('token')) {
        $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."),
        );
        $form['tokens']['help'] = array(
          '#value' => theme('token_help', 'node'),
        );
        
        $form['enable_tokens'] = array(
          '#type' => 'checkbox',
          '#title' => t('Allow tokens'),
          '#default_value' => isset($field['enable_tokens']) ? $field['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.'),
        );
      }
      
86
87
88
89
90
91
92
93
94
95
96
97
98
      $form['display'] = array(
        '#tree' => true,
      );
      $form['display']['url_cutoff'] = array(
        '#type' => 'textfield',
        '#title' => t('URL Display Cutoff'),
        '#default_value' => $field['display']['url_cutoff'] ? $field['display']['url_cutoff'] : '80',
        '#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,
      );
      
      $target_options = array(
Nathan Haug's avatar
Nathan Haug committed
99
100
101
102
103
        'default' => t('Default (no target attribute)'),
        '_top' => t('Open link in window root'),
        '_blank' => t('Open link in new window'),
        'user' => t('Allow the user to choose'),
      );
104
      $form['attributes'] = array(
105
        '#tree' => true,
Nathan Haug's avatar
Nathan Haug committed
106
107
108
109
110
      );
      $form['attributes']['target'] = array(
        '#type' => 'radios',
        '#title' => t('Link Target'),
        '#default_value' => $field['attributes']['target'] ? $field['attributes']['target'] : 'default',
111
        '#options' => $target_options,
Nathan Haug's avatar
Nathan Haug committed
112
      );
Nathan Haug's avatar
Nathan Haug committed
113
      $form['attributes']['rel'] = array(
114
115
        '#type' => 'textfield',
        '#title' => t('Rel Attribute'),
116
117
        '#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.'),
        '#default_value' => $field['attributes']['rel'] ? $field['attributes']['rel'] : '',
Nathan Haug's avatar
Nathan Haug committed
118
      );
119
120
121
122
123
124
      $form['attributes']['class'] = array(
        '#type' => 'textfield',
        '#title' => t('Additional CSS Class'),
        '#description' => t('When output, this link will have have this class attribute. Multiple classes should be seperated by spaces.'),
        '#default_value' => isset($field['attributes']['class']) ? $field['attributes']['class'] : '',
      );
Nathan Haug's avatar
Nathan Haug committed
125
126
      return $form;

127
128
129
130
131
132
    case 'validate':
      if ($field['title'] == 'value' && empty($field['title_value'])) {
        form_set_error('title_value', t('A default title must be provided if the title is a static value'));
      }
      break;

Nathan Haug's avatar
Nathan Haug committed
133
    case 'save':
134
      return array('attributes', 'display', 'title', 'title_value', 'enable_tokens');
135
136
137
138
139

    case 'database columns':
      return array(
        'url' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => "''"),
        'title' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => "''"),
140
        'attributes' => array('type' => 'mediumtext', 'not null' => FALSE),
141
      );
142
143
144

    case 'filters':
      return array(
145
        'default' => array(
146
          'name' => t('URL'),
147
          'operator' => 'views_handler_operator_like',
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
          'handler' => 'views_handler_operator_like',
        ),
        'title' => array(
          'name' => t('Title'),
          'operator' => 'views_handler_operator_like',
          'handler' => 'views_handler_operator_like',
        ),
        'protocol' => array(
          'name' => t('Protocol'),
          'list' => drupal_map_assoc(variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'))),
          'operator' => 'views_handler_operator_or',
          'handler' => 'link_views_protocol_filter_handler',
        ),
      );
      
    case 'arguments':
      return array(
        'content: '. $field['field_name'] .'_url' => array(
          'name' => t('Link URL') .': '. $field['widget']['label'] .' ('. $field['field_name'] .')',
          'handler' => 'link_views_argument_handler',
        ),
        'content: '. $field['field_name'] .'_title' => array(
          'name' => t('Link Title') .': '. $field['widget']['label'] .' ('. $field['field_name'] .')',
          'handler' => 'link_views_argument_handler',
        ),
        'content: '. $field['field_name'] .'_target' => array(
          'name' => t('Link Target') .': '. $field['widget']['label'] .' ('. $field['field_name'] .')',
          'handler' => 'link_views_argument_handler',
        
177
178
        ),
      );
179

Nathan Haug's avatar
Nathan Haug committed
180
181
182
  }
}

183
184
185
186
187
188
189
190
191
192
193
194
195
196
/**
 * Theme the settings form for the link field.
 */
function theme_link_field_settings($form) {
  $title_value = form_render($form['title_value']);
  $title_checkbox = form_render($form['title']['value']);
  
  // Set Static Title radio option to include the title_value textfield
  $form['title']['value'] = array('#value' => '<div class="container-inline">'. $title_checkbox . $title_value .'</div>');
  
  // Reprint the title radio options with the included textfield
  return form_render($form);
}

Nathan Haug's avatar
Nathan Haug committed
197
198
199
/**
 * Implementation of hook_field().
 */
200
function link_field($op, &$node, $field, &$items, $teaser, $page) {
Nathan Haug's avatar
Nathan Haug committed
201
  switch ($op) {
202
    case 'load':
203
      foreach ($items as $delta => $item) {
204
        $items[$delta]['attributes'] = unserialize($item['attributes']);
Nathan Haug's avatar
Nathan Haug committed
205
      }
206
      return $items;
207
      break;
208
    case 'view':
209
      foreach ($items as $delta => $item) {
210
        $items[$delta]['view'] = content_format($field, $items[$delta], 'default', $node);
211
      }
212
      return theme('field', $node, $field, $items, $teaser, $page);
213
      break;
Nathan Haug's avatar
Nathan Haug committed
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
  }
}

/**
 * Implementation of hook_widget_info().
 */
function link_widget_info() {
  return array(
    'link' => array(
      'label' => 'Text Fields for Title and URL',
      'field types' => array('link'),
    ),
  );
}

/**
 * Implementation of hook_widget().
 */
function link_widget($op, &$node, $field, &$node_field) {
  switch ($op) {
234
    case 'prepare form values':
235
      foreach($node_field as $delta => $value) {
236
        if (is_numeric($delta)) {
237
          _link_widget_prepare($node_field[$delta], $delta);
238
        }
239
      }
240
241
242
243
244
      if ($_POST[$field['field_name']]) {
        $node_field = $_POST[$field['field_name']];
        unset($node_field['count'], $node_field['more-url'], $node_field['more']);
      }
    case 'form':
Nathan Haug's avatar
Nathan Haug committed
245
      $form = array();
246
247
248
249
250
251
252
253
254
      $form[$field['field_name']] = array(
        '#tree' => TRUE,
        '#theme' => 'link_widget_form',
        '#type' => $field['multiple'] ? 'fieldset' : 'markup',
        '#collapsible' => TRUE,
        '#collapsed' => FALSE,
        '#title' => $field['widget']['label'],
        '#description' => $field['widget']['description'],
      );
Nathan Haug's avatar
Nathan Haug committed
255
256

      if ($field['multiple']) {
257
258
259
260
        // Generate more fields if necessary on preview
        if ($_POST['edit'][$field['field_name']]) {
          $node_field = $_POST['edit'][$field['field_name']];
        }
261
        
Nathan Haug's avatar
Nathan Haug committed
262
        $delta = 0;
263
        // Render link fields for all the entered values
Nathan Haug's avatar
Nathan Haug committed
264
        foreach ($node_field as $data) {
265
          if (is_array($data) && $data['url']) {
266
            _link_widget_form($form[$field['field_name']][$delta], $field, $data, $delta);
Nathan Haug's avatar
Nathan Haug committed
267
268
269
            $delta++;
          }
        }
270
        // Render two additional new link fields
Nathan Haug's avatar
Nathan Haug committed
271
        foreach (range($delta, $delta + 1) as $delta) {
272
          _link_widget_form($form[$field['field_name']][$delta], $field, array(), $delta);
Nathan Haug's avatar
Nathan Haug committed
273
        }
274
275
276
277
278
279
        
        // Create a wrapper for additional fields
        $form[$field['field_name']]['wrapper'] = array(
          '#type' => 'markup',
          '#value' => '<div id="' . str_replace('_', '-', $field['field_name']) . '-wrapper" class="clear-block"></div>',
        );
280
281

        // Add token module replacements if available
282
        if (module_exist('token') && $field['enable_tokens']) {
283
284
285
286
287
          $form[$field['field_name']]['tokens'] = array(
            '#type' => 'fieldset',
            '#collapsible' => TRUE,
            '#collapsed' => TRUE,
            '#title' => t('Placeholder tokens'),
288
            '#description' => t("The following placeholder tokens can be used in both titles and URLs. When used in a URL or title, they will be replaced with the appropriate values."),
289
290
291
292
293
294
          );
          $form[$field['field_name']]['tokens']['help'] = array(
            '#value' => theme('token_help', 'node'),
          );
        }

295
296
297
298
        // Add 'More' Javascript Callback
        $form[$field['field_name']]['more-url'] = array(
          '#type' => 'hidden',
          '#value' => url('link/widget/js/' . $field['type_name'] . '/' . $field['field_name'], NULL, NULL, TRUE),
299
          '#attributes' => array('class' => 'more-links'),
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
          '#id' => str_replace('_', '-', $field['field_name']) . '-more-url',
        );
        
        // Add Current Field Count
        $form[$field['field_name']]['count'] = array(
          '#type' => 'hidden',
          '#value' => $delta,
          '#id' => str_replace('_', '-', $field['field_name']) . '-count',
        );
        
        // Add More Button
        $form[$field['field_name']]['more'] = array(
          '#type' => 'button',
          '#value' => t('More Links'),
          '#name' => 'more',
          '#id' => str_replace('_', '-', $field['field_name']) . '-more',
        );
Nathan Haug's avatar
Nathan Haug committed
317
318
      } // end if multiple
      else {
Nathan Haug's avatar
Nathan Haug committed
319
        _link_widget_form($form[$field['field_name']][0], $field, $node_field[0]);
320
321
322
323
324
325
326
327
328
329
330
331
332
333
        
        // Add token module replacements if available
        if (module_exist('token') && $field['enable_tokens']) {
          $form[$field['field_name']][0]['tokens'] = array(
            '#type' => 'fieldset',
            '#collapsible' => TRUE,
            '#collapsed' => TRUE,
            '#title' => t('Placeholder tokens'),
            '#description' => t("The following placeholder tokens can be used in both titles and URLs. When used in a URL or title, they will be replaced with the appropriate values."),
          );
          $form[$field['field_name']][0]['tokens']['help'] = array(
            '#value' => theme('token_help', 'node'),
          );
        }
Nathan Haug's avatar
Nathan Haug committed
334
335
336
337
      }
      return $form;

    case 'validate':
338
      if (!is_object($node)) return;
339
340
341
      unset($node_field['count']);
      unset($node_field['more-url']);
      unset($node_field['more']);
Nathan Haug's avatar
Nathan Haug committed
342
      foreach($node_field as $delta => $value) {
343
        if ($value['url'] && !(isset($field['widget']['default_value'][$delta]['url']) && $value['url'] == $field['widget']['default_value'][$delta]['url'] && !$field['required'])) {
Nathan Haug's avatar
Nathan Haug committed
344
          // Validate the link
345
346
          if (link_validate_url(trim($value['url'])) == FALSE) {
            form_set_error($field['field_name'] .']['. $delta. '][url', t('Not a valid URL.'));
347
          }
Nathan Haug's avatar
Nathan Haug committed
348
          // Require a title for the link if necessary
349
          elseif ($field['title'] == 'required' && strlen(trim($value['title'])) == 0) {
350
            form_set_error($field['field_name'] .']['. $delta. '][title', t('Titles are required for all links.'));
Nathan Haug's avatar
Nathan Haug committed
351
352
          }
        }
Nathan Haug's avatar
Nathan Haug committed
353
        // Require a link if we have a title
354
        elseif (strlen($value['title']) > 0) {
355
          form_set_error($field['field_name'] .']['. $delta. '][url', t('You cannot enter a title without a link.'));
356
        }
Nathan Haug's avatar
Nathan Haug committed
357
358
359
360
      }
      return;
      
    case 'process form values':
361
362
363
364
      // Remove the JS helper fields
      unset($node_field['more-url']);
      unset($node_field['count']);
      unset($node_field['more']);
Nathan Haug's avatar
Nathan Haug committed
365
      foreach($node_field as $delta => $value) {
366
367
368
        if (is_numeric($delta)) {
          _link_widget_process($node_field[$delta],$delta, $field, $node);
        }
Nathan Haug's avatar
Nathan Haug committed
369
370
371
372
      }
      return;
    
    case 'submit':
373
374
375
376
377
378
379
380
381
382
383
      // Don't save empty fields (beyond the first one)
      $save_field = array();
      unset($node_field['more-url']);
      unset($node_field['count']);
      unset($node_field['more']);
      foreach ($node_field as $delta => $value) {
        if (!empty($value['url']) || $delta == 0) {
          $save_field[] = $node_field[$delta];
        }
      }
      $node_field = $save_field;
Nathan Haug's avatar
Nathan Haug committed
384
385
386
387
      return;
  }
}

388
389
390
391
/**
 * Helper function renders the link widget in both single and multiple value cases.
 */

392
function _link_widget_form(&$form_item, $field, $node_field, $delta = 0) {
393
394
  
  $form_item = array(
395
    '#tree' => TRUE,
396
    '#theme' => 'link_widget_form_row',
397
398
    // Add a microweight to keep fields in first-in first-out order
    '#weight' => $field['widget']['weight'].".00".$delta,
399
  );
400
401
402
403
404
  
  $default_url = "";
  if (isset($field['widget']['default_value'][$delta]['url'])) {
    $default_url = $field['widget']['default_value'][$delta]['url'];
  }
405
  
406
  $form_item['url'] = array(
407
    '#type' => 'textfield',
Nathan Haug's avatar
Nathan Haug committed
408
    '#maxlength' => '255',
409
    '#title' => $delta == 0 ? t('URL') : NULL,
410
    '#default_value' => ($node_field['url']) ? $node_field['url'] : $default_url,
411
412
    '#required' => ($delta == 0) ? $field['required'] : FALSE,
  );
413
  if ($field['title'] == 'optional' || $field['title'] == 'required') {
414
415
416
417
    $default_title = "";
    if (isset($field['widget']['default_value'][$delta]['title'])) {
      $default_title = $field['widget']['default_value'][$delta]['title'];
    }
418
    $form_item['title'] = array(
419
      '#type' => 'textfield',
Nathan Haug's avatar
Nathan Haug committed
420
      '#maxlength' => '255',
421
      '#title' => $delta == 0 ? t('Title') : NULL,
422
      '#default_value' => ($node_field['title']) ? $node_field['title'] : $default_title,
423
424
425
      '#required' => ($delta == 0 && $field['title'] == 'required') ? $field['required'] : FALSE,
    );
  }
426
  if ($field['attributes']['target'] == 'user') {
427
    $form_item['attributes']['target'] = array(
428
429
      '#type' => 'checkbox',
      '#title' => t('Open URL in a New Window'),
430
      '#default_value' => $node_field['attributes']['target'],
431
432
433
434
435
      '#return_value' => "_blank",
    );
  }
}

436
437
438
439
440
function _link_widget_prepare(&$node_field, $delta = 0) {
  // Unserialize the attributes array
  $node_field['attributes'] = unserialize($node_field['attributes']);
}

441
function _link_widget_process(&$node_field, $delta = 0, $field, $node) {
442
  // Remove the target attribute if not selected
443
444
  if (!$node_field['attributes']['target'] || $node_field['attributes']['target'] == "default") {
    unset($node_field['attributes']['target']);
Nathan Haug's avatar
Nathan Haug committed
445
  }
446
447
  // Trim whitespace from URL
  $node_field['url'] = trim($node_field['url']);
448
449
  // Serialize the attributes array
  $node_field['attributes'] = serialize($node_field['attributes']);
450
451
452
453
454
455
456
  
  //don't save an invalid default value (e.g. 'http://')
  if ((isset($field['widget']['default_value'][$delta]['url']) && $node_field['url'] == $field['widget']['default_value'][$delta]['url']) && is_object($node)) {
    if (!link_validate_url($node_field['url'])) {
      unset($node_field['url']);
    }
  }
457
458
}

459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
function _link_widget_name($form) {
  if (!isset($form['#name'])) {
    $name = array_shift($form['#parents']);
    $form['#name'] = $name;

    if (count($form['#parents'])) {
      $form['#name'] .= '['. implode('][', $form['#parents']) .']';
    }
    array_unshift($form['#parents'], $name);
  }
  if (!isset($form['#id'])) {
    $form['#id'] = form_clean_id('edit-'. implode('-', $form['#parents']));
  }
  return $form;
}

475
476
477
478
479
/**
 * Theme the display of the entire link set
 */
function theme_link_widget_form($element) {
  drupal_set_html_head('<style type="text/css" media="all">@import "/' . drupal_get_path('module', 'link') .'/link.css' . '";</style>');
480
  // Check for multiple (output normally)
481
482
483
  if (isset($element[1])) {
    $output = form_render($element);
  }
484
  // Add the field label to the 'Title' and 'URL' labels
485
486
487
488
  else {
    if (isset($element[0]['title'])) {
      $element[0]['title']['#title'] = $element['#title'] . ' ' . $element[0]['title']['#title'];
      $element[0]['title']['#description'] = $element['#description'];
489
      $element[0]['url']['#title'] = $element['#title'] . ' ' . $element[0]['url']['#title'];
490
491
    }
    else {
492
      $element[0]['url']['#title'] = $element['#title'];
493
494
495
496
497
498
499
500
501
502
503
504
505
      $element[0]['url']['#description'] = $element['#description'];
    }
    $output = form_render($element[0]);
  }
  
  return $output;
}

/**
 * Theme the display of a single form row
 */
function theme_link_widget_form_row($element) {  
  $output = '';
506
  $output .= '<div class="link-field-row"><div class="link-field-subrow">';
507
508
509
510
  if ($element['title']) {
    $output .= '<div class="link-field-title link-field-column">' . form_render($element['title']) . '</div>';
  }
  $output .= '<div class="link-field-url' . ($element['title'] ? ' link-field-column' : '') . '">' . form_render($element['url']) . '</div>';
511
  $output .= '</div><br class="clear" />';
512
513
514
515
  if ($element['attributes']) {
    $output .= '<div class="link-attributes">' . form_render($element['attributes']) . '</div>';
  }
  $output .= form_render($element);
Nathan Haug's avatar
Nathan Haug committed
516
  $output .= '</div><br class="clear" />';
517
518
519
  return $output;
}

Nathan Haug's avatar
Nathan Haug committed
520
/**
521
522
523
524
525
 * Implementation of hook_field_formatter_info().
 */
function link_field_formatter_info() {
  return array(
    'default' => array(
526
      'label' => t('Default, as link with title'),
527
528
529
      'field types' => array('link'),
    ),
    'plain' => array(
530
      'label' => t('Plain, as the text URL'),
531
532
533
      'field types' => array('link'),
    ),
    'short' => array(
534
      'label' => t('Short, as link with title "Link"'),
535
536
      'field types' => array('link'),
    ),
537
538
539
540
    'label' => array(
      'label' => t('Label, as link with label as title'),
      'field types' => array('link'),
    ),
541
542
543
544
545
  );
}

/**
 * Implementation of hook_field_formatter().
Nathan Haug's avatar
Nathan Haug committed
546
 */
547
function link_field_formatter($field, $item, $formatter, $node) {
548
549
550
  if (empty($item['url'])) {
    return '';
  }
551
552
553
554
555
  
  if ($formatter == 'plain') {
    return check_plain($item['url']);
  }
  
556
  $attributes = array();
557
  $item['attributes'] = unserialize($item['attributes']);
Nathan Haug's avatar
Nathan Haug committed
558
  // Add attributes defined at the widget level
559
560
561
  if (is_array($item['attributes'])) {
    foreach($item['attributes'] as $attribute => $attbvalue) {
      if (isset($item['attributes'][$attribute]) && $field['attributes'][$attribute] == 'user') {
562
563
        $attributes[$attribute] = $attbvalue;
      }
Nathan Haug's avatar
Nathan Haug committed
564
565
566
567
568
569
    }
  }
  // Add attributes defined at the field level
  if (is_array($field['attributes'])) {
    foreach($field['attributes'] as $attribute => $attbvalue) {
      if (!empty($attbvalue) && $attbvalue != 'default' && $attbvalue != 'user') {
570
        $attributes[$attribute] = $attbvalue;
Nathan Haug's avatar
Nathan Haug committed
571
572
573
      }
    }
  }
574
  
575
576
577
578
579
  // Replace URL tokens
  if (module_exist('token') && $field['enable_tokens']) {
    $item['url'] = token_replace($item['url'], 'node', $node);
  }
  
580
  $type = link_validate_url($item['url']);
581
  $url = link_cleanup_url($item['url']);
582
583
  
  // Seperate out the anchor if any
584
585
586
587
  if (strpos($url, '#') !== FALSE) {
    $fragment = substr($url, strpos($url, '#') + 1);
    $url = substr($url, 0, strpos($url, '#'));
  }
588
  // Seperate out the query string if any
589
590
591
592
593
  if (strpos($url, '?') !== FALSE) {
    $query = substr($url, strpos($url, '?') + 1);
    $url = substr($url, 0, strpos($url, '?'));
  }
  
594
595
  // Give the link the title 'Link'
  if ($formatter == 'short') {
596
    $output = l(t('Link'), $url, $attributes, $query, $fragment);
597
  }
598
599
600
601
  // Build the link using the widget label
  elseif ($formatter == 'label') {
    $output = l(t($field['widget']['label']), $url, $attributes, $query, $fragment);
  }
Nathan Haug's avatar
Nathan Haug committed
602
  // Build the link with a title
603
604
605
606
607
608
609
610
611
  elseif (strlen(trim($item['title'])) || ($field['title'] == 'value' && strlen(trim($field['title_value'])))) {
    // Use the title defined at the field level
    if ($field['title'] == 'value' && strlen(trim($field['title_value']))) {
      $title = $field['title_value'];
    }
    // Use the title defined by the user at the widget level
    else {
      $title = $item['title'];
    }
612
    // Replace tokens
613
614
    if (module_exist('token') && ($field['title'] == 'value' || $field['enable_tokens'])) {
      $title = token_replace($title, 'node', $node);
615
    }
616
    $output = l($title, $url, $attributes, $query, $fragment);
Nathan Haug's avatar
Nathan Haug committed
617
  }
618

619
  // Build the link with the URL or email address as the title (max 80 characters)
Nathan Haug's avatar
Nathan Haug committed
620
  else {
621
    $display_url = $type == LINK_EMAIL ? str_replace('mailto:', '', $url) : url($url, $query, $fragment, TRUE);
622
623
624
    if ($field['display']['url_cutoff'] && strlen($display_url) > $field['display']['url_cutoff']) {
      $display_url = substr($display_url, 0, $field['display']['url_cutoff']) . "...";
    }
625
    $output = l($display_url, $url, $attributes, $query, $fragment);
Nathan Haug's avatar
Nathan Haug committed
626
627
628
629
  }
  return $output;
}

630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
/**
 * Views module argument handler for link fields
 */
function link_views_argument_handler($op, &$query, $argtype, $arg = '') {
  if ($op == 'filter') {
    $field_name = substr($argtype['type'], 9, strrpos($argtype['type'], '_') - 9);
    $column = substr($argtype['type'], strrpos($argtype['type'], '_') + 1);
  }
  else {
    $field_name = substr($argtype, 9, strrpos($argtype, '_') - 9);
    $column = substr($argtype, strrpos($argtype, '_') + 1);
  }
  
  // Right now the only attribute we support in views in 'target', but
  // other attributes of the href tag could be added later
  if ($column == 'target') {
    $attribute = $column;
    $column = 'attributes';
  }
  
  $field = content_fields($field_name);
  $db_info = content_database_info($field);
  $main_column = $db_info['columns'][$column];

  // The table name used here is the Views alias for the table, not the actual
  // table name.
  $table = 'node_data_'. $field['field_name'];
  
  switch ($op) {
    case 'summary':
      $query->ensure_table($table);
      $query->add_field($main_column['column'], $table);
      return array('field' => $table .'.'. $main_column['column']);
      break;

    case 'filter':
      $query->ensure_table($table);
      if ($column == 'attributes') {
        // Because attributes are stored serialized, our only option is to also
        // serialize the data we're searching for and use LIKE to find similar data
        $query->add_where($table .'.'. $main_column['column'] ." LIKE '%%%s%'", serialize($attribute) . serialize($arg));
      }
      else {
        $query->add_where($table .'.'. $main_column['column'] ." = '%s'", $arg);
      }
      break;

    case 'link':
      $item = array();
      foreach ($db_info['columns'] as $column => $attributes) {
        $view_column_name = $attributes['column'];
        $item[$column] = $query->$view_column_name;
      }

      return l(content_format($field, $item, 'plain'), $arg .'/'. $query->$main_column['column'], array(), NULL, NULL, FALSE, TRUE);

    case 'sort':
      break;

    case 'title':
      $item = array(key($db_info['columns']) => $query);
      return content_format($field, $item);
      break;
  }
}

/**
 * Views modules filter handler for link protocol filtering
 */
function link_views_protocol_filter_handler($op, $filter, $filterinfo, &$query) {
  global $db_type;
  
  $protocols = $filter['value'];
  $field = $filterinfo['field'];
  // $table is not the real table name but the views alias
  $table = 'node_data_'. $filterinfo['content_field']['field_name'];

  foreach ($protocols as $protocol) {
    // Simple case, the URL begins with the specified protocol
    $condition = $table .'.'. $field .' LIKE \''. $protocol .'%\'';
    
    // More complex case, no protocol specified but is automatically cleaned up
    // by link_cleanup_url(). RegEx is required for this search operation.
    if ($protocol == 'http') {
      if ($db_type == 'pgsql') {
        // PostGreSQL code has NOT been tested. Please report any problems to the link issue queue.
        // pgSQL requires all slashes to be double escaped in regular expressions.
        // See http://www.postgresql.org/docs/8.1/static/functions-matching.html#FUNCTIONS-POSIX-REGEXP
        $condition .= ' OR '. $table .'.'. $field .' ~* \''. '^(([a-z0-9]([a-z0-9\\-_]*\\.)+)('. LINK_DOMAINS .'|[a-z][a-z]))' .'\''; 
      }
      else {
        // mySQL requires backslashes to be double (triple?) escaped within character classes.
        // See http://dev.mysql.com/doc/refman/5.0/en/string-comparison-functions.html#operator_regexp
        $condition .= ' OR '. $table .'.'. $field .' REGEXP \''. '^(([a-z0-9]([a-z0-9\\\-_]*\.)+)('. LINK_DOMAINS .'|[a-z][a-z]))' .'\''; 
      }
    }
    
    $where_conditions[] = $condition;
  }

  $query->ensure_table($table);
  $query->add_where(implode(' '. $filter['operator'] .' ', $where_conditions));
}

734
735
736
737
/**
 * Forms a valid URL if possible from an entered address.
 * Trims whitespace and automatically adds an http:// to addresses without a protocol specified
 *
738
739
 * @param string $url
 * @param string $protocol The protocol to be prepended to the url if one is not specified
740
 */
741
function link_cleanup_url($url, $protocol = "http") {
742
  $url = trim($url);
743
  $type = link_validate_url($url);
744
    
745
  if ($type == LINK_EXTERNAL) {
746
747
748
    // Check if there is no protocol specified
    $protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i",$url);
    if (empty($protocol_match)) {
749
      // But should there be? Add an automatic http:// if it starts with a domain name
750
      $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)('. LINK_DOMAINS .'|[a-z]{2}))/i',$url);
751
752
753
      if (!empty($domain_match)) {
        $url = $protocol."://".$url;
      }
754
755
756
    }
  }
  
757
  return $url;
758
759
760
}

/**
761
762
763
 * A lenient verification for URLs. Accepts all URLs following RFC 1738 standard
 * for URL formation and all email addresses following the RFC 2368 standard for
 * mailto address formation.
764
765
766
767
768
 *
 * @param string $text
 * @return mixed Returns boolean FALSE if the URL is not valid. On success, returns an object with
 * the following attributes: protocol, hostname, ip, and port.
 */
769
770
function link_validate_url($text) {
  
771
772
773
  $allowed_protocols = variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'));
  
  $protocol = '((' . implode("|", $allowed_protocols) . '):\/\/)';
774
  $authentication = '([a-z0-9]+(:[a-z0-9]+)?@)';
775
  $domain = '(([a-z0-9]([a-z0-9\-_\[\]]*\.)+)('. LINK_DOMAINS .'|[a-z]{2}))';
776
777
778
  $ipv4 = '([0-9]{1,3}(\.[0-9]{1,3}){3})'; 
  $ipv6 = '([0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7})'; 
  $port = '(:([0-9]{1,4}))';
779
  
780
  // Pattern specific to eternal links
781
  $external_pattern = '/^' . $protocol . '?'. $authentication . '?' . '(' . $domain . '|' . $ipv4 . '|' . $ipv6 . ' |localhost)' . $port . '?';
782
783
  
  // Pattern specific to internal links
784
  $internal_pattern = "/^([a-z0-9_\-+\[\]]+)";
785
  
786
787
788
  $directories = "(\/[a-z0-9_\-\.~+%=&,$'():;*@\[\]]*)*";
  $query = "(\/?\?[a-z0-9+_\-\.\/%=&,$'():;*@\[\]]*)";
  $anchor = "(#[a-z0-9_\-\.~+%=&,$'():;*@\[\]]*)";
789
790
791
  
  // the rest of the path for a standard URL
  $end = $directories . '?' . $query . '?' .  $anchor . '?' . '$/i';
792
  
793
  $user = '[a-zA-Z0-9_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+';
794
795
  $email_pattern = '/^mailto:' . $user . '@' . '(' . $domain . '|' . $ipv4 .'|'. $ipv6 . '|localhost)' . $query . '$/';
  
796
797
798
799
800
  if (preg_match($external_pattern . $end, $text)) {
    return LINK_EXTERNAL;
  }
  elseif (preg_match($internal_pattern . $end, $text)) {
    return LINK_INTERNAL;
Nathan Haug's avatar
Nathan Haug committed
801
  }
802
  elseif (in_array('mailto', $allowed_protocols) && preg_match($email_pattern, $text)) {
803
    return LINK_EMAIL;
804
805
  }
  elseif (strpos($text, '<front>') === 0) {
806
    return LINK_FRONT;
Nathan Haug's avatar
Nathan Haug committed
807
  }
808
  return FALSE;
809
}