text_noderef.module in Text or Nodereference 7
Same filename and directory in other branches
Text or nodereference field formatter for a text field and autocomplete widget.
File
text_noderef.moduleView source
<?php
/**
* @file
* Text or nodereference field formatter for a text field and autocomplete widget.
*/
/**
* Implements hook_menu().
*/
function text_noderef_menu() {
$items = array();
$items['text_noderef/%/%'] = array(
'title' => 'Text or Nodereference',
'page callback' => 'text_noderef_json',
'page arguments' => array(
1,
2,
),
'access callback' => 'text_noderef_access',
'access arguments' => array(
1,
2,
),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Check access to the menu callback of the text_noderef widget.
*
* @param $bundle_name
* Bundle name.
* @param $field_name
* Field name.
*/
function text_noderef_access($bundle_name, $field_name) {
return user_access('access content') && ($field = field_info_instance('node', $field_name, $bundle_name)) && field_access('view', $field, 'node') && field_access('edit', $field, 'node');
}
/**
* Helper function for text_noderef_json().
*
* @param $string
* The string used as needle.
* @param $field
* Field definition whose widget is being used.
* @param $result
* EFQ result array.
*
* @return
* Array of matches found.
*/
function _text_noderef_check_result($string, $field, $result) {
$matches = array();
if (isset($result['node'])) {
$cnt = 0;
reset($result['node']);
while ($cnt <= 10 && ($node = each($result['node']))) {
$node = node_load($node['value']->nid);
if (!$field['widget']['settings']['case_sensitive']) {
$cnt++;
$matches[$node->title] = $node->title;
}
else {
if ($field['widget']['settings']['autocomplete_match'] == 'contains' && strpos($node->title, $string) !== FALSE) {
$cnt++;
$matches[$node->title] = $node->title;
}
if ($field['widget']['settings']['autocomplete_match'] == 'starts_with' && drupal_substr($node->title, 0, drupal_strlen($string)) == $string) {
$cnt++;
$matches[$node->title] = $node->title;
}
}
}
}
return $matches;
}
/**
* Menu callback; Retrieve a pipe delimited string of autocomplete suggestions.
*
* @param $bundle_name
* Bundle name whose field widget is being used.
* @param $field_name
* Field name whose widget is being used.
* @param $string
* The string used as needle.
*/
function text_noderef_json($bundle_name, $field_name, $string = '') {
$field = field_info_instance('node', $field_name, $bundle_name);
$matches = array();
// Do not act upon empty search string nor on unrelated fields.
if ($field['widget']['type'] == 'text_noderef_textfield' && $string != '') {
$needle = db_like($string) . '%';
if ($field['widget']['settings']['autocomplete_match'] == 'contains') {
$needle = '%' . $needle;
}
// Step 1/3: Check the available field data.
$query = db_select('node', 'n');
$query
->innerJoin('field_data_' . $field_name, 'fdf', 'n.nid = %alias.entity_id');
$query
->condition('n.status', 1)
->fields('fdf', array(
$field_name . '_value',
))
->condition($field_name . '_value', $needle, 'LIKE')
->groupBy($field_name . '_value')
->orderBy($field_name . '_value')
->range(0, 10)
->addTag('node_access');
$result = $query
->execute();
foreach ($result as $value) {
$value = $value->{$field_name . '_value'};
$matches[$value] = $value;
}
// Step 2/3: Check the node titles.
$query = new EntityFieldQuery();
$query
->entityCondition('entity_type', 'node');
$bundles = array_filter($field['widget']['settings']['bundles']);
if ($bundles) {
$query
->entityCondition('bundle', $bundles, 'IN');
}
$query
->propertyCondition('status', 1)
->propertyCondition('title', $needle, 'LIKE')
->propertyOrderBy('title');
$result = $query
->execute();
$matches += _text_noderef_check_result($string, $field, $result);
// Step 3/3: Merge and sanitize output.
asort($matches, SORT_LOCALE_STRING);
foreach ($matches as &$value) {
// Add a class wrapper for a few required CSS overrides.
$value = '<div class="reference-autocomplete">' . check_plain($value) . '</div>';
}
}
drupal_json_output($matches);
}
/**
* Implements hook_field_widget_info().
*/
function text_noderef_field_widget_info() {
return array(
'text_noderef_textfield' => array(
'label' => t('Text or node reference field'),
'description' => t('Autocomplete for existing text field data and/or given node titles.'),
'field types' => array(
'text',
),
'settings' => array(
'size' => 60,
'bundles' => array(),
'autocomplete_match' => 'starts_with',
'case_sensitive' => 0,
),
),
);
}
/**
* Implements hook_field_widget_form().
*/
function text_noderef_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
if ($instance['widget']['type'] == 'text_noderef_textfield') {
// Reusing text_textfield widget type from text.module, then adding the
// autocomplete stuff.
$instance['widget']['type'] = 'text_textfield';
$element = text_field_widget_form($form, $form_state, $field, $instance, $langcode, $items, $delta, $element);
$element['value']['#autocomplete_path'] = 'text_noderef/' . $element['#bundle'] . '/' . $element['#field_name'];
}
return $element;
}
/**
* Implements hook_field_widget_settings_form().
*/
function text_noderef_field_widget_settings_form($field, $instance) {
$form = array();
$widget = $instance['widget'];
$settings = $widget['settings'];
if ($widget['type'] == 'text_noderef_textfield') {
// Reusing text_textfield widget type from text.module, then adding the
// autocomplete-related stuff.
$instance['widget']['type'] = 'text_textfield';
$form = text_field_widget_settings_form($field, $instance);
$bundles = array();
foreach (node_type_get_types() as $bundle) {
$bundles[$bundle->type] = $bundle->name;
}
$form['bundles'] = array(
'#type' => 'checkboxes',
'#title' => t('Content types for autocompleting on titles'),
'#options' => $bundles,
'#default_value' => $settings['bundles'],
'#description' => t('Autocompleting is done on all the content types if none of them is selected.'),
);
$form['autocomplete_match'] = array(
'#type' => 'select',
'#title' => t('Autocomplete matching'),
'#default_value' => $settings['autocomplete_match'],
'#options' => array(
'starts_with' => t('Starts with'),
'contains' => t('Contains'),
),
'#description' => t('Select the method used to collect autocomplete suggestions. Note that <em>Contains</em> can cause performance issues on sites with thousands of records.'),
);
$form['case_sensitive'] = array(
'#type' => 'radios',
'#title' => t('Case sensitive'),
'#default_value' => $settings['case_sensitive'],
'#options' => array(
0 => t('Disabled'),
1 => t('Enabled'),
),
);
$form['case_sensitive']['#description'] = theme('item_list', array(
'items' => array(
t('Case-insensitive queries are implemented using the <a href="!function-lower-url">LOWER()</a> function in combination with the <a href="!operator-like-url">LIKE</a> operator.', array(
'!function-lower-url' => 'http://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_lower',
'!operator-like-url' => 'http://dev.mysql.com/doc/refman/5.1/en/string-comparison-functions.html#operator_like',
)),
t('Note that MySQL might ignore case sensitivity depending on the collation used in your database definition (see <a href="!mysql-i18n-l10n-url">Internationalization and Localization</a> chapter in the MySQL manual). If you need case insensitive checks, it is recommended (for performance reasons) to use a case insensitive collation as well (such as utf8_general_ci), rather than disabling the case sensitive option here.', array(
'!mysql-i18n-l10n-url' => 'http://dev.mysql.com/doc/refman/5.1/en/internationalization-localization.html',
)),
t('You may want to create an expression index using the LOWER() function to speed up this kind of queries in PostgreSQL (See <a href="!indexes-expressional-url">Indexes on Expressions</a>).', array(
'!indexes-expressional-url' => 'http://www.postgresql.org/docs/8.4/static/indexes-expressional.html',
)),
),
));
}
return $form;
}
/**
* Implements hook_form_FORM_ID_alter().
*
* 'text_noderef_textfield' field instances should not have any type of text
* processing beyond 'Plain text'.
*/
function text_noderef_form_field_ui_field_edit_form_alter(&$form, $form_state) {
if ($form['#instance']['widget']['type'] == 'text_noderef_textfield') {
$form['instance']['settings']['text_processing'] = array(
'#type' => 'value',
'#value' => 0,
);
}
}
/**
* Implements hook_field_formatter_info().
*/
function text_noderef_field_formatter_info() {
return array(
'text_noderef_default' => array(
'label' => t('Text or nodereference'),
'field types' => array(
'text',
),
),
);
}
/**
* Implements hook_field_formatter_view().
*/
function text_noderef_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
// This function may be called several times on a single page, so cache
// the already seen texts to a local static variable to speed up non-first
// calls.
$element = array();
$settings = $display['settings'];
if ($display['type'] == 'text_noderef_default') {
$cache =& drupal_static(__FUNCTION__);
$key = $instance['field_name'] . '@' . $instance['bundle'];
foreach ($items as $delta => $item) {
if (!isset($cache[$key]) || !isset($cache[$key][$item['value']])) {
$query = new EntityFieldQuery();
$query
->entityCondition('entity_type', 'node');
$bundles = array_filter($instance['widget']['settings']['bundles']);
if ($bundles) {
$query
->entityCondition('bundle', $bundles, 'IN');
}
$query
->propertyCondition('status', 1)
->propertyCondition('title', $item['value'], 'LIKE')
->propertyOrderBy('title');
$result = $query
->execute();
if (isset($result['node'])) {
reset($result['node']);
$node = each($result['node']);
$cache[$key][$item['value']] = l($item['value'], 'node/' . $node['value']->nid);
}
else {
$cache[$key][$item['value']] = $item['safe_value'];
}
}
$element[$delta] = array(
'#markup' => $cache[$key][$item['value']],
);
}
}
return $element;
}
/**
* Implements hook_content_migrate_instance_alter().
*/
function text_noderef_content_migrate_instance_alter(&$instance_value, $field_value) {
if ($instance_value['widget']['module'] == 'text_noderef') {
// The formatter name changed.
foreach ($instance_value['display'] as $context => $settings) {
if ($settings['type'] == 'text_text_noderef') {
$instance_value['display'][$context]['type'] = 'text_noderef_default';
}
}
}
}
/**
* Implements hook_content_migrate_field_alter().
*
* @see http://drupal.org/node/1417626
*/
function text_noderef_content_migrate_field_alter(&$field_value, $instance_value) {
if ($field_value['type'] == 'text' && $instance_value['widget']['type'] == 'text_noderef_textfield' && empty($field_value['settings']['max_length'])) {
// $field_value['type'] must not be changed, because our widget and field
// formatter are kicking in only for these. OTOH, a max_length must be
// defined for 'simple' text fields, so we must follow this way: let's
// define a 'high enough' (TM) value for this.
$field_value['settings']['max_length'] = 999;
}
}
Functions
Name![]() |
Description |
---|---|
text_noderef_access | Check access to the menu callback of the text_noderef widget. |
text_noderef_content_migrate_field_alter | Implements hook_content_migrate_field_alter(). |
text_noderef_content_migrate_instance_alter | Implements hook_content_migrate_instance_alter(). |
text_noderef_field_formatter_info | Implements hook_field_formatter_info(). |
text_noderef_field_formatter_view | Implements hook_field_formatter_view(). |
text_noderef_field_widget_form | Implements hook_field_widget_form(). |
text_noderef_field_widget_info | Implements hook_field_widget_info(). |
text_noderef_field_widget_settings_form | Implements hook_field_widget_settings_form(). |
text_noderef_form_field_ui_field_edit_form_alter | Implements hook_form_FORM_ID_alter(). |
text_noderef_json | Menu callback; Retrieve a pipe delimited string of autocomplete suggestions. |
text_noderef_menu | Implements hook_menu(). |
_text_noderef_check_result | Helper function for text_noderef_json(). |