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 .= '';
if ($element['title']) {
$output .= '
' . drupal_render($element['title']) . '
';
}
$output .= '
' . drupal_render($element['url']) . '
';
if ($element['attributes']) {
$output .= '
' . drupal_render($element['attributes']) . '
';
}
$output .= drupal_render($element);
$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;
}