diff --git a/link.install b/link.install index 2ca4a1bc8750b410b5e3e17c57893177f0cf2abe..6c21d26ba292d31ca0a8ac4e7650b10c461b359b 100644 --- a/link.install +++ b/link.install @@ -1,10 +1,16 @@ 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), - ); - content_alter_db_field(array(), array(), $field, $columns); - $db_info = content_database_info($field); - if ($field['multiple']) { - $ret[] = update_sql('INSERT INTO {'. $db_info['table'] .'} (vid, delta, nid, '. $field['field_name'] .'_url, '. $field['field_name'] .'_title, '. $field['field_name'] ."_attributes) SELECT vid, delta, nid, field_url, field_title, attributes FROM {node_field_link_data} WHERE field_name = '". $field['field_name'] ."'"); - } - else { - $ret[] = update_sql('UPDATE {'. $db_info['table'] .'} c, {node_field_link_data} l SET c.'. $field['field_name'] .'_url = l.field_url, c.'. $field['field_name'] .'_title = l.field_title, c.'. $field['field_name'] ."_attributes = l.attributes WHERE l.field_name = '". $field['field_name'] ."' AND c.vid = l.vid AND c.nid = l.nid"); - } +/** + * Change the database schema to allow NULL values. + */ +function link_update_6001() { + $ret = array(); + + // Build a list of fields that need updating. + $update_fields = array(); + foreach (content_types_install() as $type_name => $fields) { + foreach ($fields as $field) { + if ($field['type'] == 'link') { + // We only process a given field once. + $update_fields[$field['field_name']] = $field; + } } } - - $ret[] = update_sql('DROP TABLE {node_field_link_data}'); - - db_query('DELETE FROM {cache}'); + + // Update each field's storage to match the current definition. + foreach ($update_fields as $field) { + $db_info = content_database_info($field); + foreach ($db_info['columns'] as $column) { + db_change_field($ret, $db_info['table'], $column['column'], $column['column'], $column); + $ret[] = update_sql("UPDATE {". $db_info['table'] ."} SET ". $column['column'] ." = NULL WHERE ". $column['column'] ." = '' OR ". $column['column'] ." = 'N;'"); + } + } + + // Let CCK re-associate link fields with Link module and activate the fields. + content_associate_fields('link'); + return $ret; } diff --git a/link.module b/link.module index bb1d8fb11c81d518c5f1a4d398ffa6030cf2df0f..762dca0a4798383e23a5a34869774cc37ac40897 100644 --- a/link.module +++ b/link.module @@ -10,14 +10,17 @@ define('LINK_EXTERNAL', 'external'); define('LINK_INTERNAL', 'internal'); define('LINK_FRONT', 'front'); define('LINK_EMAIL', 'email'); -define('LINK_DOMAINS', 'aero|arpa|biz|com|cat|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|mobi'); +define('LINK_DOMAINS', 'aero|arpa|asia|biz|com|cat|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|mobi|local'); /** * Implementation of hook_field_info(). */ function link_field_info() { return array( - 'link' => array('label' => 'Link'), + 'link' => array( + 'label' => t('Link'), + 'description' => t('Store a title, href, and attributes in the database to assemble a link.'), + ), ); } @@ -51,7 +54,7 @@ function link_field_settings($op, $field) { '#title' => t('Link Title'), '#default_value' => isset($field['title']) ? $field['title'] : 'optional', '#options' => $title_options, - '#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 token module is installed, the static title value may use any other node field as its value.'), + '#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 token module is installed, the static title value may use any other node field as its value. Static and token-based titles may include most inline XHTML tags such as strong, em, img, span, etc.'), ); $form['title_value'] = array( @@ -75,14 +78,14 @@ function link_field_settings($op, $field) { $form['enable_tokens'] = array( '#type' => 'checkbox', - '#title' => t('Allow tokens'), - '#default_value' => isset($field['enable_tokens']) ? $field['enable_tokens'] : 1, + '#title' => t('Allow user-entered tokens'), + '#default_value' => isset($field['enable_tokens']) ? $field['enable_tokens'] : 1, '#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.'), ); } $form['display'] = array( - '#tree' => true, + '#tree' => TRUE, ); $form['display']['url_cutoff'] = array( '#type' => 'textfield', @@ -100,25 +103,28 @@ function link_field_settings($op, $field) { 'user' => t('Allow the user to choose'), ); $form['attributes'] = array( - '#tree' => true, + '#tree' => TRUE, ); $form['attributes']['target'] = array( '#type' => 'radios', '#title' => t('Link Target'), - '#default_value' => $field['attributes']['target'] ? $field['attributes']['target'] : 'default', + '#default_value' => empty($field['attributes']['target']) ? 'default' : $field['attributes']['target'], '#options' => $target_options, ); $form['attributes']['rel'] = array( '#type' => 'textfield', '#title' => t('Rel Attribute'), '#description' => t('When output, this link will have this rel attribute. The most common usage is rel="nofollow" which prevents some search engines from spidering entered links.'), - '#default_value' => $field['attributes']['rel'] ? $field['attributes']['rel'] : '', + '#default_value' => empty($field['attributes']['rel']) ? '' : $field['attributes']['rel'], + '#field_prefix' => 'rel = "', + '#field_suffix' => '"', + '#size' => 20, ); $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'] : '', + '#description' => t('When output, this link will have have this class attribute. Multiple classes should be separated by spaces.'), + '#default_value' => empty($field['attributes']['class']) ? '' : $field['attributes']['class'], ); return $form; @@ -133,47 +139,14 @@ function link_field_settings($op, $field) { case 'database columns': return array( - 'url' => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE), - 'title' => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE), + 'url' => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'sortable' => TRUE), + 'title' => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'sortable' => TRUE), 'attributes' => array('type' => 'text', 'size' => 'medium', 'not null' => FALSE), ); - case 'filters': - return array( - 'default' => array( - 'name' => t('URL'), - 'operator' => 'views_handler_operator_like', - 'handler' => 'views_handler_operator_like', - ), - 'title' => array( - 'name' => t('Title'), - 'operator' => 'views_handler_operator_like', - 'handler' => 'views_handler_operator_like', - ), - 'protocol' => array( - 'name' => t('Protocol'), - 'list' => drupal_map_assoc(variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'))), - 'operator' => 'views_handler_operator_or', - 'handler' => 'link_views_protocol_filter_handler', - ), - ); - - case 'arguments': - return array( - 'content: '. $field['field_name'] .'_url' => array( - 'name' => t('Link URL') .': '. t($field['widget']['label']) .' ('. $field['field_name'] .')', - 'handler' => 'link_views_argument_handler', - ), - 'content: '. $field['field_name'] .'_title' => array( - 'name' => t('Link Title') .': '. t($field['widget']['label']) .' ('. $field['field_name'] .')', - 'handler' => 'link_views_argument_handler', - ), - 'content: '. $field['field_name'] .'_target' => array( - 'name' => t('Link Target') .': '. t($field['widget']['label']) .' ('. $field['field_name'] .')', - 'handler' => 'link_views_argument_handler', - ), - ); - + case 'views data': + module_load_include('inc', 'link', 'views/link.views'); + return link_views_content_field_data($field); } } @@ -192,7 +165,7 @@ function theme_link_field_settings($form) { } /** - * Implementation of hook_field_is_empty(). + * Implementation of hook_content_is_empty(). */ function link_content_is_empty($item, $field) { if (empty($item['title']) && empty($item['url'])) { @@ -210,13 +183,12 @@ function link_field($op, &$node, $field, &$items, $teaser, $page) { foreach ($items as $delta => $item) { _link_load($items[$delta], $delta); } - return $items; break; case 'validate': $optional_field_found = FALSE; - foreach($items as $delta => $value) { - _link_widget_validate($items[$delta],$delta, $field, $node, $optional_field_found); + foreach ($items as $delta => $value) { + _link_validate($items[$delta], $delta, $field, $node, $optional_field_found); } if ($field['url'] == 'optional' && $field['title'] == 'optional' && $field['required'] && !$optional_field_found) { @@ -224,15 +196,15 @@ function link_field($op, &$node, $field, &$items, $teaser, $page) { } break; - case 'process form values': - foreach($items as $delta => $value) { - _link_widget_process($items[$delta],$delta, $field, $node); + case 'presave': + foreach ($items as $delta => $value) { + _link_process($items[$delta], $delta, $field, $node); } break; case 'sanitize': foreach ($items as $delta => $value) { - link_item_sanitize($items[$delta], $delta, $field, $node); + _link_sanitize($items[$delta], $delta, $field, $node); } break; } @@ -244,7 +216,7 @@ function link_field($op, &$node, $field, &$items, $teaser, $page) { function link_widget_info() { return array( 'link' => array( - 'label' => 'Text Fields for Title and URL', + 'label' => 'Link', 'field types' => array('link'), 'multiple values' => CONTENT_HANDLE_CORE, ), @@ -258,11 +230,15 @@ function link_widget(&$form, &$form_state, $field, $items, $delta = 0) { $element = array( '#type' => $field['widget']['type'], '#default_value' => isset($items[$delta]) ? $items[$delta] : '', + '#title' => $field['widget']['label'], + '#weight' => $field['widget']['weight'], + '#description' => $field['widget']['description'], + '#required' => $field['required'], + '#field' => $field, ); return $element; } - function _link_load(&$item, $delta = 0) { // Unserialize the attributes array. $item['attributes'] = unserialize($item['attributes']); @@ -290,16 +266,16 @@ function _link_validate(&$item, $delta, $field, $node, &$optional_field_found) { if ($item['url'] && !(isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url'] && !$field['required'])) { // Validate the link. if (link_validate_url(trim($item['url'])) == FALSE) { - form_set_error($field['field_name'] .']['. $delta. '][url', t('Not a valid URL.')); + form_set_error($field['field_name'] .']['. $delta .'][url', t('Not a valid URL.')); } // Require a title for the link if necessary. if ($field['title'] == 'required' && strlen(trim($item['title'])) == 0) { - form_set_error($field['field_name'] .']['. $delta. '][title', t('Titles are required for all links.')); + form_set_error($field['field_name'] .']['. $delta .'][title', t('Titles are required for all links.')); } } // Require a link if we have a title. if ($field['url'] !== 'optional' && strlen($item['title']) > 0 && strlen(trim($item['url'])) == 0) { - form_set_error($field['field_name'] .']['. $delta. '][url', t('You cannot enter a title without a link url.')); + form_set_error($field['field_name'] .']['. $delta .'][url', t('You cannot enter a title without a link url.')); } // In a totally bizzaro case, where URLs and titles are optional but the field is required, ensure there is at least one link. if ($field['url'] == 'optional' && $field['title'] == 'optional' && (strlen(trim($item['url'])) != 0 || strlen(trim($item['title'])) != 0)) { @@ -309,7 +285,7 @@ function _link_validate(&$item, $delta, $field, $node, &$optional_field_found) { /** * Cleanup user-entered values for a link field according to field settings. - * + * * @param $item * A single link item, usually containing url, title, and attributes. * @param $delta @@ -320,21 +296,27 @@ function _link_validate(&$item, $delta, $field, $node, &$optional_field_found) { * The node containing this link. */ function _link_sanitize(&$item, $delta, &$field, &$node) { + // Don't try to process empty links. + if (empty($item['url']) && empty($item['title'])) { + return; + } + // Replace URL tokens. if (module_exists('token') && $field['enable_tokens']) { - $node = node_load($node->nid); // Necessary for nodes in views. - $item['url'] = token_replace($item['url'], 'node', $node); + // Load the node if necessary for nodes in views. + $token_node = isset($node->nid) ? node_load($node->nid) : $node; + $item['url'] = token_replace($item['url'], 'node', $token_node); } $type = link_validate_url($item['url']); $url = link_cleanup_url($item['url']); - // Seperate out the anchor if any. + // Separate out the anchor if any. if (strpos($url, '#') !== FALSE) { $item['fragment'] = substr($url, strpos($url, '#') + 1); $url = substr($url, 0, strpos($url, '#')); } - // Seperate out the query string if any. + // Separate out the query string if any. if (strpos($url, '?') !== FALSE) { $item['query'] = substr($url, strpos($url, '?') + 1); $url = substr($url, 0, strpos($url, '?')); @@ -343,9 +325,9 @@ function _link_sanitize(&$item, $delta, &$field, &$node) { $item['url'] = $url; // Create a shortened URL for display. - $display_url = $type == LINK_EMAIL ? str_replace('mailto:', '', $url) : url($url, array('query' => $item['query'], 'fragment' => $item['fragment'], 'absolute' => TRUE)); + $display_url = $type == LINK_EMAIL ? str_replace('mailto:', '', $url) : url($url, array('query' => isset($item['query']) ? $item['query'] : NULL, 'fragment' => isset($item['fragment']) ? $item['fragment'] : NULL, 'absolute' => TRUE)); if ($field['display']['url_cutoff'] && strlen($display_url) > $field['display']['url_cutoff']) { - $display_url = substr($display_url, 0, $field['display']['url_cutoff']) . "..."; + $display_url = substr($display_url, 0, $field['display']['url_cutoff']) ."..."; } $item['display_url'] = $display_url; @@ -359,15 +341,17 @@ function _link_sanitize(&$item, $delta, &$field, &$node) { } // Replace tokens. if (module_exists('token') && ($field['title'] == 'value' || $field['enable_tokens'])) { - $node = node_load($node->nid); // Necessary for nodes in views. - $title = token_replace($title, 'node', $node); + // Load the node if necessary for nodes in views. + $token_node = isset($node->nid) ? node_load($node->nid) : $node; + $title = filter_xss(token_replace($title, 'node', $token_node), array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u')); + $item['html'] = TRUE; } $item['display_title'] = empty($title) ? $item['display_url'] : $title; // Add attributes defined at the widget level $attributes = array(); - if (is_array($item['attributes'])) { - foreach($item['attributes'] as $attribute => $attbvalue) { + if (!empty($item['attributes']) && is_array($item['attributes'])) { + foreach ($item['attributes'] as $attribute => $attbvalue) { if (isset($item['attributes'][$attribute]) && $field['attributes'][$attribute] == 'user') { $attributes[$attribute] = $attbvalue; } @@ -375,12 +359,19 @@ function _link_sanitize(&$item, $delta, &$field, &$node) { } // Add attributes defined at the field level if (is_array($field['attributes'])) { - foreach($field['attributes'] as $attribute => $attbvalue) { + foreach ($field['attributes'] as $attribute => $attbvalue) { if (!empty($attbvalue) && $attbvalue != 'default' && $attbvalue != 'user') { $attributes[$attribute] = $attbvalue; } } } + // Remove the rel=nofollow for internal links. + if ($type != LINK_EXTERNAL && isset($attributes['rel']) && strpos($attributes['rel'], 'nofollow') !== FALSE) { + $attributes['rel'] = str_replace('nofollow', '', $attributes['rel']); + if (empty($attributes['rel'])) { + unset($attributes['rel']); + } + } $item['attributes'] = $attributes; // Add the widget label. @@ -401,12 +392,18 @@ function link_theme() { 'link_formatter_plain' => array( 'arguments' => array('element' => NULL), ), + 'link_formatter_url' => array( + 'arguments' => array('element' => NULL), + ), 'link_formatter_short' => array( 'arguments' => array('element' => NULL), ), 'link_formatter_label' => array( 'arguments' => array('element' => NULL), ), + 'link_formatter_separate' => array( + 'arguments' => array('element' => NULL), + ), 'link' => array( 'arguments' => array('element' => NULL), ), @@ -415,56 +412,42 @@ function link_theme() { /** * FAPI theme for an individual text elements. - * - * The textfield or textarea is already rendered by the - * textfield or textarea themes and the html output - * lives in $element['#children']. Override this theme to - * make custom changes to the output. - * - * $element['#field_name'] contains the field name - * $element['#delta'] is the position of this element in the group */ function theme_link($element) { drupal_add_css(drupal_get_path('module', 'link') .'/link.css'); - if ($element['#delta'] == 0) { - + // Prefix single value link fields with the name of the field. + if (empty($element['#field']['multiple'])) { + if (isset($element['url']) && isset($element['title'])) { + $element['url']['#title'] = $element['#title'] .' '. $element['url']['#title']; + $element['title']['#title'] = $element['#title'] .' '. $element['title']['#title']; + } + elseif ($element['url']) { + $element['url']['#title'] = $element['#title']; + } } $output = ''; $output .= ''; - if ($element['attributes']) { - $output .= ''; + if (!empty($element['attributes']['target'])) { + $output .= ''; } return $output; } -/* - * Implementation of FAPI hook_elements(). - * - * Any FAPI callbacks needed for individual widgets can be declared here, - * and the element will be passed to those callbacks for processing. - * - * Drupal will automatically theme the element using a theme with - * the same name as the hook_elements key. - * - * Autocomplete_path is not used by text_widget but other widgets can use it - * (see nodereference and userreference). +/** + * Implementation of hook_elements(). */ function link_elements() { $elements = array(); $elements['link'] = array( '#input' => TRUE, - '#columns' => array('url', 'title'), - '#delta' => 0, '#process' => array('link_process'), - '#autocomplete_path' => FALSE, - '#theme' => 'link_widget_form_row', ); return $elements; } @@ -485,10 +468,10 @@ function link_process($element, $edit, $form_state, $form) { '#maxlength' => '255', '#title' => t('URL'), '#description' => $element['#description'], - '#required' => ($delta == 0 && $field['title'] != 'optional') ? $element['#required'] : FALSE, + '#required' => ($delta == 0 && $field['url'] !== 'optional') ? $element['#required'] : FALSE, '#default_value' => isset($element['#value']['url']) ? $element['#value']['url'] : NULL, ); - if ($field['title'] != 'none') { + if ($field['title'] != 'none' && $field['title'] != 'value') { $element['title'] = array( '#type' => 'textfield', '#maxlength' => '255', @@ -496,13 +479,14 @@ function link_process($element, $edit, $form_state, $form) { '#required' => ($delta == 0 && $field['title'] == 'required') ? $field['required'] : FALSE, '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL, ); - } - if ($field['attributes']['target'] == 'user') { + if (!empty($field['attributes']['target']) && $field['attributes']['target'] == 'user') { + $attributes = is_array($element['#value']['attributes'])? $element['#value']['attributes'] : unserialize($element['#value']['attributes']); $element['attributes']['target'] = array( '#type' => 'checkbox', '#title' => t('Open URL in a New Window'), '#return_value' => "_blank", + '#default_value' => isset($attributes['target']) ? $attributes['target'] : NULL, ); } return $element; @@ -514,12 +498,17 @@ function link_process($element, $edit, $form_state, $form) { function link_field_formatter_info() { return array( 'default' => array( - 'label' => t('Default, as link with title'), + 'label' => t('Title, as link (default)'), + 'field types' => array('link'), + 'multiple values' => CONTENT_HANDLE_CORE, + ), + 'url' => array( + 'label' => t('URL, as link'), 'field types' => array('link'), 'multiple values' => CONTENT_HANDLE_CORE, ), 'plain' => array( - 'label' => t('Plain, as the text URL'), + 'label' => t('URL, as plain text'), 'field types' => array('link'), 'multiple values' => CONTENT_HANDLE_CORE, ), @@ -533,6 +522,11 @@ function link_field_formatter_info() { 'field types' => array('link'), 'multiple values' => CONTENT_HANDLE_CORE, ), + 'separate' => array( + 'label' => t('Separate title and URL'), + 'field types' => array('link'), + 'multiple values' => CONTENT_HANDLE_CORE, + ), ); } @@ -545,7 +539,7 @@ function theme_link_formatter_default($element) { return l($element['#item']['display_title'], $element['#item']['url'], $element['#item']); } // If only a title, display the title. - else { + elseif (!empty($element['#item']['display_title'])) { return check_plain($element['#item']['display_title']); } } @@ -554,7 +548,14 @@ function theme_link_formatter_default($element) { * Theme function for 'plain' text field formatter. */ function theme_link_formatter_plain($element) { - return empty($element['#item']['url']) ? check_plain($element['#item']['title']) : check_plain($element['#item']['url']); + return empty($element['#item']['url']) ? check_plain($element['#item']['title']) : url($element['#item']['url'], $element['#item']); +} + +/** + * Theme function for 'url' text field formatter. + */ +function theme_link_formatter_url($element) { + return $element['#item']['url'] ? l($element['#item']['display_url'], $element['#item']['url'], $element['#item']) : ''; } /** @@ -565,113 +566,59 @@ function theme_link_formatter_short($element) { } /** - * Theme function for 'short' text field formatter. + * Theme function for 'label' text field formatter. */ function theme_link_formatter_label($element) { return $element['#item']['url'] ? l($element['#item']['label'], $element['#item']['url'], $element['#item']) : ''; } + /** - * Views module argument handler for link fields + * Theme function for 'separate' text field formatter. */ -function link_views_argument_handler($op, &$query, $argtype, $arg = '') { - if ($op == 'filter') { - $field_name = substr($argtype['type'], 9, strrpos($argtype['type'], '_') - 9); - $column = substr($argtype['type'], strrpos($argtype['type'], '_') + 1); - } - else { - $field_name = substr($argtype, 9, strrpos($argtype, '_') - 9); - $column = substr($argtype, strrpos($argtype, '_') + 1); - } +function theme_link_formatter_separate($element) { + $class = empty($element['#item']['attributes']['class']) ? '' : ' '. $element['#item']['attributes']['class']; + unset($element['#item']['attributes']['class']); - // Right now the only attribute we support in views in 'target', but - // other attributes of the href tag could be added later. - if ($column == 'target') { - $attribute = $column; - $column = 'attributes'; - } - - $field = content_fields($field_name); - $db_info = content_database_info($field); - $main_column = $db_info['columns'][$column]; - - // The table name used here is the Views alias for the table, not the actual - // table name. - $table = 'node_data_'. $field['field_name']; + $output = ''; + $output .= ''; + return $output; +} - switch ($op) { - case 'summary': - $query->ensure_table($table); - $query->add_field($main_column['column'], $table); - return array('field' => $table .'.'. $main_column['column']); - break; +function link_token_list($type = 'all') { + if ($type == 'field' || $type == 'all') { + $tokens = array(); - case 'filter': - $query->ensure_table($table); - if ($column == 'attributes') { - // Because attributes are stored serialized, our only option is to also - // serialize the data we're searching for and use LIKE to find similar data. - $query->add_where($table .'.'. $main_column['column'] ." LIKE '%%%s%'", serialize($attribute) . serialize($arg)); - } - else { - $query->add_where($table .'.'. $main_column['column'] ." = '%s'", $arg); - } - break; + $tokens['link']['url'] = t("Link URL"); + $tokens['link']['title'] = t("Link title"); + $tokens['link']['view'] = t("Formatted html link"); - case 'link': - $item = array(); - foreach ($db_info['columns'] as $column => $attributes) { - $view_column_name = $attributes['column']; - $item[$column] = $query->$view_column_name; - } + return $tokens; + } +} - return l(content_format($field, $item, 'plain'), $arg .'/'. $query->$main_column['column'], array(), NULL, NULL, FALSE, TRUE); +function link_token_values($type, $object = NULL) { + if ($type == 'field') { + $item = $object[0]; - case 'sort': - break; + $tokens['url'] = $item['url']; + $tokens['title'] = $item['title']; + $tokens['view'] = isset($item['view']) ? $item['view'] : ''; - case 'title': - $item = array(key($db_info['columns']) => $query); - return content_format($field, $item); - break; + return $tokens; } } /** - * Views modules filter handler for link protocol filtering + * Implementation of hook_views_api(). */ -function link_views_protocol_filter_handler($op, $filter, $filterinfo, &$query) { - global $db_type; - - $protocols = $filter['value']; - $field = $filterinfo['field']; - // $table is not the real table name but the views alias. - $table = 'node_data_'. $filterinfo['content_field']['field_name']; - - foreach ($protocols as $protocol) { - // Simple case, the URL begins with the specified protocol. - $condition = $table .'.'. $field .' LIKE \''. $protocol .'%\''; - - // More complex case, no protocol specified but is automatically cleaned up - // by link_cleanup_url(). RegEx is required for this search operation. - if ($protocol == 'http') { - if ($db_type == 'pgsql') { - // PostGreSQL code has NOT been tested. Please report any problems to the link issue queue. - // pgSQL requires all slashes to be double escaped in regular expressions. - // See http://www.postgresql.org/docs/8.1/static/functions-matching.html#FUNCTIONS-POSIX-REGEXP - $condition .= ' OR '. $table .'.'. $field .' ~* \''. '^(([a-z0-9]([a-z0-9\\-_]*\\.)+)('. LINK_DOMAINS .'|[a-z][a-z]))' .'\''; - } - else { - // mySQL requires backslashes to be double (triple?) escaped within character classes. - // See http://dev.mysql.com/doc/refman/5.0/en/string-comparison-functions.html#operator_regexp - $condition .= ' OR '. $table .'.'. $field .' REGEXP \''. '^(([a-z0-9]([a-z0-9\\\-_]*\.)+)('. LINK_DOMAINS .'|[a-z][a-z]))' .'\''; - } - } - - $where_conditions[] = $condition; - } - - $query->ensure_table($table); - $query->add_where(implode(' '. $filter['operator'] .' ', $where_conditions)); +function link_views_api() { + return array( + 'api' => 2, + 'path' => drupal_get_path('module', 'link') .'/views', + ); } /** @@ -687,12 +634,12 @@ function link_cleanup_url($url, $protocol = "http") { if ($type == LINK_EXTERNAL) { // Check if there is no protocol specified. - $protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i",$url); + $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\-_]*\.)+)('. LINK_DOMAINS .'|[a-z]{2}))/i',$url); + $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)('. LINK_DOMAINS .'|[a-z]{2}))/i', $url); if (!empty($domain_match)) { - $url = $protocol."://".$url; + $url = $protocol ."://". $url; } } } @@ -713,40 +660,42 @@ 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) . '):\/\/)'; + $protocol = '(('. implode("|", $allowed_protocols) .'):\/\/)'; $authentication = '([a-z0-9]+(:[a-z0-9]+)?@)'; - $domain = '((([a-z0-9]([a-z0-9\-_\[\]]*\.))+)('. LINK_DOMAINS .'|[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})'; + $domain = '(([a-z0-9]([a-z0-9\-_\[\]])*)(\.(([a-z0-9\-_\[\]])+\.)*('. LINK_DOMAINS .'|[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,5}))'; // Pattern specific to eternal links. - $external_pattern = '/^' . $protocol . '?'. $authentication . '?' . '(' . $domain . '|' . $ipv4 . '|' . $ipv6 . ' |localhost)' . $port . '?'; + $external_pattern = '/^'. $protocol .'?'. $authentication .'?('. $domain .'|'. $ipv4 .'|'. $ipv6 .' |localhost)'. $port .'?'; // Pattern specific to internal links. $internal_pattern = "/^([a-z0-9_\-+\[\]]+)"; - $directories = "(\/[a-z0-9_\-\.~+%=&,$'():;*@\[\]]*)*"; - $query = "(\/?\?([?a-z0-9+_\-\.\/%=&,$'():;*@\[\]]*))"; + $directories = "(\/[a-z0-9_\-\.~+%=&,$'!():;*@\[\]]*)*"; + // Yes, four backslashes == a single backslash. + $query = "(\/?\?([?a-z0-9+_|\-\.\/\\\\%=&,$'():;*@\[\]]*))"; $anchor = "(#[a-z0-9_\-\.~+%=&,$'():;*@\[\]]*)"; // The rest of the path for a standard URL. - $end = $directories . '?' . $query . '?' . $anchor . '?' . '$/i'; + $end = $directories .'?'. $query .'?'. $anchor .'?'.'$/i'; $user = '[a-zA-Z0-9_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+'; - $email_pattern = '/^mailto:' . $user . '@' . '(' . $domain . '|' . $ipv4 .'|'. $ipv6 . '|localhost)' . $query . '$/'; + $email_pattern = '/^mailto:'. $user .'@'.'('. $domain .'|'. $ipv4 .'|'. $ipv6 .'|localhost)'. $query .'?$/'; - if (preg_match($external_pattern . $end, $text)) { - return LINK_EXTERNAL; - } - elseif (preg_match($internal_pattern . $end, $text)) { - return LINK_INTERNAL; + if (strpos($text, '') === 0) { + return LINK_FRONT; } - elseif (in_array('mailto', $allowed_protocols) && preg_match($email_pattern, $text)) { + if (in_array('mailto', $allowed_protocols) && preg_match($email_pattern, $text)) { return LINK_EMAIL; } - elseif (strpos($text, '') === 0) { - return LINK_FRONT; + if (preg_match($internal_pattern . $end, $text)) { + return LINK_INTERNAL; } + if (preg_match($external_pattern . $end, $text)) { + return LINK_EXTERNAL; + } + return FALSE; } diff --git a/tests/link.crud.test b/tests/link.crud.test new file mode 100644 index 0000000000000000000000000000000000000000..fe871b1de93db10e9650820d4b82ee097e2609bd --- /dev/null +++ b/tests/link.crud.test @@ -0,0 +1,48 @@ + t('Link CRUD - Basic API tests'), + 'description' => t('Tests the field CRUD (create, read, update, delete) API. Requires Schema module.', array('@schema_link' => 'http://www.drupal.org/project/schema')), + 'group' => t('Link'), + ); + } + + function setUp() { + parent::setUp('link', 'views'); + $this->loginWithPermissions(); + } + + /** + * All we're doing here is creating a content type, creating a simple link field + * on that content type, and making sure said field exists in the database. + */ + function testLinkCreateFieldAPI() { + $this->acquireContentTypes(1); + + $field = $this->createField(array('type' => 'link', 'widget_type' => 'link'), 0); + + $this->assertEqual(1, 1, print_r($this->content_types, TRUE)); + $this->assertEqual(1, 1, print_r($field, TRUE)); + + $table_schema = drupal_get_schema(); + $this->assertEqual(1, 1, print_r(array_keys($table_schema), TRUE)); + // Check the schema - the values should be in the per-type table. + $this->assertSchemaMatchesTables(array( + 'per_type' => array( + $this->content_types[0]->type => array($field['field_name'] => array('url', 'title', 'attributes')), + ), + )); + } +} diff --git a/translations/fr.po b/translations/fr.po index 126e882976c48a7461d35b7d358e729cbf963594..3493b034f2aa053b1fb17de11bf4eb568e397d9e 100644 --- a/translations/fr.po +++ b/translations/fr.po @@ -1,197 +1,165 @@ -# $Id$ -# -# French translation of Drupal (general) -# Copyright YEAR NAME -# Generated from files: -# link.module,v 1.24.2.29 2008/09/22 04:20:33 quicksketch -# link.info,v 1.2 2008/03/31 06:50:42 quicksketch -# link_views_handler_argument_target.inc,v 1.1.2.1 2008/09/06 21:45:25 quicksketch -# views/link.views.inc: n/a -# link_views_handler_filter_protocol.inc,v 1.1.2.1 2008/09/06 21:45:25 quicksketch -# msgid "" msgstr "" -"Project-Id-Version: French translation for drupal link module\n" -"POT-Creation-Date: 2009-03-26 18:34+0100\n" -"PO-Revision-Date: 2009-03-26 19:03+0100\n" +"Project-Id-Version: link drupal module : french translation\n" +"POT-Creation-Date: 2007-12-08 13:14+0100\n" +"PO-Revision-Date: \n" "Last-Translator: Sylvain Moreau \n" -"Language-Team: Sylvain Moreau, OWS \n" +"Language-Team: Sylvain Moreau \n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" +"Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n>1);\n" -"X-Poedit-Language: French\n" -"X-Poedit-Country: FRANCE\n" -#: link.module:21;564 -#: link.info:0 -msgid "Link" -msgstr "Lien" - -#: link.module:22 -msgid "Store a title, href, and attributes in the database to assemble a link." -msgstr "Stocker un titre, href et des attributs dans la base de données pour les assembler dans un lien." - -#: link.module:39 -msgid "Optional URL" -msgstr "URL optionnelle" - -#: link.module:42 -msgid "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." -msgstr "Si coché, le champ URL est facultatif, et soumettre seulement un titre sera acceptable. Si l'URL est omise, le titre sera affiché comme un texte simple." - -#: link.module:46 +#: link.module:51 msgid "Optional Title" msgstr "Titre Optionnel" -#: link.module:47 +#: link.module:52 msgid "Required Title" msgstr "Titre Obligatoire" -#: link.module:48 +#: link.module:53 msgid "Static Title: " msgstr "Titre Statique : " -#: link.module:49 +#: link.module:54 msgid "No Title" msgstr "Aucun Titre" -#: link.module:54 +#: link.module:59;176 msgid "Link Title" msgstr "Titre du Lien" -#: link.module:57 -msgid "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 token module is installed, the static title value may use any other node field as its value. Static and token-based titles may include most inline XHTML tags such as strong, em, img, span, etc." -msgstr "Si le titre du lien est facultatif ou obligatoire, un champ sera affiché à l'utilisateur final. Si le titre du lien est statique, le lien utilisera toujours le même titre. Si le module token est installé, le titre statique peut utiliser n'importe quel autre champ du noeud pour sa valeur. Les titres statiques et basés sur des jetons (tokens) peuvent contenir la plupart des balises XHTML en ligne, telles que strong, em, img, span, etc." +#: link.module:62 +msgid "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 token module is installed, the static title value may use any other node field as its value." +msgstr "Si le titre du lien est optionnel ou obligatoire, un champ sera affiché à l'utilisateur final. Si le titre du lien est statique, le lien utilisera toujours le même titre. Si le module token est installé, la valeur du titre statique peut utiliser n'importe quel autre champ du noeud en tant que valeur." -#: link.module:72 +#: link.module:77;268 +#, fuzzy msgid "Placeholder tokens" msgstr "Ebauches de jetons" -#: link.module:73 +#: link.module:78 +#, fuzzy msgid "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." msgstr "Les ébauches de jetons suivantes peuvent être utilisées à la fois dans les chemins et dans les titres. Lorsqu'elles sont utilisées dans un chemin ou un titre, elles seront remplacées par les valeurs appropriées." -#: link.module:81 -msgid "Allow user-entered tokens" -msgstr "Autoriser les jetons (tokens) saisis par l'utilisateur" +#: link.module:86 +msgid "Allow tokens" +msgstr "Autoriser les jetons (tokens)" -#: link.module:83 +#: link.module:88 msgid "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." -msgstr "Le fait de cocher cette case permettra aux utilisateurs de saisir des jetons (tokens) dans les URL et les Titres dans le formulaire d'édition du noeud. Ceci n'affecte pas les configurations de champ sur cette page." +msgstr "Le fait de cocher cette case permettra aux utilisateurs de saisir des jetons dans les URL et les Titres dans le formulaire d'édition du noeud. Ceci n'affecte pas les configurations de champ sur cette page." -#: link.module:92 +#: link.module:97 msgid "URL Display Cutoff" msgstr "Coupure de l'Affichage de l'URL" -#: link.module:94 +#: link.module:99 msgid "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." msgstr "Si l'utilisateur n'inclue pas de titre pour ce lien, l'URL sera utilisée en tant que titre. A quel endroit le lien devra-t-il être coupé et terminé par une ellipse (…) ? Laissez vide pour aucune limite." -#: link.module:100 +#: link.module:105 msgid "Default (no target attribute)" msgstr "Par Défaut (aucun attribut de cible)" -#: link.module:101 +#: link.module:106 msgid "Open link in window root" msgstr "Ouvrir le lien dans la fenêtre courante" -#: link.module:102 +#: link.module:107 msgid "Open link in new window" msgstr "Ouvrir le lien dans une nouvelle fenêtre" -#: link.module:103 +#: link.module:108 msgid "Allow the user to choose" msgstr "Autoriser l'utilisateur à choisir" -#: link.module:110 +#: link.module:115;180 msgid "Link Target" msgstr "Cible du Lien" -#: link.module:116 +#: link.module:121 msgid "Rel Attribute" msgstr "Attribut Rel" -#: link.module:117 +#: link.module:122 msgid "When output, this link will have this rel attribute. The most common usage is rel="nofollow" which prevents some search engines from spidering entered links." msgstr "Quand il sera affiché, ce lien aura cet attribut rel. L'usage le plus commun est rel="nofollow" qui empêche certains moteurs de recherche d'aspirer les liens suivis." -#: link.module:125 +#: link.module:127 msgid "Additional CSS Class" msgstr "Classe CSS additionnelle" -#: link.module:126 -msgid "When output, this link will have have this class attribute. Multiple classes should be separated by spaces." -msgstr "Lors de son rendu, ce lien aura cet attribut de classe. Les classes multiples doivent être séparées par des espaces." +#: link.module:128 +msgid "When output, this link will have have this class attribute. Multiple classes should be seperated by spaces." +msgstr "A l'affichage, ce lien possédera cet attribut de classe. Les classes multiples doivent être séparées par des espaces." -#: link.module:133 +#: link.module:135 msgid "A default title must be provided if the title is a static value" msgstr "Un titre pas défaut doit être fourni si le titre est une valeur statique" -#: link.module:195 -msgid "At least one title or URL must be entered." -msgstr "Vous devez saisir au moins un titre ou une URL." +#: link.module:152;404 +msgid "URL" +msgstr "URL" + +#: link.module:157;416 +msgid "Title" +msgstr "Titre" + +#: link.module:162 +msgid "Protocol" +msgstr "Protocole" + +#: link.module:172 +msgid "Link URL" +msgstr "Url du Lien" #: link.module:269 +#, fuzzy +msgid "The following placeholder tokens can be used in both titles and URLs. When used in a URL or title, they will be replaced with the appropriate values." +msgstr "Les ébauches de jetons suivantes peuvent être utilisées à la fois dans les titres et dans les URL. Lorsqu'elles sont utilisées dans une URL ou un titre, elles seront remplacées par les valeurs appropriées." + +#: link.module:317 +msgid "More Links" +msgstr "Plus de Liens" + +#: link.module:343 msgid "Not a valid URL." msgstr "Cette URL n'est pas valide." -#: link.module:273 +#: link.module:347 msgid "Titles are required for all links." msgstr "Les titres sont obligatoires pour tous les liens." -#: link.module:278 -msgid "You cannot enter a title without a link url." -msgstr "Vous ne pouvez pas saisir un titre dans une url de lien." +#: link.module:352 +msgid "You cannot enter a title without a link." +msgstr "Vous ne pouvez pas saisir de titre sans un lien." -#: link.module:470 -msgid "URL" -msgstr "URL" - -#: link.module:479 -#: views/link_views_handler_argument_target.inc:26 -msgid "Title" -msgstr "Titre" - -#: link.module:487 +#: link.module:424 msgid "Open URL in a New Window" msgstr "Ouvril l'URL dans une Nouvelle Fenêtre" -#: link.module:500 -msgid "Title, as link (default)" -msgstr "Titre, en tant que lien (par défaut)" - -#: link.module:505 -msgid "URL, as link" -msgstr "URL, en tant que lien" +#: link.module:532 +msgid "Default, as link with title" +msgstr "Par défaut, comme lien avec titre" -#: link.module:510 -msgid "URL, as plain text" -msgstr "URL, texte simple" +#: link.module:536 +msgid "Plain, as the text URL" +msgstr "Plat, comme l'URL texte" -#: link.module:515 +#: link.module:540 msgid "Short, as link with title \"Link\"" msgstr "Court, comme lien avec le titre \"Lien\"" -#: link.module:520 +#: link.module:544 msgid "Label, as link with label as title" msgstr "Etiquette, comme lien avec l'étiquette comme titre" -#: link.module:525 -msgid "Separate title and URL" -msgstr "Titre et URL séparés" - -#: link.module:593 -msgid "Link URL" -msgstr "Url du Lien" - -#: link.module:594 -msgid "Link title" -msgstr "Titre du lien" - -#: link.module:595 -msgid "Formatted html link" -msgstr "Lien html formaté" +#: link.module:602 +#: link.info:0 +msgid "Link" +msgstr "Lien" #: link.module:0 msgid "link" @@ -205,75 +173,3 @@ msgstr "Définit les types de champs \"lien simple\"." msgid "CCK" msgstr "CCK" -#: views/link.views.inc:36 -msgid "@label URL" -msgstr "URL de @label" - -#: views/link.views.inc:42;75;92 -msgid "Content" -msgstr "Contenu" - -#: views/link.views.inc:43;56 -msgid "@label title" -msgstr "Titre de @label" - -#: views/link.views.inc:76;80 -msgid "@label protocol" -msgstr "Protocole de @label" - -#: views/link.views.inc:93;97 -msgid "@label target" -msgstr "Cible de @label" - -#: views/link_views_handler_argument_target.inc:28 -msgid "The title to use when this argument is present; it will override the title of the view and titles from previous arguments. You can use percent substitution here to replace with argument titles. Use \"%1\" for the first argument, \"%2\" for the second, etc." -msgstr "Le titre à utiliser lorsque cet argument est présent; il écrasera le titre de la vue et les titres provenant des arguments précédents. Vous pouvez utiliser ici la substitution avec le caractère pourcentagen pour remplacer avec les titres des arguments. Utilisez \"%1\" pour le premier argument, \"%2\" pour le second, etc." - -#: views/link_views_handler_argument_target.inc:41 -msgid "Action to take if argument is not present" -msgstr "Action à prendre si l'argument n'est pas présent" - -#: views/link_views_handler_argument_target.inc:53 -msgid "Wildcard" -msgstr "Joker" - -#: views/link_views_handler_argument_target.inc:56 -msgid "If this value is received as an argument, the argument will be ignored; i.e, \"all values\"" -msgstr "Si cette valeur est reçue en tant qu'argument, l'argument sera ignoré; c'est-à-dire \"toutes les valeurs\"" - -#: views/link_views_handler_argument_target.inc:62 -msgid "Wildcard title" -msgstr "Titre du joker" - -#: views/link_views_handler_argument_target.inc:65 -msgid "The title to use for the wildcard in substitutions elsewhere." -msgstr "Le titre à utiliser pour le joker dans les substitutions partout ailleurs." - -#: views/link_views_handler_argument_target.inc:88 -msgid "Validator" -msgstr "Validateur" - -#: views/link_views_handler_argument_target.inc:92 -msgid "" -msgstr "" - -#: views/link_views_handler_argument_target.inc:128 -msgid "Action to take if argument does not validate" -msgstr "Action à prendre si l'argument n'est pas valide" - -#: views/link_views_handler_filter_protocol.inc:24 -msgid "Is one of" -msgstr "Est l'un de" - -#: views/link_views_handler_filter_protocol.inc:25 -msgid "=" -msgstr "=" - -#: views/link_views_handler_filter_protocol.inc:58 -msgid "Protocol" -msgstr "Protocole" - -#: views/link_views_handler_filter_protocol.inc:63 -msgid "The protocols displayed here are those globally available. You may add more protocols by modifying the filter_allowed_protocols variable in your installation." -msgstr "Les protocoles affichés ici sont ceux disponibles de manière globale. Vous pouvez ajouter plus de protocoles en modifiant la variable filter_allowed_protocols de votre installation." - diff --git a/views/link.views.inc b/views/link.views.inc new file mode 100644 index 0000000000000000000000000000000000000000..e7ec7f73338c6426999a0f12949c9019a1602aed --- /dev/null +++ b/views/link.views.inc @@ -0,0 +1,112 @@ + array( + 'path' => drupal_get_path('module', 'link') .'/views', + ), + 'handlers' => array( + 'link_views_handler_argument_target' => array( + 'parent' => 'views_handler_argument', + ), + 'link_views_handler_filter_protocol' => array( + 'parent' => 'views_handler_filter_string', + ), + ), + ); +} + +/** + * Return CCK Views data for the link_field_settings($op == 'views data'). + */ +function link_views_content_field_data($field) { + // Build the automatic views data provided for us by CCK. + // This creates all the information necessary for the "url" field. + $data = content_views_field_views_data($field); + + $db_info = content_database_info($field); + $table_alias = content_views_tablename($field); + $field_types = _content_field_types(); + + // Tweak the automatic views data for the link "url" field. + // Set the filter title to "@label URL" + $data[$table_alias][$field['field_name'] .'_url']['filter']['title'] = t('@label URL', array('@label' => t($field_types[$field['type']]['label']))) .': '. t($field['widget']['label']); + // Remove the argument handling for URLs. + unset($data[$table_alias][$field['field_name'] .'_url']['argument']); + + // Build out additional views data for the link "title" field. + $data[$table_alias][$field['field_name'] .'_title'] = array( + 'group' => t('Content'), + 'title' => t('@label title', array('@label' => t($field_types[$field['type']]['label']))) .': '. t($field['widget']['label']) .' ('. $field['field_name'] .')', + 'help' => $data[$table_alias][$field['field_name'] .'_url']['help'], + 'argument' => array( + 'field' => $db_info['columns']['title']['column'], + 'tablename' => $db_info['table'], + 'handler' => 'content_handler_argument_string', + 'click sortable' => TRUE, + 'name field' => '', // TODO, mimic content.views.inc :) + 'content_field_name' => $field['field_name'], + 'allow_empty' => TRUE, + ), + 'filter' => array( + 'field' => $db_info['columns']['title']['column'], + 'title' => t('@label title', array('@label' => t($field_types[$field['type']]['label']))), + 'tablename' => $db_info['table'], + 'handler' => 'content_handler_filter_string', + 'additional fields' => array(), + 'content_field_name' => $field['field_name'], + 'allow_empty' => TRUE, + ), + 'sort' => array( + 'field' => $db_info['columns']['title']['column'], + 'tablename' => $db_info['table'], + 'handler' => 'content_handler_sort', + 'content_field_name' => $field['field_name'], + 'allow_empty' => TRUE, + ), + ); + + // Build out additional Views filter for the link "protocol" pseudo field. + // TODO: Add a protocol argument. + $data[$table_alias][$field['field_name'] .'_protocol'] = array( + 'group' => t('Content'), + 'title' => t('@label protocol', array('@label' => t($field_types[$field['type']]['label']))) .': '. t($field['widget']['label']) .' ('. $field['field_name'] .')', + 'help' => $data[$table_alias][$field['field_name'] .'_url']['help'], + 'filter' => array( + 'field' => $db_info['columns']['url']['column'], + 'title' => t('@label protocol', array('@label' => t($field_types[$field['type']]['label']))), + 'tablename' => $db_info['table'], + 'handler' => 'link_views_handler_filter_protocol', + 'additional fields' => array(), + 'content_field_name' => $field['field_name'], + 'allow_empty' => TRUE, + ), + ); + + // Build out additional Views argument for the link "target" pseudo field. + // TODO: Add a target filter. + $data[$table_alias][$field['field_name'] .'_target'] = array( + 'group' => t('Content'), + 'title' => t('@label target', array('@label' => t($field_types[$field['type']]['label']))) .': '. t($field['widget']['label']) .' ('. $field['field_name'] .')', + 'help' => $data[$table_alias][$field['field_name'] .'_url']['help'], + 'argument' => array( + 'field' => $db_info['columns']['attributes']['column'], + 'title' => t('@label target', array('@label' => t($field_types[$field['type']]['label']))) .': '. t($field['widget']['label']) .' ('. $field['field_name'] .')', + 'tablename' => $db_info['table'], + 'handler' => 'link_views_handler_argument_target', + 'additional fields' => array(), + 'content_field_name' => $field['field_name'], + 'allow_empty' => TRUE, + ), + ); + + return $data; +} diff --git a/views/link_views_handler_argument_target.inc b/views/link_views_handler_argument_target.inc new file mode 100644 index 0000000000000000000000000000000000000000..c8187b497a67d9703edee4a4315d7bafcf28ab6e --- /dev/null +++ b/views/link_views_handler_argument_target.inc @@ -0,0 +1,150 @@ +default_actions(); + + $form['title'] = array( + '#prefix' => '
', + '#suffix' => '
', + '#type' => 'textfield', + '#title' => t('Title'), + '#default_value' => $this->options['title'], + '#description' => t('The title to use when this argument is present; it will override the title of the view and titles from previous arguments. You can use percent substitution here to replace with argument titles. Use "%1" for the first argument, "%2" for the second, etc.'), + ); + + $form['clear_start'] = array( + '#value' => '
', + ); + + $form['defaults_start'] = array( + '#value' => '
', + ); + + $form['default_action'] = array( + '#type' => 'radios', + '#title' => t('Action to take if argument is not present'), + '#default_value' => $this->options['default_action'], + ); + + $form['defaults_stop'] = array( + '#value' => '
', + ); + + $form['wildcard'] = array( + '#prefix' => '
', + // prefix and no suffix means these two items will be grouped together. + '#type' => 'textfield', + '#title' => t('Wildcard'), + '#size' => 20, + '#default_value' => $this->options['wildcard'], + '#description' => t('If this value is received as an argument, the argument will be ignored; i.e, "all values"'), + ); + + $form['wildcard_substitution'] = array( + '#suffix' => '
', + '#type' => 'textfield', + '#title' => t('Wildcard title'), + '#size' => 20, + '#default_value' => $this->options['wildcard_substitution'], + '#description' => t('The title to use for the wildcard in substitutions elsewhere.'), + ); + + $form['clear_stop'] = array( + '#value' => '
', + ); + + $options = array(); + $validate_options = array(); + foreach ($defaults as $id => $info) { + $options[$id] = $info['title']; + if (empty($info['default only'])) { + $validate_options[$id] = $info['title']; + } + if (!empty($info['form method'])) { + $this->{$info['form method']}($form, $form_state); + } + } + + $form['default_action']['#options'] = $options; + + $form['validate_type'] = array( + '#type' => 'select', + '#title' => t('Validator'), + '#default_value' => $this->options['validate_type'], + ); + + $validate_types = array('none' => t('')); + $plugins = views_fetch_plugin_data('argument validator'); + foreach ($plugins as $id => $info) { + $valid = TRUE; + if (!empty($info['type'])) { + $valid = FALSE; + if (empty($this->definition['validate type'])) { + continue; + } + foreach ((array) $info['type'] as $type) { + if ($type == $this->definition['validate type']) { + $valid = TRUE; + break; + } + } + } + + // If we decide this validator is ok, add it to the list. + if ($valid) { + $plugin = views_get_plugin('argument validator', $id); + if ($plugin) { + $plugin->init($this->view, $this, $id); + if ($plugin->access()) { + $plugin->validate_form($form, $form_state, $id); + $validate_types[$id] = $info['title']; + } + } + } + } + + asort($validate_types); + $form['validate_type']['#options'] = $validate_types; + // Show this gadget if *anything* but 'none' is selected + + $form['validate_fail'] = array( + '#type' => 'select', + '#title' => t('Action to take if argument does not validate'), + '#default_value' => $this->options['validate_fail'], + '#options' => $validate_options, + ); + } + + /** + * Set up the query for this argument. + * + * The argument sent may be found at $this->argument. + */ + function query() { + $this->ensure_my_table(); + // Because attributes are stored serialized, our only option is to also + // serialize the data we're searching for and use LIKE to find similar data. + $this->query->add_where(0, $this->table_alias .'.'. $this->real_field ." LIKE '%%%s%'", serialize(array('target' => $this->argument))); + } +} diff --git a/views/link_views_handler_filter_protocol.inc b/views/link_views_handler_filter_protocol.inc new file mode 100644 index 0000000000000000000000000000000000000000..2d79e99d14343afee80816670b4ff1d84efdbbd1 --- /dev/null +++ b/views/link_views_handler_filter_protocol.inc @@ -0,0 +1,107 @@ + array( + 'title' => t('Is one of'), + 'short' => t('='), + 'method' => 'op_protocol', + 'values' => 1, + ), + ); + + return $operators; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $form['case'] = array( + '#type' => 'value', + '#value' => 0, + ); + } + + /** + * Provide a select list to choose the desired protocols. + */ + function value_form(&$form, &$form_state) { + // We have to make some choices when creating this as an exposed + // filter form. For example, if the operator is locked and thus + // not rendered, we can't render dependencies; instead we only + // render the form items we need. + $which = 'all'; + if (!empty($form_state['exposed']) && empty($this->options['expose']['operator'])) { + $which = in_array($this->operator, $this->operator_values(1)) ? 'value' : 'none'; + } + + if ($which == 'all' || $which == 'value') { + $form['value'] = array( + '#type' => 'select', + '#title' => t('Protocol'), + '#default_value' => $this->value, + '#options' => drupal_map_assoc(variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'))), + '#multiple' => 1, + '#size' => 4, + '#description' => t('The protocols displayed here are those globally available. You may add more protocols by modifying the filter_allowed_protocols variable in your installation.'), + ); + } + } + + /** + * Filter down the query to include only the selected protocols. + */ + function op_protocol($field, $upper) { + global $db_type; + + $protocols = $this->value; + + $where_conditions = array(); + foreach ($protocols as $protocol) { + // Simple case, the URL begins with the specified protocol. + $condition = $field .' LIKE \''. $protocol .'%\''; + + // More complex case, no protocol specified but is automatically cleaned up + // by link_cleanup_url(). RegEx is required for this search operation. + if ($protocol == 'http') { + if ($db_type == 'pgsql') { + // PostGreSQL code has NOT been tested. Please report any problems to the link issue queue. + // pgSQL requires all slashes to be double escaped in regular expressions. + // See http://www.postgresql.org/docs/8.1/static/functions-matching.html#FUNCTIONS-POSIX-REGEXP + $condition .= ' OR '. $field .' ~* \''.'^(([a-z0-9]([a-z0-9\\-_]*\\.)+)('. LINK_DOMAINS .'|[a-z][a-z]))'.'\''; + } + else { + // mySQL requires backslashes to be double (triple?) escaped within character classes. + // See http://dev.mysql.com/doc/refman/5.0/en/string-comparison-functions.html#operator_regexp + $condition .= ' OR '. $field .' REGEXP \''.'^(([a-z0-9]([a-z0-9\\\-_]*\.)+)('. LINK_DOMAINS .'|[a-z][a-z]))'.'\''; + } + } + + $where_conditions[] = $condition; + } + + $this->query->add_where($this->options['group'], implode(' '. $this->operator .' ', $where_conditions)); + } +}