/[drupal]/contributions/modules/link/link.module
ViewVC logotype

Contents of /contributions/modules/link/link.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.24 - (show annotations) (download) (as text)
Mon Mar 31 06:50:42 2008 UTC (19 months, 4 weeks ago) by quicksketch
Branch: MAIN
CVS Tags: DRUPAL-6--2-1-beta1
Branch point for: DRUPAL-6--1, DRUPAL-6--2
Changes since 1.23: +307 -395 lines
File MIME type: text/x-php
#229569. First working copy of Drupal 6 version.
1 <?php
2 // $Id: link.module,v 1.23 2008/03/30 05:52:09 quicksketch Exp $
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('label' => 'Link'),
21 );
22 }
23
24 /**
25 * Implementation of hook_field_settings().
26 */
27 function link_field_settings($op, $field) {
28 switch ($op) {
29 case 'form':
30 $form = array(
31 '#theme' => 'link_field_settings',
32 );
33
34 $form['url'] = array(
35 '#type' => 'checkbox',
36 '#title' => t('Optional URL'),
37 '#default_value' => $field['url'],
38 '#return_value' => 'optional',
39 '#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.'),
40 );
41
42 $title_options = array(
43 'optional' => t('Optional Title'),
44 'required' => t('Required Title'),
45 'value' => t('Static Title: '),
46 'none' => t('No Title'),
47 );
48
49 $form['title'] = array(
50 '#type' => 'radios',
51 '#title' => t('Link Title'),
52 '#default_value' => isset($field['title']) ? $field['title'] : 'optional',
53 '#options' => $title_options,
54 '#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.'),
55 );
56
57 $form['title_value'] = array(
58 '#type' => 'textfield',
59 '#default_value' => $field['title_value'],
60 '#size' => '46',
61 );
62
63 // Add token module replacements if available
64 if (module_exists('token')) {
65 $form['tokens'] = array(
66 '#type' => 'fieldset',
67 '#collapsible' => TRUE,
68 '#collapsed' => TRUE,
69 '#title' => t('Placeholder tokens'),
70 '#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."),
71 );
72 $form['tokens']['help'] = array(
73 '#value' => theme('token_help', 'node'),
74 );
75
76 $form['enable_tokens'] = array(
77 '#type' => 'checkbox',
78 '#title' => t('Allow tokens'),
79 '#default_value' => isset($field['enable_tokens']) ? $field['enable_tokens'] : 1,
80 '#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.'),
81 );
82 }
83
84 $form['display'] = array(
85 '#tree' => true,
86 );
87 $form['display']['url_cutoff'] = array(
88 '#type' => 'textfield',
89 '#title' => t('URL Display Cutoff'),
90 '#default_value' => isset($field['display']['url_cutoff']) ? $field['display']['url_cutoff'] : '80',
91 '#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.'),
92 '#maxlength' => 3,
93 '#size' => 3,
94 );
95
96 $target_options = array(
97 'default' => t('Default (no target attribute)'),
98 '_top' => t('Open link in window root'),
99 '_blank' => t('Open link in new window'),
100 'user' => t('Allow the user to choose'),
101 );
102 $form['attributes'] = array(
103 '#tree' => true,
104 );
105 $form['attributes']['target'] = array(
106 '#type' => 'radios',
107 '#title' => t('Link Target'),
108 '#default_value' => $field['attributes']['target'] ? $field['attributes']['target'] : 'default',
109 '#options' => $target_options,
110 );
111 $form['attributes']['rel'] = array(
112 '#type' => 'textfield',
113 '#title' => t('Rel Attribute'),
114 '#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.'),
115 '#default_value' => $field['attributes']['rel'] ? $field['attributes']['rel'] : '',
116 );
117 $form['attributes']['class'] = array(
118 '#type' => 'textfield',
119 '#title' => t('Additional CSS Class'),
120 '#description' => t('When output, this link will have have this class attribute. Multiple classes should be seperated by spaces.'),
121 '#default_value' => isset($field['attributes']['class']) ? $field['attributes']['class'] : '',
122 );
123 return $form;
124
125 case 'validate':
126 if ($field['title'] == 'value' && empty($field['title_value'])) {
127 form_set_error('title_value', t('A default title must be provided if the title is a static value'));
128 }
129 break;
130
131 case 'save':
132 return array('attributes', 'display', 'url', 'title', 'title_value', 'enable_tokens');
133
134 case 'database columns':
135 return array(
136 'url' => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE),
137 'title' => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE),
138 'attributes' => array('type' => 'text', 'size' => 'medium', 'not null' => FALSE),
139 );
140
141 case 'filters':
142 return array(
143 'default' => array(
144 'name' => t('URL'),
145 'operator' => 'views_handler_operator_like',
146 'handler' => 'views_handler_operator_like',
147 ),
148 'title' => array(
149 'name' => t('Title'),
150 'operator' => 'views_handler_operator_like',
151 'handler' => 'views_handler_operator_like',
152 ),
153 'protocol' => array(
154 'name' => t('Protocol'),
155 'list' => drupal_map_assoc(variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'))),
156 'operator' => 'views_handler_operator_or',
157 'handler' => 'link_views_protocol_filter_handler',
158 ),
159 );
160
161 case 'arguments':
162 return array(
163 'content: '. $field['field_name'] .'_url' => array(
164 'name' => t('Link URL') .': '. t($field['widget']['label']) .' ('. $field['field_name'] .')',
165 'handler' => 'link_views_argument_handler',
166 ),
167 'content: '. $field['field_name'] .'_title' => array(
168 'name' => t('Link Title') .': '. t($field['widget']['label']) .' ('. $field['field_name'] .')',
169 'handler' => 'link_views_argument_handler',
170 ),
171 'content: '. $field['field_name'] .'_target' => array(
172 'name' => t('Link Target') .': '. t($field['widget']['label']) .' ('. $field['field_name'] .')',
173 'handler' => 'link_views_argument_handler',
174 ),
175 );
176
177 }
178 }
179
180 /**
181 * Theme the settings form for the link field.
182 */
183 function theme_link_field_settings($form) {
184 $title_value = drupal_render($form['title_value']);
185 $title_checkbox = drupal_render($form['title']['value']);
186
187 // Set Static Title radio option to include the title_value textfield.
188 $form['title']['value'] = array('#value' => '<div class="container-inline">'. $title_checkbox . $title_value .'</div>');
189
190 // Reprint the title radio options with the included textfield.
191 return drupal_render($form);
192 }
193
194 /**
195 * Implementation of hook_field_is_empty().
196 */
197 function link_content_is_empty($item, $field) {
198 if (empty($item['title']) && empty($item['url'])) {
199 return TRUE;
200 }
201 return FALSE;
202 }
203
204 /**
205 * Implementation of hook_field().
206 */
207 function link_field($op, &$node, $field, &$items, $teaser, $page) {
208 switch ($op) {
209 case 'load':
210 foreach ($items as $delta => $item) {
211 _link_load($items[$delta], $delta);
212 }
213 return $items;
214 break;
215
216 case 'validate':
217 $optional_field_found = FALSE;
218 foreach($items as $delta => $value) {
219 _link_widget_validate($items[$delta],$delta, $field, $node, $optional_field_found);
220 }
221
222 if ($field['url'] == 'optional' && $field['title'] == 'optional' && $field['required'] && !$optional_field_found) {
223 form_set_error($field['field_name'] .'][0][title', t('At least one title or URL must be entered.'));
224 }
225 break;
226
227 case 'process form values':
228 foreach($items as $delta => $value) {
229 _link_widget_process($items[$delta],$delta, $field, $node);
230 }
231 break;
232
233 case 'sanitize':
234 foreach ($items as $delta => $value) {
235 link_item_sanitize($items[$delta], $delta, $field, $node);
236 }
237 break;
238 }
239 }
240
241 /**
242 * Implementation of hook_widget_info().
243 */
244 function link_widget_info() {
245 return array(
246 'link' => array(
247 'label' => 'Text Fields for Title and URL',
248 'field types' => array('link'),
249 'multiple values' => CONTENT_HANDLE_CORE,
250 ),
251 );
252 }
253
254 /**
255 * Implementation of hook_widget().
256 */
257 function link_widget(&$form, &$form_state, $field, $items, $delta = 0) {
258 $element = array(
259 '#type' => $field['widget']['type'],
260 '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
261 );
262 return $element;
263 }
264
265
266 function _link_load(&$item, $delta = 0) {
267 // Unserialize the attributes array.
268 $item['attributes'] = unserialize($item['attributes']);
269 }
270
271 function _link_process(&$item, $delta = 0, $field, $node) {
272 // Remove the target attribute if not selected.
273 if (!$item['attributes']['target'] || $item['attributes']['target'] == "default") {
274 unset($item['attributes']['target']);
275 }
276 // Trim whitespace from URL.
277 $item['url'] = trim($item['url']);
278 // Serialize the attributes array.
279 $item['attributes'] = serialize($item['attributes']);
280
281 // Don't save an invalid default value (e.g. 'http://').
282 if ((isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url']) && is_object($node)) {
283 if (!link_validate_url($item['url'])) {
284 unset($item['url']);
285 }
286 }
287 }
288
289 function _link_validate(&$item, $delta, $field, $node, &$optional_field_found) {
290 if ($item['url'] && !(isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url'] && !$field['required'])) {
291 // Validate the link.
292 if (link_validate_url(trim($item['url'])) == FALSE) {
293 form_set_error($field['field_name'] .']['. $delta. '][url', t('Not a valid URL.'));
294 }
295 // Require a title for the link if necessary.
296 if ($field['title'] == 'required' && strlen(trim($item['title'])) == 0) {
297 form_set_error($field['field_name'] .']['. $delta. '][title', t('Titles are required for all links.'));
298 }
299 }
300 // Require a link if we have a title.
301 if ($field['url'] !== 'optional' && strlen($item['title']) > 0 && strlen(trim($item['url'])) == 0) {
302 form_set_error($field['field_name'] .']['. $delta. '][url', t('You cannot enter a title without a link url.'));
303 }
304 // In a totally bizzaro case, where URLs and titles are optional but the field is required, ensure there is at least one link.
305 if ($field['url'] == 'optional' && $field['title'] == 'optional' && (strlen(trim($item['url'])) != 0 || strlen(trim($item['title'])) != 0)) {
306 $optional_field_found = TRUE;
307 }
308 }
309
310 /**
311 * Cleanup user-entered values for a link field according to field settings.
312 *
313 * @param $item
314 * A single link item, usually containing url, title, and attributes.
315 * @param $delta
316 * The delta value if this field is one of multiple fields.
317 * @param $field
318 * The CCK field definition.
319 * @param $node
320 * The node containing this link.
321 */
322 function _link_sanitize(&$item, $delta, &$field, &$node) {
323 // Replace URL tokens.
324 if (module_exists('token') && $field['enable_tokens']) {
325 $node = node_load($node->nid); // Necessary for nodes in views.
326 $item['url'] = token_replace($item['url'], 'node', $node);
327 }
328
329 $type = link_validate_url($item['url']);
330 $url = link_cleanup_url($item['url']);
331
332 // Seperate out the anchor if any.
333 if (strpos($url, '#') !== FALSE) {
334 $item['fragment'] = substr($url, strpos($url, '#') + 1);
335 $url = substr($url, 0, strpos($url, '#'));
336 }
337 // Seperate out the query string if any.
338 if (strpos($url, '?') !== FALSE) {
339 $item['query'] = substr($url, strpos($url, '?') + 1);
340 $url = substr($url, 0, strpos($url, '?'));
341 }
342 // Save the new URL without the anchor or query.
343 $item['url'] = $url;
344
345 // Create a shortened URL for display.
346 $display_url = $type == LINK_EMAIL ? str_replace('mailto:', '', $url) : url($url, array('query' => $item['query'], 'fragment' => $item['fragment'], 'absolute' => TRUE));
347 if ($field['display']['url_cutoff'] && strlen($display_url) > $field['display']['url_cutoff']) {
348 $display_url = substr($display_url, 0, $field['display']['url_cutoff']) . "...";
349 }
350 $item['display_url'] = $display_url;
351
352 // Use the title defined at the field level.
353 if ($field['title'] == 'value' && strlen(trim($field['title_value']))) {
354 $title = $field['title_value'];
355 }
356 // Use the title defined by the user at the widget level.
357 else {
358 $title = $item['title'];
359 }
360 // Replace tokens.
361 if (module_exists('token') && ($field['title'] == 'value' || $field['enable_tokens'])) {
362 $node = node_load($node->nid); // Necessary for nodes in views.
363 $title = token_replace($title, 'node', $node);
364 }
365 $item['display_title'] = empty($title) ? $item['display_url'] : $title;
366
367 // Add attributes defined at the widget level
368 $attributes = array();
369 if (is_array($item['attributes'])) {
370 foreach($item['attributes'] as $attribute => $attbvalue) {
371 if (isset($item['attributes'][$attribute]) && $field['attributes'][$attribute] == 'user') {
372 $attributes[$attribute] = $attbvalue;
373 }
374 }
375 }
376 // Add attributes defined at the field level
377 if (is_array($field['attributes'])) {
378 foreach($field['attributes'] as $attribute => $attbvalue) {
379 if (!empty($attbvalue) && $attbvalue != 'default' && $attbvalue != 'user') {
380 $attributes[$attribute] = $attbvalue;
381 }
382 }
383 }
384 $item['attributes'] = $attributes;
385
386 // Add the widget label.
387 $item['label'] = $field['widget']['label'];
388 }
389
390 /**
391 * Implementation of hook_theme().
392 */
393 function link_theme() {
394 return array(
395 'link_field_settings' => array(
396 'arguments' => array('element' => NULL),
397 ),
398 'link_formatter_default' => array(
399 'arguments' => array('element' => NULL),
400 ),
401 'link_formatter_plain' => array(
402 'arguments' => array('element' => NULL),
403 ),
404 'link_formatter_short' => array(
405 'arguments' => array('element' => NULL),
406 ),
407 'link_formatter_label' => array(
408 'arguments' => array('element' => NULL),
409 ),
410 'link' => array(
411 'arguments' => array('element' => NULL),
412 ),
413 );
414 }
415
416 /**
417 * FAPI theme for an individual text elements.
418 *
419 * The textfield or textarea is already rendered by the
420 * textfield or textarea themes and the html output
421 * lives in $element['#children']. Override this theme to
422 * make custom changes to the output.
423 *
424 * $element['#field_name'] contains the field name
425 * $element['#delta'] is the position of this element in the group
426 */
427 function theme_link($element) {
428 drupal_add_css(drupal_get_path('module', 'link') .'/link.css');
429
430 if ($element['#delta'] == 0) {
431
432 }
433
434 $output = '';
435 $output .= '<div class="link-field-subrow clear-block">';
436 if ($element['title']) {
437 $output .= '<div class="link-field-title link-field-column">' . theme('textfield', $element['title']) . '</div>';
438 }
439 $output .= '<div class="link-field-url' . ($element['title'] ? ' link-field-column' : '') . '">' . theme('textfield', $element['url']) . '</div>';
440 $output .= '</div>';
441 if ($element['attributes']) {
442 $output .= '<div class="link-attributes">' . theme('form_element', $element['attributes'], $element['attributes']['#value']) . '</div>';
443 }
444 return $output;
445 }
446
447 /*
448 * Implementation of FAPI hook_elements().
449 *
450 * Any FAPI callbacks needed for individual widgets can be declared here,
451 * and the element will be passed to those callbacks for processing.
452 *
453 * Drupal will automatically theme the element using a theme with
454 * the same name as the hook_elements key.
455 *
456 * Autocomplete_path is not used by text_widget but other widgets can use it
457 * (see nodereference and userreference).
458 */
459 function link_elements() {
460 $elements = array();
461 $elements['link'] = array(
462 '#input' => TRUE,
463 '#columns' => array('url', 'title'),
464 '#delta' => 0,
465 '#process' => array('link_process'),
466 '#autocomplete_path' => FALSE,
467 '#theme' => 'link_widget_form_row',
468 );
469 return $elements;
470 }
471
472 /**
473 * Process the link type element before displaying the field.
474 *
475 * Build the form element. When creating a form using FAPI #process,
476 * note that $element['#value'] is already set.
477 *
478 * The $fields array is in $form['#field_info'][$element['#field_name']].
479 */
480 function link_process($element, $edit, $form_state, $form) {
481 $field = $form['#field_info'][$element['#field_name']];
482 $delta = $element['#delta'];
483 $element['url'] = array(
484 '#type' => 'textfield',
485 '#maxlength' => '255',
486 '#title' => t('URL'),
487 '#description' => $element['#description'],
488 '#required' => ($delta == 0 && $field['title'] != 'optional') ? $element['#required'] : FALSE,
489 '#default_value' => isset($element['#value']['url']) ? $element['#value']['url'] : NULL,
490 );
491 if ($field['title'] != 'none') {
492 $element['title'] = array(
493 '#type' => 'textfield',
494 '#maxlength' => '255',
495 '#title' => t('Title'),
496 '#required' => ($delta == 0 && $field['title'] == 'required') ? $field['required'] : FALSE,
497 '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
498 );
499
500 }
501 if ($field['attributes']['target'] == 'user') {
502 $element['attributes']['target'] = array(
503 '#type' => 'checkbox',
504 '#title' => t('Open URL in a New Window'),
505 '#return_value' => "_blank",
506 );
507 }
508 return $element;
509 }
510
511 /**
512 * Implementation of hook_field_formatter_info().
513 */
514 function link_field_formatter_info() {
515 return array(
516 'default' => array(
517 'label' => t('Default, as link with title'),
518 'field types' => array('link'),
519 'multiple values' => CONTENT_HANDLE_CORE,
520 ),
521 'plain' => array(
522 'label' => t('Plain, as the text URL'),
523 'field types' => array('link'),
524 'multiple values' => CONTENT_HANDLE_CORE,
525 ),
526 'short' => array(
527 'label' => t('Short, as link with title "Link"'),
528 'field types' => array('link'),
529 'multiple values' => CONTENT_HANDLE_CORE,
530 ),
531 'label' => array(
532 'label' => t('Label, as link with label as title'),
533 'field types' => array('link'),
534 'multiple values' => CONTENT_HANDLE_CORE,
535 ),
536 );
537 }
538
539 /**
540 * Theme function for 'default' text field formatter.
541 */
542 function theme_link_formatter_default($element) {
543 // Display a normal link if both title and URL are available.
544 if (!empty($element['#item']['display_title']) && !empty($element['#item']['url'])) {
545 return l($element['#item']['display_title'], $element['#item']['url'], $element['#item']);
546 }
547 // If only a title, display the title.
548 else {
549 return check_plain($element['#item']['display_title']);
550 }
551 }
552
553 /**
554 * Theme function for 'plain' text field formatter.
555 */
556 function theme_link_formatter_plain($element) {
557 return empty($element['#item']['url']) ? check_plain($element['#item']['title']) : check_plain($element['#item']['url']);
558 }
559
560 /**
561 * Theme function for 'short' text field formatter.
562 */
563 function theme_link_formatter_short($element) {
564 return $element['#item']['url'] ? l(t('Link'), $element['#item']['url'], $element['#item']) : '';
565 }
566
567 /**
568 * Theme function for 'short' text field formatter.
569 */
570 function theme_link_formatter_label($element) {
571 return $element['#item']['url'] ? l($element['#item']['label'], $element['#item']['url'], $element['#item']) : '';
572 }
573 /**
574 * Views module argument handler for link fields
575 */
576 function link_views_argument_handler($op, &$query, $argtype, $arg = '') {
577 if ($op == 'filter') {
578 $field_name = substr($argtype['type'], 9, strrpos($argtype['type'], '_') - 9);
579 $column = substr($argtype['type'], strrpos($argtype['type'], '_') + 1);
580 }
581 else {
582 $field_name = substr($argtype, 9, strrpos($argtype, '_') - 9);
583 $column = substr($argtype, strrpos($argtype, '_') + 1);
584 }
585
586 // Right now the only attribute we support in views in 'target', but
587 // other attributes of the href tag could be added later.
588 if ($column == 'target') {
589 $attribute = $column;
590 $column = 'attributes';
591 }
592
593 $field = content_fields($field_name);
594 $db_info = content_database_info($field);
595 $main_column = $db_info['columns'][$column];
596
597 // The table name used here is the Views alias for the table, not the actual
598 // table name.
599 $table = 'node_data_'. $field['field_name'];
600
601 switch ($op) {
602 case 'summary':
603 $query->ensure_table($table);
604 $query->add_field($main_column['column'], $table);
605 return array('field' => $table .'.'. $main_column['column']);
606 break;
607
608 case 'filter':
609 $query->ensure_table($table);
610 if ($column == 'attributes') {
611 // Because attributes are stored serialized, our only option is to also
612 // serialize the data we're searching for and use LIKE to find similar data.
613 $query->add_where($table .'.'. $main_column['column'] ." LIKE '%%%s%'", serialize($attribute) . serialize($arg));
614 }
615 else {
616 $query->add_where($table .'.'. $main_column['column'] ." = '%s'", $arg);
617 }
618 break;
619
620 case 'link':
621 $item = array();
622 foreach ($db_info['columns'] as $column => $attributes) {
623 $view_column_name = $attributes['column'];
624 $item[$column] = $query->$view_column_name;
625 }
626
627 return l(content_format($field, $item, 'plain'), $arg .'/'. $query->$main_column['column'], array(), NULL, NULL, FALSE, TRUE);
628
629 case 'sort':
630 break;
631
632 case 'title':
633 $item = array(key($db_info['columns']) => $query);
634 return content_format($field, $item);
635 break;
636 }
637 }
638
639 /**
640 * Views modules filter handler for link protocol filtering
641 */
642 function link_views_protocol_filter_handler($op, $filter, $filterinfo, &$query) {
643 global $db_type;
644
645 $protocols = $filter['value'];
646 $field = $filterinfo['field'];
647 // $table is not the real table name but the views alias.
648 $table = 'node_data_'. $filterinfo['content_field']['field_name'];
649
650 foreach ($protocols as $protocol) {
651 // Simple case, the URL begins with the specified protocol.
652 $condition = $table .'.'. $field .' LIKE \''. $protocol .'%\'';
653
654 // More complex case, no protocol specified but is automatically cleaned up
655 // by link_cleanup_url(). RegEx is required for this search operation.
656 if ($protocol == 'http') {
657 if ($db_type == 'pgsql') {
658 // PostGreSQL code has NOT been tested. Please report any problems to the link issue queue.
659 // pgSQL requires all slashes to be double escaped in regular expressions.
660 // See http://www.postgresql.org/docs/8.1/static/functions-matching.html#FUNCTIONS-POSIX-REGEXP
661 $condition .= ' OR '. $table .'.'. $field .' ~* \''. '^(([a-z0-9]([a-z0-9\\-_]*\\.)+)('. LINK_DOMAINS .'|[a-z][a-z]))' .'\'';
662 }
663 else {
664 // mySQL requires backslashes to be double (triple?) escaped within character classes.
665 // See http://dev.mysql.com/doc/refman/5.0/en/string-comparison-functions.html#operator_regexp
666 $condition .= ' OR '. $table .'.'. $field .' REGEXP \''. '^(([a-z0-9]([a-z0-9\\\-_]*\.)+)('. LINK_DOMAINS .'|[a-z][a-z]))' .'\'';
667 }
668 }
669
670 $where_conditions[] = $condition;
671 }
672
673 $query->ensure_table($table);
674 $query->add_where(implode(' '. $filter['operator'] .' ', $where_conditions));
675 }
676
677 /**
678 * Forms a valid URL if possible from an entered address.
679 * Trims whitespace and automatically adds an http:// to addresses without a protocol specified
680 *
681 * @param string $url
682 * @param string $protocol The protocol to be prepended to the url if one is not specified
683 */
684 function link_cleanup_url($url, $protocol = "http") {
685 $url = trim($url);
686 $type = link_validate_url($url);
687
688 if ($type == LINK_EXTERNAL) {
689 // Check if there is no protocol specified.
690 $protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i",$url);
691 if (empty($protocol_match)) {
692 // But should there be? Add an automatic http:// if it starts with a domain name.
693 $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)('. LINK_DOMAINS .'|[a-z]{2}))/i',$url);
694 if (!empty($domain_match)) {
695 $url = $protocol."://".$url;
696 }
697 }
698 }
699
700 return $url;
701 }
702
703 /**
704 * A lenient verification for URLs. Accepts all URLs following RFC 1738 standard
705 * for URL formation and all email addresses following the RFC 2368 standard for
706 * mailto address formation.
707 *
708 * @param string $text
709 * @return mixed Returns boolean FALSE if the URL is not valid. On success, returns an object with
710 * the following attributes: protocol, hostname, ip, and port.
711 */
712 function link_validate_url($text) {
713
714 $allowed_protocols = variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'));
715
716 $protocol = '((' . implode("|", $allowed_protocols) . '):\/\/)';
717 $authentication = '([a-z0-9]+(:[a-z0-9]+)?@)';
718 $domain = '((([a-z0-9]([a-z0-9\-_\[\]]*\.))+)('. LINK_DOMAINS .'|[a-z]{2}))';
719 $ipv4 = '([0-9]{1,3}(\.[0-9]{1,3}){3})';
720 $ipv6 = '([0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7})';
721 $port = '(:([0-9]{1,5}))';
722
723 // Pattern specific to eternal links.
724 $external_pattern = '/^' . $protocol . '?'. $authentication . '?' . '(' . $domain . '|' . $ipv4 . '|' . $ipv6 . ' |localhost)' . $port . '?';
725
726 // Pattern specific to internal links.
727 $internal_pattern = "/^([a-z0-9_\-+\[\]]+)";
728
729 $directories = "(\/[a-z0-9_\-\.~+%=&,$'():;*@\[\]]*)*";
730 $query = "(\/?\?([?a-z0-9+_\-\.\/%=&,$'():;*@\[\]]*))";
731 $anchor = "(#[a-z0-9_\-\.~+%=&,$'():;*@\[\]]*)";
732
733 // The rest of the path for a standard URL.
734 $end = $directories . '?' . $query . '?' . $anchor . '?' . '$/i';
735
736 $user = '[a-zA-Z0-9_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+';
737 $email_pattern = '/^mailto:' . $user . '@' . '(' . $domain . '|' . $ipv4 .'|'. $ipv6 . '|localhost)' . $query . '$/';
738
739 if (preg_match($external_pattern . $end, $text)) {
740 return LINK_EXTERNAL;
741 }
742 elseif (preg_match($internal_pattern . $end, $text)) {
743 return LINK_INTERNAL;
744 }
745 elseif (in_array('mailto', $allowed_protocols) && preg_match($email_pattern, $text)) {
746 return LINK_EMAIL;
747 }
748 elseif (strpos($text, '<front>') === 0) {
749 return LINK_FRONT;
750 }
751 return FALSE;
752 }

  ViewVC Help
Powered by ViewVC 1.1.2