#214194. Allow HTML in Title (using tokens).
[project/link.git] / link.module
1 <?php
2 // $Id$
3
4 /**
5 * @file
6 * Defines simple link field types.
7 */
8
9 define('LINK_EXTERNAL', 'external');
10 define('LINK_INTERNAL', 'internal');
11 define('LINK_FRONT', 'front');
12 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
15 /**
16 * Implementation of hook_field_info().
17 */
18 function link_field_info() {
19 return array(
20 'link' => array(
21 'label' => t('Link'),
22 'description' => t('Store a title, href, and attributes in the database to assemble a link.'),
23 ),
24 );
25 }
26
27 /**
28 * Implementation of hook_field_settings().
29 */
30 function link_field_settings($op, $field) {
31 switch ($op) {
32 case 'form':
33 $form = array(
34 '#theme' => 'link_field_settings',
35 );
36
37 $form['url'] = array(
38 '#type' => 'checkbox',
39 '#title' => t('Optional URL'),
40 '#default_value' => $field['url'],
41 '#return_value' => 'optional',
42 '#description' => t('If checked, the URL field is optional and submitting a title alone will be acceptable. If the URL is ommitted, the title will be displayed as plain text.'),
43 );
44
45 $title_options = array(
46 'optional' => t('Optional Title'),
47 'required' => t('Required Title'),
48 'value' => t('Static Title: '),
49 'none' => t('No Title'),
50 );
51
52 $form['title'] = array(
53 '#type' => 'radios',
54 '#title' => t('Link Title'),
55 '#default_value' => isset($field['title']) ? $field['title'] : 'optional',
56 '#options' => $title_options,
57 '#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.'),
58 );
59
60 $form['title_value'] = array(
61 '#type' => 'textfield',
62 '#default_value' => $field['title_value'],
63 '#size' => '46',
64 );
65
66 // Add token module replacements if available
67 if (module_exists('token')) {
68 $form['tokens'] = array(
69 '#type' => 'fieldset',
70 '#collapsible' => TRUE,
71 '#collapsed' => TRUE,
72 '#title' => t('Placeholder tokens'),
73 '#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."),
74 );
75 $form['tokens']['help'] = array(
76 '#value' => theme('token_help', 'node'),
77 );
78
79 $form['enable_tokens'] = array(
80 '#type' => 'checkbox',
81 '#title' => t('Allow user-entered tokens'),
82 '#default_value' => isset($field['enable_tokens']) ? $field['enable_tokens'] : 1,
83 '#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.'),
84 );
85 }
86
87 $form['display'] = array(
88 '#tree' => true,
89 );
90 $form['display']['url_cutoff'] = array(
91 '#type' => 'textfield',
92 '#title' => t('URL Display Cutoff'),
93 '#default_value' => isset($field['display']['url_cutoff']) ? $field['display']['url_cutoff'] : '80',
94 '#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.'),
95 '#maxlength' => 3,
96 '#size' => 3,
97 );
98
99 $target_options = array(
100 'default' => t('Default (no target attribute)'),
101 '_top' => t('Open link in window root'),
102 '_blank' => t('Open link in new window'),
103 'user' => t('Allow the user to choose'),
104 );
105 $form['attributes'] = array(
106 '#tree' => true,
107 );
108 $form['attributes']['target'] = array(
109 '#type' => 'radios',
110 '#title' => t('Link Target'),
111 '#default_value' => $field['attributes']['target'] ? $field['attributes']['target'] : 'default',
112 '#options' => $target_options,
113 );
114 $form['attributes']['rel'] = array(
115 '#type' => 'textfield',
116 '#title' => t('Rel Attribute'),
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.'),
118 '#default_value' => $field['attributes']['rel'] ? $field['attributes']['rel'] : '',
119 '#field_prefix' => 'rel = "',
120 '#field_suffix' => '"',
121 '#size' => 20,
122 );
123 $form['attributes']['class'] = array(
124 '#type' => 'textfield',
125 '#title' => t('Additional CSS Class'),
126 '#description' => t('When output, this link will have have this class attribute. Multiple classes should be seperated by spaces.'),
127 '#default_value' => isset($field['attributes']['class']) ? $field['attributes']['class'] : '',
128 );
129 return $form;
130
131 case 'validate':
132 if ($field['title'] == 'value' && empty($field['title_value'])) {
133 form_set_error('title_value', t('A default title must be provided if the title is a static value'));
134 }
135 break;
136
137 case 'save':
138 return array('attributes', 'display', 'url', 'title', 'title_value', 'enable_tokens');
139
140 case 'database columns':
141 return array(
142 'url' => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'sortable' => TRUE),
143 'title' => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'sortable' => TRUE),
144 'attributes' => array('type' => 'text', 'size' => 'medium', 'not null' => FALSE),
145 );
146
147 case 'views data':
148 if (!empty($field)) {
149 $data = link_views_data($field);
150 }
151 return isset($data) ? $data : NULL;
152 }
153 }
154
155 /**
156 * Theme the settings form for the link field.
157 */
158 function theme_link_field_settings($form) {
159 $title_value = drupal_render($form['title_value']);
160 $title_checkbox = drupal_render($form['title']['value']);
161
162 // Set Static Title radio option to include the title_value textfield.
163 $form['title']['value'] = array('#value' => '<div class="container-inline">'. $title_checkbox . $title_value .'</div>');
164
165 // Reprint the title radio options with the included textfield.
166 return drupal_render($form);
167 }
168
169 /**
170 * Implementation of hook_content_is_empty().
171 */
172 function link_content_is_empty($item, $field) {
173 if (empty($item['title']) && empty($item['url'])) {
174 return TRUE;
175 }
176 return FALSE;
177 }
178
179 /**
180 * Implementation of hook_field().
181 */
182 function link_field($op, &$node, $field, &$items, $teaser, $page) {
183 switch ($op) {
184 case 'load':
185 foreach ($items as $delta => $item) {
186 _link_load($items[$delta], $delta);
187 }
188 return $items;
189 break;
190
191 case 'validate':
192 $optional_field_found = FALSE;
193 foreach($items as $delta => $value) {
194 _link_validate($items[$delta],$delta, $field, $node, $optional_field_found);
195 }
196
197 if ($field['url'] == 'optional' && $field['title'] == 'optional' && $field['required'] && !$optional_field_found) {
198 form_set_error($field['field_name'] .'][0][title', t('At least one title or URL must be entered.'));
199 }
200 break;
201
202 case 'process form values':
203 foreach($items as $delta => $value) {
204 _link_process($items[$delta],$delta, $field, $node);
205 }
206 break;
207
208 case 'sanitize':
209 foreach ($items as $delta => $value) {
210 _link_sanitize($items[$delta], $delta, $field, $node);
211 }
212 break;
213 }
214 }
215
216 /**
217 * Implementation of hook_widget_info().
218 */
219 function link_widget_info() {
220 return array(
221 'link' => array(
222 'label' => 'Text Fields for Title and URL',
223 'field types' => array('link'),
224 'multiple values' => CONTENT_HANDLE_CORE,
225 ),
226 );
227 }
228
229 /**
230 * Implementation of hook_widget().
231 */
232 function link_widget(&$form, &$form_state, $field, $items, $delta = 0) {
233 $element = array(
234 '#type' => $field['widget']['type'],
235 '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
236 '#title' => $field['widget']['label'],
237 '#weight' => $field['widget']['weight'],
238 '#description' => $field['widget']['description'],
239 '#required' => $field['required'],
240 '#field' => $field,
241 );
242 return $element;
243 }
244
245 function _link_load(&$item, $delta = 0) {
246 // Unserialize the attributes array.
247 $item['attributes'] = unserialize($item['attributes']);
248 }
249
250 function _link_process(&$item, $delta = 0, $field, $node) {
251 // Remove the target attribute if not selected.
252 if (!$item['attributes']['target'] || $item['attributes']['target'] == "default") {
253 unset($item['attributes']['target']);
254 }
255 // Trim whitespace from URL.
256 $item['url'] = trim($item['url']);
257 // Serialize the attributes array.
258 $item['attributes'] = serialize($item['attributes']);
259
260 // Don't save an invalid default value (e.g. 'http://').
261 if ((isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url']) && is_object($node)) {
262 if (!link_validate_url($item['url'])) {
263 unset($item['url']);
264 }
265 }
266 }
267
268 function _link_validate(&$item, $delta, $field, $node, &$optional_field_found) {
269 if ($item['url'] && !(isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url'] && !$field['required'])) {
270 // Validate the link.
271 if (link_validate_url(trim($item['url'])) == FALSE) {
272 form_set_error($field['field_name'] .']['. $delta. '][url', t('Not a valid URL.'));
273 }
274 // Require a title for the link if necessary.
275 if ($field['title'] == 'required' && strlen(trim($item['title'])) == 0) {
276 form_set_error($field['field_name'] .']['. $delta. '][title', t('Titles are required for all links.'));
277 }
278 }
279 // Require a link if we have a title.
280 if ($field['url'] !== 'optional' && strlen($item['title']) > 0 && strlen(trim($item['url'])) == 0) {
281 form_set_error($field['field_name'] .']['. $delta. '][url', t('You cannot enter a title without a link url.'));
282 }
283 // In a totally bizzaro case, where URLs and titles are optional but the field is required, ensure there is at least one link.
284 if ($field['url'] == 'optional' && $field['title'] == 'optional' && (strlen(trim($item['url'])) != 0 || strlen(trim($item['title'])) != 0)) {
285 $optional_field_found = TRUE;
286 }
287 }
288
289 /**
290 * Cleanup user-entered values for a link field according to field settings.
291 *
292 * @param $item
293 * A single link item, usually containing url, title, and attributes.
294 * @param $delta
295 * The delta value if this field is one of multiple fields.
296 * @param $field
297 * The CCK field definition.
298 * @param $node
299 * The node containing this link.
300 */
301 function _link_sanitize(&$item, $delta, &$field, &$node) {
302 // Don't try to process empty links.
303 if (empty($item['url']) && empty($item['title'])) {
304 return;
305 }
306
307 // Replace URL tokens.
308 if (module_exists('token') && $field['enable_tokens']) {
309 $token_node = node_load($node->nid); // Necessary for nodes in views.
310 $item['url'] = token_replace($item['url'], 'node', $token_node);
311 }
312
313 $type = link_validate_url($item['url']);
314 $url = link_cleanup_url($item['url']);
315
316 // Seperate out the anchor if any.
317 if (strpos($url, '#') !== FALSE) {
318 $item['fragment'] = substr($url, strpos($url, '#') + 1);
319 $url = substr($url, 0, strpos($url, '#'));
320 }
321 // Seperate out the query string if any.
322 if (strpos($url, '?') !== FALSE) {
323 $item['query'] = substr($url, strpos($url, '?') + 1);
324 $url = substr($url, 0, strpos($url, '?'));
325 }
326 // Save the new URL without the anchor or query.
327 $item['url'] = $url;
328
329 // Create a shortened URL for display.
330 $display_url = $type == LINK_EMAIL ? str_replace('mailto:', '', $url) : url($url, array('query' => isset($item['query']) ? $item['query'] : NULL, 'fragment' => isset($item['fragment']) ? $item['fragment'] : NULL, 'absolute' => TRUE));
331 if ($field['display']['url_cutoff'] && strlen($display_url) > $field['display']['url_cutoff']) {
332 $display_url = substr($display_url, 0, $field['display']['url_cutoff']) . "...";
333 }
334 $item['display_url'] = $display_url;
335
336 // Use the title defined at the field level.
337 if ($field['title'] == 'value' && strlen(trim($field['title_value']))) {
338 $title = $field['title_value'];
339 }
340 // Use the title defined by the user at the widget level.
341 else {
342 $title = $item['title'];
343 }
344 // Replace tokens.
345 if (module_exists('token') && ($field['title'] == 'value' || $field['enable_tokens'])) {
346 $token_node = node_load($node->nid); // Necessary for nodes in views.
347 $title = filter_xss(token_replace($title, 'node', $token_node), array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u'));
348 $item['html'] = TRUE;
349 }
350 $item['display_title'] = empty($title) ? $item['display_url'] : $title;
351
352 // Add attributes defined at the widget level
353 $attributes = array();
354 if (is_array($item['attributes'])) {
355 foreach($item['attributes'] as $attribute => $attbvalue) {
356 if (isset($item['attributes'][$attribute]) && $field['attributes'][$attribute] == 'user') {
357 $attributes[$attribute] = $attbvalue;
358 }
359 }
360 }
361 // Add attributes defined at the field level
362 if (is_array($field['attributes'])) {
363 foreach($field['attributes'] as $attribute => $attbvalue) {
364 if (!empty($attbvalue) && $attbvalue != 'default' && $attbvalue != 'user') {
365 $attributes[$attribute] = $attbvalue;
366 }
367 }
368 }
369 // Remove the rel=nofollow for internal links.
370 if ($type != LINK_EXTERNAL && isset($attributes['rel']) && strpos($attributes['rel'], 'nofollow') !== FALSE) {
371 $attributes['rel'] = str_replace('nofollow', '', $attributes['rel']);
372 if (empty($attributes['rel'])) {
373 unset($attributes['rel']);
374 }
375 }
376 $item['attributes'] = $attributes;
377
378 // Add the widget label.
379 $item['label'] = $field['widget']['label'];
380 }
381
382 /**
383 * Implementation of hook_theme().
384 */
385 function link_theme() {
386 return array(
387 'link_field_settings' => array(
388 'arguments' => array('element' => NULL),
389 ),
390 'link_formatter_default' => array(
391 'arguments' => array('element' => NULL),
392 ),
393 'link_formatter_plain' => array(
394 'arguments' => array('element' => NULL),
395 ),
396 'link_formatter_url' => array(
397 'arguments' => array('element' => NULL),
398 ),
399 'link_formatter_short' => array(
400 'arguments' => array('element' => NULL),
401 ),
402 'link_formatter_label' => array(
403 'arguments' => array('element' => NULL),
404 ),
405 'link_formatter_separate' => array(
406 'arguments' => array('element' => NULL),
407 ),
408 'link' => array(
409 'arguments' => array('element' => NULL),
410 ),
411 );
412 }
413
414 /**
415 * FAPI theme for an individual text elements.
416 */
417 function theme_link($element) {
418 drupal_add_css(drupal_get_path('module', 'link') .'/link.css');
419
420 // Prefix single value link fields with the name of the field.
421 if (empty($element['#field']['multiple'])) {
422 if (isset($element['url']) && isset($element['title'])) {
423 $element['url']['#title'] = $element['#title'] .' '. $element['url']['#title'];
424 $element['title']['#title'] = $element['#title'] .' '. $element['title']['#title'];
425 }
426 elseif($element['url']) {
427 $element['url']['#title'] = $element['#title'];
428 }
429 }
430
431 $output = '';
432 $output .= '<div class="link-field-subrow clear-block">';
433 if (isset($element['title'])) {
434 $output .= '<div class="link-field-title link-field-column">' . theme('textfield', $element['title']) . '</div>';
435 }
436 $output .= '<div class="link-field-url' . (isset($element['title']) ? ' link-field-column' : '') . '">' . theme('textfield', $element['url']) . '</div>';
437 $output .= '</div>';
438 if (!empty($element['attributes'])) {
439 $output .= '<div class="link-attributes">' . theme('form_element', $element['attributes'], $element['attributes']['#value']) . '</div>';
440 }
441 return $output;
442 }
443
444 /**
445 * Implementation of hook_elements().
446 */
447 function link_elements() {
448 $elements = array();
449 $elements['link'] = array(
450 '#input' => TRUE,
451 '#columns' => array('url', 'title'),
452 '#process' => array('link_process'),
453 );
454 return $elements;
455 }
456
457 /**
458 * Process the link type element before displaying the field.
459 *
460 * Build the form element. When creating a form using FAPI #process,
461 * note that $element['#value'] is already set.
462 *
463 * The $fields array is in $form['#field_info'][$element['#field_name']].
464 */
465 function link_process($element, $edit, $form_state, $form) {
466 $field = $form['#field_info'][$element['#field_name']];
467 $delta = $element['#delta'];
468 $element['url'] = array(
469 '#type' => 'textfield',
470 '#maxlength' => '255',
471 '#title' => t('URL'),
472 '#description' => $element['#description'],
473 '#required' => ($delta == 0 && $field['url'] !== 'optional') ? $element['#required'] : FALSE,
474 '#default_value' => isset($element['#value']['url']) ? $element['#value']['url'] : NULL,
475 );
476 if ($field['title'] != 'none' && $field['title'] != 'value') {
477 $element['title'] = array(
478 '#type' => 'textfield',
479 '#maxlength' => '255',
480 '#title' => t('Title'),
481 '#required' => ($delta == 0 && $field['title'] == 'required') ? $field['required'] : FALSE,
482 '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
483 );
484 }
485 if ($field['attributes']['target'] == 'user') {
486 $element['attributes']['target'] = array(
487 '#type' => 'checkbox',
488 '#title' => t('Open URL in a New Window'),
489 '#return_value' => "_blank",
490 );
491 }
492 return $element;
493 }
494
495 /**
496 * Implementation of hook_field_formatter_info().
497 */
498 function link_field_formatter_info() {
499 return array(
500 'default' => array(
501 'label' => t('Title, as link (default)'),
502 'field types' => array('link'),
503 'multiple values' => CONTENT_HANDLE_CORE,
504 ),
505 'url' => array(
506 'label' => t('URL, as link'),
507 'field types' => array('link'),
508 'multiple values' => CONTENT_HANDLE_CORE,
509 ),
510 'plain' => array(
511 'label' => t('URL, as plain text'),
512 'field types' => array('link'),
513 'multiple values' => CONTENT_HANDLE_CORE,
514 ),
515 'short' => array(
516 'label' => t('Short, as link with title "Link"'),
517 'field types' => array('link'),
518 'multiple values' => CONTENT_HANDLE_CORE,
519 ),
520 'label' => array(
521 'label' => t('Label, as link with label as title'),
522 'field types' => array('link'),
523 'multiple values' => CONTENT_HANDLE_CORE,
524 ),
525 'separate' => array(
526 'label' => t('Separate title and URL'),
527 'field types' => array('link'),
528 'multiple values' => CONTENT_HANDLE_CORE,
529 ),
530 );
531 }
532
533 /**
534 * Theme function for 'default' text field formatter.
535 */
536 function theme_link_formatter_default($element) {
537 // Display a normal link if both title and URL are available.
538 if (!empty($element['#item']['display_title']) && !empty($element['#item']['url'])) {
539 return l($element['#item']['display_title'], $element['#item']['url'], $element['#item']);
540 }
541 // If only a title, display the title.
542 elseif (!empty($element['#item']['display_title'])) {
543 return check_plain($element['#item']['display_title']);
544 }
545 }
546
547 /**
548 * Theme function for 'plain' text field formatter.
549 */
550 function theme_link_formatter_plain($element) {
551 return empty($element['#item']['url']) ? check_plain($element['#item']['title']) : check_plain($element['#item']['url']);
552 }
553
554 /**
555 * Theme function for 'url' text field formatter.
556 */
557 function theme_link_formatter_url($element) {
558 return $element['#item']['url'] ? l($element['#item']['display_url'], $element['#item']['url'], $element['#item']) : '';
559 }
560
561 /**
562 * Theme function for 'short' text field formatter.
563 */
564 function theme_link_formatter_short($element) {
565 return $element['#item']['url'] ? l(t('Link'), $element['#item']['url'], $element['#item']) : '';
566 }
567
568 /**
569 * Theme function for 'label' text field formatter.
570 */
571 function theme_link_formatter_label($element) {
572 return $element['#item']['url'] ? l($element['#item']['label'], $element['#item']['url'], $element['#item']) : '';
573 }
574
575 /**
576 * Theme function for 'separate' text field formatter.
577 */
578 function theme_link_formatter_separate($element) {
579 $output = '';
580 $output .= '<div class="link-item">';
581 $output .= '<div class="link-title">'. $element['#item']['display_title'] .'</div>';
582 $output .= '<div class="link-url">'. l($element['#item']['display_url'], $element['#item']['url'], $element['#item']) .'</div>';
583 $output .= '</div>';
584 return $output;
585 }
586
587 function link_token_list($type = 'all') {
588 if ($type == 'field' || $type == 'all') {
589 $tokens = array();
590
591 $tokens['link']['url'] = t("Link URL");
592 $tokens['link']['title'] = t("Link title");
593 $tokens['link']['view'] = t("Formatted html link");
594
595 return $tokens;
596 }
597 }
598
599 function link_token_values($type, $object = NULL) {
600 if ($type == 'field') {
601 $item = $object[0];
602
603 $tokens['url'] = $item['url'];
604 $tokens['title'] = $item['title'];
605 $tokens['view'] = $item['view'];
606
607 return $tokens;
608 }
609 }
610
611 /**
612 * Forms a valid URL if possible from an entered address.
613 * Trims whitespace and automatically adds an http:// to addresses without a protocol specified
614 *
615 * @param string $url
616 * @param string $protocol The protocol to be prepended to the url if one is not specified
617 */
618 function link_cleanup_url($url, $protocol = "http") {
619 $url = trim($url);
620 $type = link_validate_url($url);
621
622 if ($type == LINK_EXTERNAL) {
623 // Check if there is no protocol specified.
624 $protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i",$url);
625 if (empty($protocol_match)) {
626 // But should there be? Add an automatic http:// if it starts with a domain name.
627 $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)('. LINK_DOMAINS .'|[a-z]{2}))/i',$url);
628 if (!empty($domain_match)) {
629 $url = $protocol."://".$url;
630 }
631 }
632 }
633
634 return $url;
635 }
636
637 /**
638 * A lenient verification for URLs. Accepts all URLs following RFC 1738 standard
639 * for URL formation and all email addresses following the RFC 2368 standard for
640 * mailto address formation.
641 *
642 * @param string $text
643 * @return mixed Returns boolean FALSE if the URL is not valid. On success, returns an object with
644 * the following attributes: protocol, hostname, ip, and port.
645 */
646 function link_validate_url($text) {
647
648 $allowed_protocols = variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'));
649
650 $protocol = '((' . implode("|", $allowed_protocols) . '):\/\/)';
651 $authentication = '([a-z0-9]+(:[a-z0-9]+)?@)';
652 $domain = '((([a-z0-9]([a-z0-9\-_\[\]]*\.))+)('. LINK_DOMAINS .'|[a-z]{2}))';
653 $ipv4 = '([0-9]{1,3}(\.[0-9]{1,3}){3})';
654 $ipv6 = '([0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7})';
655 $port = '(:([0-9]{1,5}))';
656
657 // Pattern specific to eternal links.
658 $external_pattern = '/^' . $protocol . '?'. $authentication . '?' . '(' . $domain . '|' . $ipv4 . '|' . $ipv6 . ' |localhost)' . $port . '?';
659
660 // Pattern specific to internal links.
661 $internal_pattern = "/^([a-z0-9_\-+\[\]]+)";
662
663 $directories = "(\/[a-z0-9_\-\.~+%=&,$'():;*@\[\]]*)*";
664 $query = "(\/?\?([?a-z0-9+_\-\.\/%=&,$'():;*@\[\]]*))";
665 $anchor = "(#[a-z0-9_\-\.~+%=&,$'():;*@\[\]]*)";
666
667 // The rest of the path for a standard URL.
668 $end = $directories . '?' . $query . '?' . $anchor . '?' . '$/i';
669
670 $user = '[a-zA-Z0-9_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+';
671 $email_pattern = '/^mailto:' . $user . '@' . '(' . $domain . '|' . $ipv4 .'|'. $ipv6 . '|localhost)' . $query . '$/';
672
673 if (preg_match($external_pattern . $end, $text)) {
674 return LINK_EXTERNAL;
675 }
676 elseif (preg_match($internal_pattern . $end, $text)) {
677 return LINK_INTERNAL;
678 }
679 elseif (in_array('mailto', $allowed_protocols) && preg_match($email_pattern, $text)) {
680 return LINK_EMAIL;
681 }
682 elseif (strpos($text, '<front>') === 0) {
683 return LINK_FRONT;
684 }
685 return FALSE;
686 }