array('label' => 'Link'), ); } /** * Implementation of hook_field_settings(). */ function link_field_settings($op, $field) { switch ($op) { case 'form': $form = array(); $title_options = array ( 'optional' => t('Optional Title'), 'required' => t('Required Title'), 'none' => t('No Title'), ); $form['title'] = array( '#type' => 'radios', '#title' => t('Link Title'), '#default_value' => isset($field['title']) ? $field['title'] : 'optional', '#options' => $title_options, ); $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 (…)? Leave blank for no limit.'), '#maxlength' => 3, '#size' => 3, ); $target_options = array( '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'), ); $form['attributes'] = array( '#tree' => true, ); $form['attributes']['target'] = array( '#type' => 'radios', '#title' => t('Link Target'), '#default_value' => $field['attributes']['target'] ? $field['attributes']['target'] : 'default', '#options' => $target_options, ); $form['attributes']['rel'] = array( '#type' => 'checkbox', '#return_value' => 'nofollow', '#prefix' => '
', '#suffix' => '
', '#title' => t('Add rel="nofollow" Attribute'), '#description' => t('The rel="nofollow" attribute prevents some search engines from spidering entered links.'), '#default_value' => isset($field['attributes']['rel']) ? $field['attributes']['rel'] : false, ); $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'] : '', ); return $form; case 'save': return array('attributes', 'display', 'title'); 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' => "''"), 'attributes' => array('type' => 'mediumtext', 'not null' => FALSE), ); case 'filters': return array( 'default' => array( 'operator' => 'views_handler_operator_like', 'handler' => 'views_handler_filter_like', ), ); } } /** * Implementation of hook_field(). */ function link_field($op, &$node, $field, &$items, $teaser, $page) { switch ($op) { case 'load': foreach ($items as $delta => $item) { $items[$delta]['attributes'] = unserialize($item['attributes']); } return $items; break; case 'view': foreach ($items as $delta => $item) { $items[$delta]['view'] = content_format($field, $items[$delta], 'default', $node); } return theme('field', $node, $field, $items, $teaser, $page); break; } } /** * 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) { case 'form': foreach($node_field as $delta => $value) { _link_widget_prepare($node_field[$delta],$delta); } $form = array(); $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'], ); if ($field['multiple']) { // Generate more fields if necessary on preview if ($_POST[$field['field_name']]) { $node_field = $_POST[$field['field_name']]; } $delta = 0; // Render link fields for all the entered values foreach ($node_field as $data) { if ($data['url']) { _link_widget_form($form[$field['field_name']][$delta], $field, $data, $delta); $delta++; } } // Render two additional new link fields foreach (range($delta, $delta + 1) as $delta) { _link_widget_form($form[$field['field_name']][$delta], $field, $node_field, $delta); } } // end if multiple else { _link_widget_form($form[$field['field_name']][0], $field, $node_field[0]); } return $form; case 'validate': if (!is_object($node)) return; foreach($node_field as $delta => $value) { if ($value['url'] && !(isset($field['widget']['default_value'][$delta]['url']) && $value['url'] == $field['widget']['default_value'][$delta]['url'] && !$field['required'])) { // Validate the link if (link_validate_url(trim($value['url'])) == FALSE) { form_set_error($field['field_name'] .']['. $delta. '][url', t('Not a valid URL.')); } // Require a title for the link if necessary elseif ($field['title'] == 'required' && strlen(trim($value['title'])) == 0) { form_set_error($field['field_name'] .']['. $delta. '][title', t('Titles are required for all links.')); } } // Require a link if we have a title elseif (strlen($value['title']) > 0) { form_set_error($field['field_name'] .']['. $delta. '][url', t('You cannot enter a title without a link.')); } } return; case 'process form values': foreach($node_field as $delta => $value) { _link_widget_process($node_field[$delta],$delta, $field, $node); } return; case 'submit': return; } } /** * Helper function renders the link widget in both single and multiple value cases. */ function _link_widget_form(&$form_item, $field, $node_field, $delta = 0) { $form_item = array( '#tree' => true, '#theme' => 'link_widget_form_row', // Add a microweight to keep fields in first-in first-out order '#weight' => $field['widget']['weight'].".00".$delta, ); $default_url = ""; if (isset($field['widget']['default_value'][$delta]['url'])) { $default_url = $field['widget']['default_value'][$delta]['url']; } $form_item['url'] = array( '#type' => 'textfield', '#maxlength' => '255', '#title' => t('URL'), '#default_value' => ($node_field['url']) ? $node_field['url'] : $default_url, '#required' => ($delta == 0) ? $field['required'] : FALSE, ); if ($field['title'] != 'none') { $default_title = ""; if (isset($field['widget']['default_value'][$delta]['title'])) { $default_title = $field['widget']['default_value'][$delta]['title']; } $form_item['title'] = array( '#type' => 'textfield', '#maxlength' => '255', '#title' => t('Title'), '#default_value' => ($node_field['title']) ? $node_field['title'] : $default_title, '#required' => ($delta == 0 && $field['title'] == 'required') ? $field['required'] : FALSE, ); } if ($field['attributes']['target'] == 'user') { $form_item['attributes']['target'] = array( '#type' => 'checkbox', '#title' => t('Open URL in a New Window'), '#default_value' => $node_field['attributes']['target'], '#return_value' => "_blank", ); } } function _link_widget_prepare(&$node_field, $delta = 0) { // Unserialize the attributes array $node_field['attributes'] = unserialize($node_field['attributes']); } function _link_widget_process(&$node_field, $delta = 0, $field, $node) { // Remove the target attribute if not selected if (!$node_field['attributes']['target'] || $node_field['attributes']['target'] == "default") { unset($node_field['attributes']['target']); } // Trim whitespace from URL $node_field['url'] = trim($node_field['url']); // Serialize the attributes array $node_field['attributes'] = serialize($node_field['attributes']); //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']); } } } /** * Theme the display of the entire link set */ function theme_link_widget_form($element) { drupal_add_css(drupal_get_path('module', 'link') .'/link.css'); // Check for multiple if (isset($element[1])) { $output = drupal_render($element); } else { if (isset($element[0]['title'])) { $element[0]['title']['#title'] = $element['#title'] . ' ' . $element[0]['title']['#title']; $element[0]['title']['#description'] = $element['#description']; } else { $element[0]['url']['#description'] = $element['#description']; } $element[0]['url']['#title'] = $element['#title'] . ' ' . $element[0]['url']['#title']; $output = drupal_render($element[0]); } return $output; } /** * Theme the display of a single form row */ function theme_link_widget_form_row($element) { $output = ''; $output .= ''; return $output; } /** * Implementation of hook_field_formatter_info(). */ function link_field_formatter_info() { return array( 'default' => array( 'label' => t('Default, as link'), 'field types' => array('link'), ), 'plain' => array( 'label' => t('Plain, no link'), 'field types' => array('link'), ), 'short' => array( 'label' => t('Short, no title as link'), 'field types' => array('link'), ), ); } /** * Implementation of hook_field_formatter(). */ function link_field_formatter($field, $item, $formatter, $node) { if (empty($item['url'])) { return ''; } if ($formatter == 'plain') { return check_plain($item['url']); } $attributes = array(); $item['attributes'] = unserialize($item['attributes']); // Add attributes defined at the widget level if (is_array($item['attributes'])) { foreach($item['attributes'] as $attribute => $attbvalue) { if (isset($item['attributes'][$attribute]) && $field['attributes'][$attribute] == 'user') { $attributes[$attribute] = $attbvalue; } } } // 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') { $attributes[$attribute] = $attbvalue; } } } $type = link_validate_url($item['url']); $url = link_cleanup_url($item['url']); // Seperate out the anchor if any if (strpos($url, '#') !== FALSE) { $fragment = substr($url, strpos($url, '#') + 1); $url = substr($url, 0, strpos($url, '#')); } // Seperate out the query string if any if (strpos($url, '?') !== FALSE) { $query = substr($url, strpos($url, '?') + 1); $url = substr($url, 0, strpos($url, '?')); } // Give the link the title 'Link' if ($formatter == 'short') { $output = l(t('Link'), link_cleanup_url($item['url']), $attributes, $query, $fragment); } // Build the link with a title elseif (strlen(trim($item['title']))) { $output = l($item['title'], $url, $attributes, $query, $fragment); } // Build the link with the URL or email address as the title (max 80 characters) else { $display_url = $type == LINK_EMAIL ? str_replace('mailto:', '', $url) : url($url, $query, $fragment, TRUE); if ($field['display']['url_cutoff'] && strlen($display_url) > $field['display']['url_cutoff']) { $display_url = substr($display_url, 0, $field['display']['url_cutoff']) . "..."; } $output = l($display_url, $url, $attributes, $query, $fragment, array('class' => $field['attributes']['class'])); } return $output; } /** * Forms a valid URL if possible from an entered address. * Trims whitespace and automatically adds an http:// to addresses without a protocol specified * * @param string $url * @param string $protocol The protocol to be prepended to the url if one is not specified */ function link_cleanup_url($url, $protocol = "http") { $url = trim($url); $type = link_validate_url($url); if ($type == LINK_EXTERNAL) { // Check if there is no protocol specified $protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i",$url); if (empty($protocol_match)) { // But should there be? Add an automatic http:// if it starts with a domain name $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)(aero|arpa|biz|com|cat|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|mobi|[a-z]{2}))/i',$url); if (!empty($domain_match)) { $url = $protocol."://".$url; } } } return $url; } /** * A lenient verification for URLs. Accepts all URLs following RFC 1738 standard for URL formation. * * @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. */ function link_validate_url($text) { $allowed_protocols = variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal')); $protocol = '((' . implode("|", $allowed_protocols) . '):\/\/)'; $domain = '(([a-z0-9]([a-z0-9\-_]*\.)+)(aero|arpa|biz|com|cat|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|mobi|[a-z]{2}))'; $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}))'; // Pattern specific to eternal links $external_pattern = '/^' . $protocol . '?'. '(' . $domain . '|' . $ipv4 . '|' . $ipv6 . ' |localhost)' . $port . '?'; // Pattern specific to internal links $internal_pattern = "/^([a-z0-9_\-+]+)"; $directories = "(\/[a-z0-9_\-\.~+%=&,$'():;*@]*)*"; $query = "(\/?\?[a-z0-9+_\-\.\/%=&,$'():;*@]*)"; $anchor = "(#[a-z0-9_\-\.~+%=&,$'():;*@]*)"; // the rest of the path for a standard URL $end = $directories . '?' . $query . '?' . $anchor . '?' . '$/i'; if (preg_match($external_pattern . $end, $text)) { return LINK_EXTERNAL; } elseif (preg_match($internal_pattern . $end, $text)) { return LINK_INTERNAL; } elseif (in_array('mailto', $allowed_protocols) && ($address = preg_replace('/^mailto:/', '', $text)) && valid_email_address($address)) { return LINK_EMAIL; } elseif (strpos($text, '') === 0) { return LINK_FRONT; } return FALSE; }