6 * Defines simple link field types.
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|local');
16 * Implementation of hook_menu().
18 function link_menu($may_cache) {
22 'path' => 'link/widget/js',
23 'callback' => 'link_widget_js',
24 'access' => user_access('access content'),
25 'type' => MENU_CALLBACK
,
32 * Implementation of hook_field_info().
34 function link_field_info() {
36 'link' => array('label' => 'Link'),
41 * Implementation of hook_field_settings().
43 function link_field_settings($op, $field) {
47 '#theme' => 'link_field_settings',
51 '#type' => 'checkbox',
52 '#title' => t('Optional URL'),
53 '#default_value' => $field['url'],
54 '#return_value' => 'optional',
55 '#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.'),
58 $title_options = array(
59 'optional' => t('Optional Title'),
60 'required' => t('Required Title'),
61 'value' => t('Static Title: '),
62 'none' => t('No Title'),
65 $form['title'] = array(
67 '#title' => t('Link Title'),
68 '#default_value' => isset($field['title']) ?
$field['title'] : 'optional',
69 '#options' => $title_options,
70 '#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.'),
73 $form['title_value'] = array(
74 '#type' => 'textfield',
75 '#default_value' => $field['title_value'],
79 // Add token module replacements if available
80 if (module_exists('token')) {
81 $form['tokens'] = array(
82 '#type' => 'fieldset',
83 '#collapsible' => TRUE
,
85 '#title' => t('Placeholder tokens'),
86 '#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."),
88 $form['tokens']['help'] = array(
89 '#value' => theme('token_help', 'node'),
92 $form['enable_tokens'] = array(
93 '#type' => 'checkbox',
94 '#title' => t('Allow user-entered tokens'),
95 '#default_value' => isset($field['enable_tokens']) ?
$field['enable_tokens'] : 1,
96 '#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.'),
100 $form['display'] = array(
103 $form['display']['url_cutoff'] = array(
104 '#type' => 'textfield',
105 '#title' => t('URL Display Cutoff'),
106 '#default_value' => isset($field['display']['url_cutoff']) ?
$field['display']['url_cutoff'] : '80',
107 '#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 (…)? Leave blank for no limit.'),
112 $target_options = array(
113 'default' => t('Default (no target attribute)'),
114 '_top' => t('Open link in window root'),
115 '_blank' => t('Open link in new window'),
116 'user' => t('Allow the user to choose'),
118 $form['attributes'] = array(
121 $form['attributes']['target'] = array(
123 '#title' => t('Link Target'),
124 '#default_value' => empty($field['attributes']['target']) ?
'default' : $field['attributes']['target'],
125 '#options' => $target_options,
127 $form['attributes']['rel'] = array(
128 '#type' => 'textfield',
129 '#title' => t('Rel Attribute'),
130 '#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="nofollow"</a> which prevents some search engines from spidering entered links.'),
131 '#default_value' => empty($field['attributes']['rel']) ?
'' : $field['attributes']['rel'],
132 '#field_prefix' => 'rel = "',
133 '#field_suffix' => '"',
136 $form['attributes']['class'] = array(
137 '#type' => 'textfield',
138 '#title' => t('Additional CSS Class'),
139 '#description' => t('When output, this link will have have this class attribute. Multiple classes should be separated by spaces.'),
140 '#default_value' => empty($field['attributes']['class']) ?
'' : $field['attributes']['class'],
145 if ($field['title'] == 'value' && empty($field['title_value'])) {
146 form_set_error('title_value', t('A default title must be provided if the title is a static value'));
151 return array('attributes', 'display', 'url', 'title', 'title_value', 'enable_tokens');
153 case
'database columns':
155 'url' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE
, 'default' => "''", 'sortable' => TRUE
),
156 'title' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE
, 'default' => "''", 'sortable' => TRUE
),
157 'attributes' => array('type' => 'mediumtext', 'not null' => FALSE
),
164 'operator' => 'views_handler_operator_like',
165 'handler' => 'views_handler_operator_like',
168 'name' => t('Title'),
169 'operator' => 'views_handler_operator_like',
170 'handler' => 'views_handler_operator_like',
173 'name' => t('Protocol'),
174 'list' => drupal_map_assoc(variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'))),
175 'operator' => 'views_handler_operator_or',
176 'handler' => 'link_views_protocol_filter_handler',
182 'content: '.
$field['field_name'] .
'_title' => array(
183 'name' => t('Link Title') .
': '.
t($field['widget']['label']) .
' ('.
$field['field_name'] .
')',
184 'handler' => 'link_views_argument_handler',
186 'content: '.
$field['field_name'] .
'_target' => array(
187 'name' => t('Link Target') .
': '.
t($field['widget']['label']) .
' ('.
$field['field_name'] .
')',
188 'handler' => 'link_views_argument_handler',
196 * Theme the settings form for the link field.
198 function theme_link_field_settings($form) {
199 $title_value = drupal_render($form['title_value']);
200 $title_checkbox = drupal_render($form['title']['value']);
202 // Set Static Title radio option to include the title_value textfield.
203 $form['title']['value'] = array('#value' => '<div class="container-inline">'.
$title_checkbox .
$title_value .
'</div>');
205 // Reprint the title radio options with the included textfield.
206 return drupal_render($form);
210 * Implementation of hook_field().
212 function link_field($op, &$node, $field, &$items, $teaser, $page) {
215 foreach ($items as
$delta => $item) {
216 $items[$delta]['attributes'] = unserialize($item['attributes']);
220 foreach ($items as
$delta => $item) {
221 $items[$delta]['view'] = content_format($field, $items[$delta], 'default', $node);
223 return theme('field', $node, $field, $items, $teaser, $page);
229 * Implementation of hook_widget_info().
231 function link_widget_info() {
234 'label' => 'Text Fields for Title and URL',
235 'field types' => array('link'),
241 * Implementation of hook_widget().
243 function link_widget($op, &$node, $field, &$items) {
245 case
'prepare form values':
246 foreach($items as
$delta => $value) {
247 if (is_numeric($delta)) {
248 _link_widget_prepare($items[$delta], $delta);
251 if ($_POST[$field['field_name']]) {
252 $items = $_POST[$field['field_name']];
253 unset($items['count'], $items['more-url'], $items['more']);
258 $form[$field['field_name']] = array(
260 '#theme' => 'link_widget_form',
261 '#type' => $field['multiple'] ?
'fieldset' : 'markup',
262 '#collapsible' => TRUE
,
263 '#collapsed' => FALSE
,
264 '#title' => t($field['widget']['label']),
265 '#description' => $field['widget']['description'],
268 // Add token module replacements if available.
269 if (module_exists('token') && $field['enable_tokens']) {
270 $tokens_form = array(
271 '#type' => 'fieldset',
272 '#collapsible' => TRUE
,
273 '#collapsed' => TRUE
,
274 '#title' => t('Placeholder tokens'),
275 '#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."),
278 $tokens_form['help'] = array(
279 '#value' => theme('token_help', 'node'),
283 if ($field['multiple']) {
284 drupal_add_js(drupal_get_path('module', 'link') .
'/link.js');
287 // Render link fields for all the entered values.
288 foreach ($items as
$data) {
289 if (is_array($data) && ($data['url'] || $data['title'])) {
290 _link_widget_form($form[$field['field_name']][$delta], $field, $data, $delta);
294 // Render two additional new link fields.
295 foreach (range($delta, $delta + 1) as
$delta) {
296 _link_widget_form($form[$field['field_name']][$delta], $field, array(), $delta);
299 // Create a wrapper for additional fields.
300 $form[$field['field_name']]['wrapper'] = array(
302 '#value' => '<div id="' .
str_replace('_', '-', $field['field_name']) .
'-wrapper" class="clear-block"></div>',
305 // Add 'More' Javascript Callback.
306 $form[$field['field_name']]['more-url'] = array(
308 '#value' => url('link/widget/js/' .
$field['type_name'] .
'/' .
$field['field_name'], NULL
, NULL
, TRUE
),
309 '#attributes' => array('class' => 'more-links'),
310 '#id' => str_replace('_', '-', $field['field_name']) .
'-more-url',
313 // Add Current Field Count.
314 $form[$field['field_name']]['count'] = array(
317 '#id' => str_replace('_', '-', $field['field_name']) .
'-count',
321 $form[$field['field_name']]['more'] = array(
323 '#value' => t('More Links'),
325 '#id' => str_replace('_', '-', $field['field_name']) .
'-more',
328 if (isset($tokens_form)) {
329 $form[$field['field_name']]['tokens'] = $tokens_form;
331 } // end if multiple.
333 _link_widget_form($form[$field['field_name']][0], $field, $items[0]);
334 if (isset($tokens_form)) {
335 $form[$field['field_name']][0]['tokens'] = $tokens_form;
341 if (!is_object($node)) return;
342 unset($items['count']);
343 unset($items['more-url']);
344 unset($items['more']);
345 $optional_field_found = FALSE
;
346 foreach($items as
$delta => $value) {
347 _link_widget_validate($items[$delta],$delta, $field, $node, $optional_field_found);
350 if ($field['url'] == 'optional' && $field['title'] == 'optional' && $field['required'] && !$optional_field_found) {
351 form_set_error($field['field_name'] .
'][0][title', t('At least one title or URL must be entered.'));
355 case
'process form values':
356 // Remove the JS helper fields.
357 unset($items['more-url']);
358 unset($items['count']);
359 unset($items['more']);
360 foreach($items as
$delta => $value) {
361 if (is_numeric($delta)) {
362 _link_widget_process($items[$delta],$delta, $field, $node);
368 // Don't save empty fields (beyond the first one).
369 $save_field = array();
370 unset($items['more-url']);
371 unset($items['count']);
372 unset($items['more']);
373 foreach ($items as
$delta => $value) {
374 if ($value['url'] !== 'optional' || $delta == 0) {
375 $save_items[] = $items[$delta];
378 $items = $save_items;
384 * Helper function renders the link widget in both single and multiple value cases.
387 function _link_widget_form(&$form_item, $field, $item, $delta = 0) {
390 '#theme' => 'link_widget_form_row',
394 if (isset($field['widget']['default_value'][$delta]['url'])) {
395 $default_url = $field['widget']['default_value'][$delta]['url'];
398 $form_item['url'] = array(
399 '#type' => 'textfield',
400 '#maxlength' => '255',
401 '#title' => $delta == 0 ?
t('URL') : NULL
,
402 '#default_value' => ($item['url']) ?
$item['url'] : $default_url,
403 '#required' => ($delta == 0) ?
($field['required'] && empty($field['url'])) : FALSE
,
405 if ($field['title'] != 'value' && $field['title'] != 'none') {
407 if (isset($field['widget']['default_value'][$delta]['title'])) {
408 $default_title = $field['widget']['default_value'][$delta]['title'];
410 $form_item['title'] = array(
411 '#type' => 'textfield',
412 '#maxlength' => '255',
413 '#title' => $delta == 0 ?
t('Title') : NULL
,
414 '#default_value' => ($item['title']) ?
$item['title'] : $default_title,
415 '#required' => ($delta == 0 && $field['title'] == 'required') ?
$field['required'] : FALSE
,
418 if (!empty($field['attributes']['target']) && $field['attributes']['target'] == 'user') {
419 $form_item['attributes']['target'] = array(
420 '#type' => 'checkbox',
421 '#title' => t('Open URL in a New Window'),
422 '#default_value' => $item['attributes']['target'],
423 '#return_value' => "_blank",
428 function _link_widget_prepare(&$item, $delta = 0) {
429 // Unserialize the attributes array.
430 $item['attributes'] = unserialize($item['attributes']);
433 function _link_widget_process(&$item, $delta = 0, $field, $node) {
434 // Remove the target attribute if not selected.
435 if (!$item['attributes']['target'] || $item['attributes']['target'] == "default") {
436 unset($item['attributes']['target']);
438 // Trim whitespace from URL.
439 $item['url'] = trim($item['url']);
440 // Serialize the attributes array.
441 $item['attributes'] = serialize($item['attributes']);
443 // Don't save an invalid default value (e.g. 'http://').
444 if ((isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url']) && is_object($node)) {
445 if (!link_validate_url($item['url'])) {
451 function _link_widget_validate(&$item, $delta, $field, $node, &$optional_field_found) {
452 if ($item['url'] && !(isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url'] && !$field['required'])) {
453 // Validate the link.
454 if (link_validate_url(trim($item['url'])) == FALSE
) {
455 form_set_error($field['field_name'] .
']['.
$delta.
'][url', t('Not a valid URL.'));
457 // Require a title for the link if necessary.
458 if ($field['title'] == 'required' && strlen(trim($item['title'])) == 0) {
459 form_set_error($field['field_name'] .
']['.
$delta.
'][title', t('Titles are required for all links.'));
462 // Require a link if we have a title.
463 if ($field['url'] !== 'optional' && strlen($item['title']) > 0 && strlen(trim($item['url'])) == 0) {
464 form_set_error($field['field_name'] .
']['.
$delta.
'][url', t('You cannot enter a title without a link url.'));
466 // In a totally bizzaro case, where URLs and titles are optional but the field is required, ensure there is at least one link.
467 if ($field['url'] == 'optional' && $field['title'] == 'optional' && (strlen(trim($item['url'])) != 0 || strlen(trim($item['title'])) != 0)) {
468 $optional_field_found = TRUE
;
472 function link_widget_js($type_name, $field_name) {
473 $field = content_fields($field_name, $type_name);
474 $type = content_types($type);
475 $delta = $_POST[$field_name]['count'];
477 $node_field = array();
479 _link_widget_form($form, $field, $node_field, $delta);
481 // Assign parents matching the original form.
482 foreach (element_children($form) as
$key) {
483 $form[$key]['#parents'] = array($field_name, $delta, $key);
486 // Add names, ids, and other form properties.
487 foreach (module_implements('form_alter') as
$module) {
488 $function = $module .
'_form_alter';
489 $function('link_widget_js', $form);
491 $form = form_builder('link_widget_js', $form);
493 $output = drupal_render($form);
495 print drupal_to_js(array('status' => TRUE
, 'data' => $output));
500 * Theme the display of the entire link set
502 function theme_link_widget_form($element) {
503 drupal_add_css(drupal_get_path('module', 'link') .
'/link.css');
504 // Check for multiple (output normally).
505 if (isset($element[1])) {
506 $output = drupal_render($element);
508 // Add the field label to the 'Title' and 'URL' labels.
510 if (isset($element[0]['title'])) {
511 $element[0]['title']['#title'] = $element['#title'] .
' ' .
$element[0]['title']['#title'];
512 $element[0]['title']['#description'] = $element['#description'];
513 $element[0]['url']['#title'] = $element['#title'] .
' ' .
$element[0]['url']['#title'];
516 $element[0]['url']['#title'] = $element['#title'];
517 $element[0]['url']['#description'] = $element['#description'];
519 $output = drupal_render($element);
526 * Theme the display of a single form row
528 function theme_link_widget_form_row($element) {
530 $output .
= '<div class="link-field-row clear-block"><div class="link-field-subrow clear-block">';
531 if ($element['title']) {
532 $output .
= '<div class="link-field-title link-field-column">' .
drupal_render($element['title']) .
'</div>';
534 $output .
= '<div class="link-field-url' .
($element['title'] ?
' link-field-column' : '') .
'">' .
drupal_render($element['url']) .
'</div>';
536 if ($element['attributes']) {
537 $output .
= '<div class="link-attributes">' .
drupal_render($element['attributes']) .
'</div>';
539 $output .
= drupal_render($element);
545 * Implementation of hook_field_formatter_info().
547 function link_field_formatter_info() {
550 'label' => t('Title, as link (default)'),
551 'field types' => array('link'),
554 'label' => t('URL, as link'),
555 'field types' => array('link'),
558 'label' => t('URL, plain text'),
559 'field types' => array('link'),
562 'label' => t('Short, as link with title "Link"'),
563 'field types' => array('link'),
566 'label' => t('Label, as link with label as title'),
567 'field types' => array('link'),
570 'label' => t('Separate title and URL'),
571 'field types' => array('link'),
577 * Implementation of hook_field_formatter().
579 function link_field_formatter($field, $item, $formatter, $node) {
580 if (empty($item['url']) && ($field['url'] != 'optional' || empty($item['title']))) {
584 if ($formatter == 'plain') {
585 return empty($item['url']) ?
check_plain($item['title']) : check_plain(link_cleanup_url($item['url']));
588 // Replace URL tokens.
589 if (module_exists('token') && $field['enable_tokens']) {
590 // Load the node if necessary for nodes in views.
591 $token_node = isset($node->nid
) ?
node_load($node->nid
) : $node;
592 $item['url'] = token_replace($item['url'], 'node', $token_node);
595 $type = link_validate_url($item['url']);
596 $url = link_cleanup_url($item['url']);
598 // Separate out the anchor if any.
599 if (strpos($url, '#') !== FALSE
) {
600 $fragment = substr($url, strpos($url, '#') + 1);
601 $url = substr($url, 0, strpos($url, '#'));
603 // Separate out the query string if any.
604 if (strpos($url, '?') !== FALSE
) {
605 $query = substr($url, strpos($url, '?') + 1);
606 $url = substr($url, 0, strpos($url, '?'));
609 // Create a display URL.
610 $display_url = $type == LINK_EMAIL ?
str_replace('mailto:', '', $url) : url($url, $query, $fragment, TRUE
);
611 if ($field['display']['url_cutoff'] && strlen($display_url) > $field['display']['url_cutoff']) {
612 $display_url = substr($display_url, 0, $field['display']['url_cutoff']) .
"...";
615 // Build a list of attributes.
616 $attributes = array();
617 $item['attributes'] = unserialize($item['attributes']);
618 // Add attributes defined at the widget level.
619 if (!empty($item['attributes']) && is_array($item['attributes'])) {
620 foreach($item['attributes'] as
$attribute => $attbvalue) {
621 if (isset($item['attributes'][$attribute]) && $field['attributes'][$attribute] == 'user') {
622 $attributes[$attribute] = $attbvalue;
626 // Add attributes defined at the field level.
627 if (is_array($field['attributes'])) {
628 foreach($field['attributes'] as
$attribute => $attbvalue) {
629 if (!empty($attbvalue) && $attbvalue != 'default' && $attbvalue != 'user') {
630 $attributes[$attribute] = $attbvalue;
634 // Remove the rel=nofollow for internal links.
635 if ($type != LINK_EXTERNAL
&& isset($attributes['rel']) && strpos($attributes['rel'], 'nofollow') !== FALSE
) {
636 $attributes['rel'] = str_replace('nofollow', '', $attributes['rel']);
637 if (empty($attributes['rel'])) {
638 unset($attributes['rel']);
642 // Give the link the title 'Link'.
643 if ($formatter == 'short') {
644 $output = l(t('Link'), $url, $attributes, $query, $fragment);
646 // Build the link using the widget label.
647 elseif ($formatter == 'label') {
648 $output = l(t($field['widget']['label']), $url, $attributes, $query, $fragment);
650 // Build the link using the URL as title
651 elseif ($formatter == 'url') {
652 $output = l($display_url, $url, $attributes, $query, $fragment);
654 // Build the link using the widget label as separate title.
655 elseif ($formatter == 'separate') {
656 $title = check_plain(_link_field_formatter_title($field, $item, $node));
657 $class = empty($field['attributes']['class']) ?
'' : ' '.
$field['attributes']['class'];
658 unset($field['attributes']['class']);
661 $output .
= '<div class="link-item'.
$class .
'">';
662 $output .
= '<div class="link-title">'.
$title .
'</div>';
663 $output .
= '<div class="link-url">'.
l($display_url, $url, $attributes, $query, $fragment, FALSE
, $item['html']) .
'</div>';
666 // Build the link with a title.
667 elseif (strlen(trim($item['title'])) || ($field['title'] == 'value' && strlen(trim($field['title_value'])))) {
668 $title = _link_field_formatter_title($field, $item, $node);
669 if (empty($url) && !empty($title)) {
670 $output = check_plain($title);
673 $output = l($title, $url, $attributes, $query, $fragment, FALSE
, $item['html']);
676 // Build the link with the URL or email address as the title (max 80 characters).
678 $output = l($display_url, $url, $attributes, $query, $fragment);
684 * Helper function for link_field_formatter().
686 function _link_field_formatter_title(&$field, &$item, &$node) {
687 // Use the title defined at the field level.
688 if ($field['title'] == 'value' && trim($field['title_value'])) {
689 $title = $field['title_value'];
691 // Use the title defined by the user at the widget level.
693 $title = $item['title'];
696 $item['html'] = FALSE
;
697 if (module_exists('token') && ($field['title'] == 'value' || $field['enable_tokens'])) {
698 // Load the node if necessary for nodes in views.
699 $token_node = isset($node->nid
) ?
node_load($node->nid
) : $node;
700 $title = filter_xss(token_replace($title, 'node', $token_node), array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u'));
701 $item['html'] = TRUE
;
707 * Views module argument handler for link fields
709 function link_views_argument_handler($op, &$query, $argtype, $arg = '') {
710 if ($op == 'filter') {
711 $field_name = substr($argtype['type'], 9, strrpos($argtype['type'], '_') - 9);
712 $column = substr($argtype['type'], strrpos($argtype['type'], '_') + 1);
715 $field_name = substr($argtype, 9, strrpos($argtype, '_') - 9);
716 $column = substr($argtype, strrpos($argtype, '_') + 1);
719 // Right now the only attribute we support in views in 'target', but
720 // other attributes of the href tag could be added later.
721 if ($column == 'target') {
722 $attribute = $column;
723 $column = 'attributes';
726 $field = content_fields($field_name);
727 $db_info = content_database_info($field);
728 $main_column = $db_info['columns'][$column];
730 // The table name used here is the Views alias for the table, not the actual
732 $table = 'node_data_'.
$field['field_name'];
736 $query->ensure_table($table);
737 $query->add_field($main_column['column'], $table);
738 return array('field' => $table .
'.'.
$main_column['column']);
742 $query->ensure_table($table);
743 if ($column == 'attributes') {
744 // Because attributes are stored serialized, our only option is to also
745 // serialize the data we're searching for and use LIKE to find similar data.
746 $query->add_where($table .
'.'.
$main_column['column'] .
" LIKE '%%%s%'", serialize($attribute) .
serialize($arg));
749 $query->add_where($table .
'.'.
$main_column['column'] .
" = '%s'", $arg);
755 foreach ($db_info['columns'] as
$column => $attributes) {
756 $view_column_name = $attributes['column'];
757 $item[$column] = $query->$view_column_name;
760 return l(content_format($field, $item, 'plain'), $arg .
'/'.
$query->$main_column['column'], array(), NULL
, NULL
, FALSE
, TRUE
);
766 $item = array(key($db_info['columns']) => $query);
767 return content_format($field, $item);
773 * Views modules filter handler for link protocol filtering
775 function link_views_protocol_filter_handler($op, $filter, $filterinfo, &$query) {
778 $protocols = $filter['value'];
779 $field = $filterinfo['field'];
780 // $table is not the real table name but the views alias.
781 $table = 'node_data_'.
$filterinfo['content_field']['field_name'];
783 foreach ($protocols as
$protocol) {
784 // Simple case, the URL begins with the specified protocol.
785 $condition = $table .
'.'.
$field .
' LIKE \''.
$protocol .
'%\'';
787 // More complex case, no protocol specified but is automatically cleaned up
788 // by link_cleanup_url(). RegEx is required for this search operation.
789 if ($protocol == 'http') {
790 if ($db_type == 'pgsql') {
791 // PostGreSQL code has NOT been tested. Please report any problems to the link issue queue.
792 // pgSQL requires all slashes to be double escaped in regular expressions.
793 // See http://www.postgresql.org/docs/8.1/static/functions-matching.html#FUNCTIONS-POSIX-REGEXP
794 $condition .
= ' OR '.
$table .
'.'.
$field .
' ~* \''.
'^(([a-z0-9]([a-z0-9\\-_]*\\.)+)('. LINK_DOMAINS .
'|[a-z][a-z]))' .
'\'';
797 // mySQL requires backslashes to be double (triple?) escaped within character classes.
798 // See http://dev.mysql.com/doc/refman/5.0/en/string-comparison-functions.html#operator_regexp
799 $condition .
= ' OR '.
$table .
'.'.
$field .
' REGEXP \''.
'^(([a-z0-9]([a-z0-9\\\-_]*\.)+)('. LINK_DOMAINS .
'|[a-z][a-z]))' .
'\'';
803 $where_conditions[] = $condition;
806 $query->ensure_table($table);
807 $query->add_where(implode(' '.
$filter['operator'] .
' ', $where_conditions));
811 * Forms a valid URL if possible from an entered address.
812 * Trims whitespace and automatically adds an http:// to addresses without a protocol specified
815 * @param string $protocol The protocol to be prepended to the url if one is not specified
817 function link_cleanup_url($url, $protocol = "http") {
819 $type = link_validate_url($url);
821 if ($type == LINK_EXTERNAL
) {
822 // Check if there is no protocol specified.
823 $protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i",$url);
824 if (empty($protocol_match)) {
825 // But should there be? Add an automatic http:// if it starts with a domain name.
826 $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)('. LINK_DOMAINS .
'|[a-z]{2}))/i',$url);
827 if (!empty($domain_match)) {
828 $url = $protocol.
"://".
$url;
837 * A lenient verification for URLs. Accepts all URLs following RFC 1738 standard
838 * for URL formation and all email addresses following the RFC 2368 standard for
839 * mailto address formation.
841 * @param string $text
842 * @return mixed Returns boolean FALSE if the URL is not valid. On success, returns an object with
843 * the following attributes: protocol, hostname, ip, and port.
845 function link_validate_url($text) {
847 $allowed_protocols = variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'));
849 $protocol = '((' .
implode("|", $allowed_protocols) .
'):\/\/)';
850 $authentication = '([a-z0-9]+(:[a-z0-9]+)?@)';
851 $domain = '(([a-z0-9]([a-z0-9\-_\[\]])*)(\.(([a-z0-9\-_\[\]])+\.)*('. LINK_DOMAINS .
'|[a-z]{2}))?)';
852 $ipv4 = '([0-9]{1,3}(\.[0-9]{1,3}){3})';
853 $ipv6 = '([0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7})';
854 $port = '(:([0-9]{1,5}))';
856 // Pattern specific to eternal links.
857 $external_pattern = '/^' .
$protocol .
'?' .
$authentication .
'?' .
'(' .
$domain .
'|' .
$ipv4 .
'|' .
$ipv6 .
' |localhost)' .
$port .
'?';
859 // Pattern specific to internal links.
860 $internal_pattern = "/^([a-z0-9_\-+\[\]]+)";
862 $directories = "(\/[a-z0-9_\-\.~+%=&,$'():;*@\[\]]*)*";
863 // Yes, four backslashes == a single backslash.
864 $query = "(\/?\?([?a-z0-9+_|\-\.\/\\\\%=&,$'():;*@\[\]]*))";
865 $anchor = "(#[a-z0-9_\-\.~+%=&,$'():;*@\[\]]*)";
867 // The rest of the path for a standard URL.
868 $end = $directories .
'?' .
$query .
'?' .
$anchor .
'?' .
'$/i';
870 $user = '[a-zA-Z0-9_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+';
871 $email_pattern = '/^mailto:' .
$user .
'@' .
'(' .
$domain .
'|' .
$ipv4 .
'|'.
$ipv6 .
'|localhost)' .
$query .
'?$/';
873 if (strpos($text, '<front>') === 0) {
876 if (in_array('mailto', $allowed_protocols) && preg_match($email_pattern, $text)) {
879 if (preg_match($internal_pattern .
$end, $text)) {
880 return LINK_INTERNAL
;
882 if (preg_match($external_pattern .
$end, $text)) {
883 return LINK_EXTERNAL
;