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]['view'] = content_format($field, $item, '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':
$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;
}