array('label' => 'Link'), ); } /** * Implementation of hook_field_settings(). */ function link_field_settings($op, $field) { switch ($op) { case 'form': $form = array(); $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' => $options, ); $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' => $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, ); return $form; case 'save': return array('attributes', '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' => TRUE, 'default' => "''"), ); 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 'view': foreach ($items as $delta => $item) { $items[$delta]['attributes'] = unserialize($item['attributes']); $items[$delta]['view'] = content_format($field, $items[$delta], 'default', $node); } return theme('field', $node, $field, $items, $teaser, $page); break; case 'submit': foreach ($items as $delta => $item) { $items[$delta]['attributes'] = serialize($item['attributes']); } 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': $form = array(); $form[$field['field_name']] = array('#tree' => TRUE); if ($field['multiple']) { // Generate more fields if necessary on preview if ($_POST['edit'][$field['field_name']]) { $node_field = $_POST['edit'][$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': foreach($node_field as $delta => $value) { if ($value['url']) { // Validate the link if (link_validate_url(trim($value['url'])) == FALSE) { form_set_error($field['field_name'] .']['. $delta. '][value][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. '][value][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. '][value][link', 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); } 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, // Add a microweight to keep fields in first-in first-out order '#weight' => $field['widget']['weight'].".00".$delta, ); $form_item['url'] = array( '#type' => 'textfield', '#maxlength' => '255', '#title' => $field['title'] == 'none' ? t($field['widget']['label']) : t($field['widget']['label'])." ".t('URL'), '#default_value' => $node_field['url'], '#required' => ($delta == 0) ? $field['required'] : FALSE, '#description' => $field['widget']['description'], ); if ($field['title'] != 'none') { $form_item['title'] = array( '#type' => 'textfield', '#maxlength' => '255', '#title' => t($field['widget']['label'])." ".t('Title'), '#default_value' => $node_field['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_process (&$node_field, $delta = 0) { // Remove the target attribute if not selected if (!$node_field['attributes']['target'] || $node_field['attributes']['target'] == "default") { unset($node_field['attributes']['target']); } } /** * Implementation of hook_field_formatter_info(). */ function link_field_formatter_info() { return array( 'default' => array( 'label' => 'Default, as link', 'field types' => array('link'), ), 'plain' => array( 'label' => 'Plain, no link', 'field types' => array('link'), ), ); } /** * Implementation of hook_field_formatter(). * */ function link_field_formatter($field, $item, $formatter) { if ($formatter == 'plain') { return check_plain($item['url']); } $attributes = array(); // 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; } } } // Build the link with a title if (strlen(trim($item['title']))) { $output = l($item['title'],link_cleanup_url($item['url']),$attributes); } // Build the link with the URL as the title (max 80 characters) else { $output = l(strlen($item['url']) > 80 ? substr($item['url'],0,80)."..." : $item['url'],link_cleanup_url($item['url']),$attributes); } 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 == 1) { // 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 it 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|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|[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) { static $allowed_protocols; if (!isset($allowed_protocols)) { $allowed_protocols = implode("|",variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'))); } $external_pattern = // protocol '/^((' . $allowed_protocols . '):\/\/)?'. '('. // domains '(([a-z0-9]([a-z0-9\-_]*\.)+)(aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|mobi|[a-z]{2}))'. // OR ip addresses '|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'. ')'. // port number '(:([0-9]{1,4}))?'; // Starting path $internal_pattern = "/^([a-z0-9_\-+]+)"; // the rest of the path $end = "(\/[a-z0-9_\-\.~+%=&,$'():;*@]+)*". // forward slash 0 or 1 times '(\/)?'. // query string "(\/?\?[a-z0-9+_\-\.\/%=&,$'():;*@]*)?". // anchors "(#[a-z0-9_\-\.~+%=&,$'():;*@]*)?". // end of the expression, case insensitive '$/i'; if (preg_match($external_pattern . $end, $text, $m)) { return 1; } elseif (preg_match($internal_pattern . $end, $text, $m)) { return 2; } elseif (strpos($text, '') === 0) { return 3; } return FALSE; }