Skip to content
text.module 26.3 KiB
Newer Older
<?php
// $Id$

/**
 * @file
 * Defines simple text field types.
 */

/**
 */
function text_theme() {
  return array(
    'text_textarea' => array(
      'arguments' => array('element' => NULL),
    ),
    'text_textfield' => array(
      'arguments' => array('element' => NULL),
    ),
    'field_formatter_text_default' => array(
      'arguments' => array('element' => NULL),
    ),
    'field_formatter_text_plain' => array(
      'arguments' => array('element' => NULL),
    ),
    'field_formatter_text_trimmed' => array(
      'arguments' => array('element' => NULL),
    ),
    'field_formatter_text_summary_or_trimmed' => array(
      'arguments' => array('element' => NULL),
    ),
 *
 * Field settings:
 *   - max_length: the maximum length for a varchar field.
 * Instance settings:
 *   - text_processing: whether text input filters should be used.
 *   - display_summary: whether the summary field should be displayed.
 *     When empty and not displayed the summary will take its value from the
 *     trimmed value of the main text field.
 */
function text_field_info() {
  return array(
    'text' => array(
      'label' => t('Text'),
      'description' => t('This field stores varchar text in the database.'),
      'settings' => array('max_length' => 255),
      'instance_settings' => array('text_processing' => 0),
      'default_widget' => 'text_textfield',
      'default_formatter' => 'text_default',
    ),
    'text_long' => array(
      'label' => t('Long text'),
      'description' => t('This field stores long text in the database.'),
      'instance_settings' => array('text_processing' => 0),
      'default_widget' => 'text_textarea',
      'default_formatter' => 'text_default',
    ),
    'text_with_summary' => array(
      'label' => t('Long text and summary'),
      'description' => t('This field stores long text in the database along with optional summary text.'),
      'settings' => array('max_length' => ''),
      'instance_settings' => array('text_processing' => 1, 'display_summary' => 0),
      'default_widget' => 'text_textarea_with_summary',
      'default_formatter' => 'text_summary_or_trimmed',
    ),
function text_field_schema($field) {
  switch ($field['type']) {
    case 'text':
      $columns = array(
        'value' => array(
          'type' => 'varchar',
          'length' => $field['settings']['max_length'],
          'not null' => FALSE,
        ),
      );
      break;
    case 'text_long':
      $columns = array(
        'value' => array(
          'type' => 'text',
          'size' => 'big',
          'not null' => FALSE,
        ),
      );
      break;
    case 'text_with_summary':
      $columns = array(
        'value' => array(
          'type' => 'text',
          'size' => 'big',
          'not null' => FALSE,
        ),
        'summary' => array(
          'type' => 'text',
          'size' => 'big',
          'not null' => FALSE,
        ),
      );
      break;
  }
  $columns += array(
    'format' => array(
      'type' => 'int',
      'unsigned' => TRUE,
      'not null' => FALSE,
    ),
  );
  return array(
    'columns' => $columns,
    'indexes' => array(
      'format' => array('format'),
    ),
  );
/**
 * Implement hook_field_settings_form().
 */
function text_field_settings_form($field, $instance) {
  $settings = $field['settings'];

  $form['max_length'] = array(
    '#type' => 'textfield',
    '#title' => t('Maximum length'),
    '#default_value' => $settings['max_length'],
    '#required' => FALSE,
    '#description' => t('The maximum length of the field in characters. Leave blank for an unlimited size.'),
    '#element_validate' => array('_element_validate_integer_positive'),
  );

  return $form;
}

/**
 * Implement hook_field_instance_settings_form().
 */
function text_field_instance_settings_form($field, $instance) {
  $settings = $instance['settings'];

  $form['text_processing'] = array(
    '#type' => 'radios',
    '#title' => t('Text processing'),
    '#default_value' => $settings['text_processing'],
    '#options' => array(
      t('Plain text'),
      t('Filtered text (user selects input format)'),
    ),
  );
  if ($field['type'] == 'text_with_summary') {
    $form['display_summary'] = array(
      '#type' => 'checkbox',
      '#title' => t('Summary input'),
      '#default_value' => $settings['display_summary'],
      '#description' => t('This allows authors to input an explicit summary, to be displayed instead of the automatically trimmed text when using the "Summary or trimmed" display format.'),
    );
  }

  return $form;
}

 * - 'text_value_max_length': The value exceeds the maximum length.
 * - 'text_summary_max_length': The summary exceeds the maximum length.
function text_field_validate($obj_type, $object, $field, $instance, $langcode, $items, &$errors) {
    foreach (array('value' => t('full text'), 'summary' => t('summary')) as $column => $desc) {
      if (!empty($item[$column])) {
        if (!empty($field['settings']['max_length']) && drupal_strlen($item[$column]) > $field['settings']['max_length']) {
          switch ($column) {
            case 'value':
              $message = t('%name: the text may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length']));
              break;
            case 'summary':
              $message = t('%name: the summary may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length']));
              break;
          }
          $errors[$field['field_name']][$langcode][$delta][] = array(
/**
 * Implement hook_field_load().
 *
 * Where possible, generate the sanitized version of each field early so that
 * it is cached in the field cache. This avoids looking up from the filter cache
 * separately.
 * @see text_field_sanitize().
 */
function text_field_load($obj_type, $objects, $field, $instances, $langcode, &$items) {
  foreach ($objects as $id => $object) {
    foreach ($items[$id] as $delta => $item) {
      if (!empty($instances[$id]['settings']['text_processing'])) {
        // Only process items with a cacheable format, the rest will be
        // handled by text_field_sanitize().
        $format = $item['format'];
        if (filter_format_allowcache($format)) {
          $items[$id][$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $langcode, FALSE) : '';
            $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $langcode, FALSE) : '';
        $items[$id][$delta]['safe'] = check_plain($item['value']);
        if ($field['type'] == 'text_with_summary') {
          $items[$id][$delta]['safe_summary'] = check_plain($item['summary']);
        }
function text_field_sanitize($obj_type, $object, $field, $instance, $langcode, &$items) {
  foreach ($items as $delta => $item) {
    // Only sanitize items which were not already processed inside
    // text_field_load(), i.e. items with uncacheable text formats, or coming
    // from a form preview.
    if (!isset($items[$delta]['safe'])) {
      if (!empty($instance['settings']['text_processing'])) {
        $items[$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $langcode) : '';
          $items[$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $langcode) : '';
        $items[$delta]['safe'] = check_plain($item['value']);
          $items[$delta]['safe_summary'] = check_plain($item['summary']);
 */
function text_field_is_empty($item, $field) {
  if (empty($item['value']) && (string)$item['value'] !== '0') {
    return TRUE;
  }
  return FALSE;
}

/**
 */
function text_field_formatter_info() {
  return array(
    'text_default' => array(
      'label' => t('Default'),
      'field types' => array('text', 'text_long', 'text_with_summary'),
    ),
    'text_plain' => array(
      'label' => t('Plain text'),
      'field types' => array('text', 'text_long', 'text_with_summary'),

    // The text_trimmed formatter displays the trimmed version of the
    // full element of the field. It is intended to be used with text
    // and text_long fields. It also works with text_with_summary
    // fields though the text_summary_or_trimmed formatter makes more
    // sense for that field type.
    'text_trimmed' => array(
      'label' => t('Trimmed'),
      'field types' => array('text', 'text_long', 'text_with_summary'),
    ),

    // The 'summary or trimmed' field formatter for text_with_summary
    // fields displays returns the summary element of the field or, if
    // the summary is empty, the trimmed version of the full element
    // of the field.
    'text_summary_or_trimmed' => array(
      'label' => t('Summary or trimmed'),
      'field types' => array('text_with_summary'),
    ),
  );
}

/**
 * Theme function for 'default' text field formatter.
 */
function theme_field_formatter_text_default($element) {
  return $element['#item']['safe'];
}

/**
 * Theme function for 'plain' text field formatter.
 */
function theme_field_formatter_text_plain($element) {
  return strip_tags($element['#item']['safe']);
}

/**
 * Theme function for 'trimmed' text field formatter.
 */
function theme_field_formatter_text_trimmed($element) {
  $field = field_info_field($element['#field_name']);
  $instance = field_info_instance($element['#field_name'], $element['#bundle']);
  return text_summary($element['#item']['safe'], $instance['settings']['text_processing'] ? $element['#item']['format'] : NULL);
}

/**
 * Theme function for 'summary or trimmed' field formatter for
 * text_with_summary fields.  This formatter returns the summary
 * element of the field or, if the summary is empty, the trimmed
 * version of the full element of the field.
 */
function theme_field_formatter_text_summary_or_trimmed($element) {
  $field = field_info_field($element['#field_name']);
  $instance = field_info_instance($element['#field_name'], $element['#bundle']);

  if (!empty($element['#item']['safe_summary'])) {
    return $element['#item']['safe_summary'];
  }
  else {
    $size = variable_get('teaser_length_' . $element['#bundle'], 600);
    return text_summary($element['#item']['safe'], $instance['settings']['text_processing'] ? $element['#item']['format'] : NULL, $size);
  }
}

/**
 * Generate a trimmed, formatted version of a text field value.
 *
 * If the end of the summary is not indicated using the <!--break--> delimiter
 * then we generate the summary automatically, trying to end it at a sensible
 * place such as the end of a paragraph, a line break, or the end of a
 * sentence (in that order of preference).
 *
 * @param $text
 *   The content for which a summary will be generated.
 * @param $format
 *   The format of the content.
 *   If the PHP filter is present and $text contains PHP code, we do not
 *   split it up to prevent parse errors.
 *   If the line break filter is present then we treat newlines embedded in
 *   $text as line breaks.
 *   If the htmlcorrector filter is present, it will be run on the generated
 *   summary (if different from the incoming $text).
 * @param $size
 *   The desired character length of the summary. If omitted, the default
 *   value will be used. Ignored if the special delimiter is present
 *   in $text.
 * @return
 *   The generated summary.
 */
function text_summary($text, $format = NULL, $size = NULL) {

  if (!isset($size)) {
    // What used to be called 'teaser' is now called 'summary', but
    // the variable 'teaser_length' is preserved for backwards compatibility.
    $size = variable_get('teaser_length', 600);
  }

  // Find where the delimiter is in the body
  $delimiter = strpos($text, '<!--break-->');

  // If the size is zero, and there is no delimiter, the entire body is the summary.
  if ($size == 0 && $delimiter === FALSE) {
    return $text;
  }

  // If a valid delimiter has been specified, use it to chop off the summary.
  if ($delimiter !== FALSE) {
    return substr($text, 0, $delimiter);
  }

  // We check for the presence of the PHP evaluator filter in the current
  // format. If the body contains PHP code, we do not split it up to prevent
  // parse errors.
  if (isset($format)) {
    $filters = filter_list_format($format);
    if (isset($filters['php_code']) && strpos($text, '<?') !== FALSE) {
      return $text;
    }
  }

  // If we have a short body, the entire body is the summary.
  if (drupal_strlen($text) <= $size) {
    return $text;
  }

  // If the delimiter has not been specified, try to split at paragraph or
  // sentence boundaries.

  // The summary may not be longer than maximum length specified. Initial slice.
  $summary = truncate_utf8($text, $size);

  // Store the actual length of the UTF8 string -- which might not be the same
  // as $size.
  $max_rpos = strlen($summary);

  // How much to cut off the end of the summary so that it doesn't end in the
  // middle of a paragraph, sentence, or word.
  // Initialize it to maximum in order to find the minimum.
  $min_rpos = $max_rpos;

  // Store the reverse of the summary. We use strpos on the reversed needle and
  // haystack for speed and convenience.
  $reversed = strrev($summary);

  // Build an array of arrays of break points grouped by preference.
  $break_points = array();

  // A paragraph near the end of sliced summary is most preferable.
  $break_points[] = array('</p>' => 0);

  // If no complete paragraph then treat line breaks as paragraphs.
  $line_breaks = array('<br />' => 6, '<br>' => 4);
  // Newline only indicates a line break if line break converter
  // filter is present.
    $line_breaks["\n"] = 1;
  }
  $break_points[] = $line_breaks;

  // If the first paragraph is too long, split at the end of a sentence.
  $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1);

  // Iterate over the groups of break points until a break point is found.
  foreach ($break_points as $points) {
    // Look for each break point, starting at the end of the summary.
    foreach ($points as $point => $offset) {
      // The summary is already reversed, but the break point isn't.
      $rpos = strpos($reversed, strrev($point));
      if ($rpos !== FALSE) {
        $min_rpos = min($rpos + $offset, $min_rpos);
      }
    }

    // If a break point was found in this group, slice and stop searching.
    if ($min_rpos !== $max_rpos) {
      // Don't slice with length 0. Length must be <0 to slice from RHS.
      $summary = ($min_rpos === 0) ? $summary : substr($summary, 0, 0 - $min_rpos);
      break;
    }
  }

  // If the htmlcorrector filter is present, apply it to the generated summary.
 *
 * Here we indicate that the field module will handle
 * the default value and multiple values for these widgets.
 *
 * Callbacks can be omitted if default handing is used.
 * They're included here just so this module can be used
 * as an example for custom modules that might do things
 * differently.
 */
function text_field_widget_info() {
  return array(
    'text_textfield' => array(
      'label' => t('Text field'),
      'field types' => array('text'),
      'settings' => array('size' => 60),
    ),
    'text_textarea' => array(
      'label' => t('Text area (multiple rows)'),
      'field types' => array('text_long'),
      'settings' => array('rows' => 5),
    ),
    'text_textarea_with_summary' => array(
      'label' => t('Text area with a summary'),
      'field types' => array('text_with_summary'),
      'settings' => array('rows' => 20, 'summary_rows' => 5),
    ),
/**
 * Implement hook_field_widget_settings_form().
 */
function text_field_widget_settings_form($field, $instance) {
  $widget = $instance['widget'];
  $settings = $widget['settings'];

  if ($widget['type'] == 'text_textfield') {
    $form['size'] = array(
      '#type' => 'textfield',
      '#title' => t('Size of textfield'),
      '#default_value' => $settings['size'],
      '#required' => TRUE,
      '#element_validate' => array('_element_validate_integer_positive'),
    );
  }
  else {
    $form['rows'] = array(
      '#type' => 'textfield',
      '#title' => t('Rows'),
      '#default_value' => $settings['rows'],
      '#required' => TRUE,
      '#element_validate' => array('_element_validate_integer_positive'),
    );
  }

  return $form;
}

 *
 * 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_field_widget but other
 * widgets can use it (see nodereference and userreference).
 */
function text_elements() {
  return array(
    'text_textfield' => array(
      '#input' => TRUE,
      '#columns' => array('value'), '#delta' => 0,
      '#process' => array('text_textfield_elements_process'),
      '#theme_wrappers' => array('text_textfield'),
      '#autocomplete_path' => FALSE,
    'text_textarea' => array(
      '#input' => TRUE,
      '#columns' => array('value', 'format'), '#delta' => 0,
      '#process' => array('text_textarea_elements_process'),
      '#theme_wrappers' => array('text_textarea'),
      '#filter_value' => FILTER_FORMAT_DEFAULT,
    ),
    'text_textarea_with_summary' => array(
      '#input' => TRUE,
      '#columns' => array('value', 'format', 'summary'), '#delta' => 0,
      '#process' => array('text_textarea_with_summary_process'),
      '#theme_wrappers' => array('text_textarea'),
 *
 * Attach a single form element to the form. It will be built out and
 * validated in the callback(s) listed in hook_elements. We build it
 * out in the callbacks rather than here in hook_field_widget so it can be
 * plugged into any module that can provide it with valid
 * $field information.
 *
 * Field module will set the weight, field name and delta values
 * for each form element.
 *
 * If there are multiple values for this field, the field module will
 * call this function as many times as needed.
 *
 * @param $form
 *   the entire form array, $form['#node'] holds node information
 * @param $form_state
 *   the form_state, $form_state['values'][$field['field_name']]
 *   holds the field's form values.
 * @param $field
 *   The field structure.
 * @param $instance
 *   the field instance array
 * @param $langcode
 *   The language associated to $items.
 * @param $items
 *   array of default values for this field
 * @param $delta
 *   the order of this item in the array of subelements (0, 1, 2, etc)
 *
 * @return
 *   the form item for a single element for this field
 */
function text_field_widget(&$form, &$form_state, $field, $instance, $langcode, $items, $delta = 0) {
  $element = array(
    '#type' => $instance['widget']['type'],
    '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
  );
  if (!empty($instance['settings']['text_processing'])) {
    $element['#value_callback'] = 'text_field_widget_formatted_text_value';
  }

 */
function text_field_widget_error($element, $error) {
  switch ($error['error']) {
    case 'text_summary_max_length':
      $error_element = $element[$element['#columns'][1]];
      break;

    default:
      $error_element = $element[$element['#columns'][0]];
      break;
  }

  form_error($error_element, $error['message']);
/**
 * Process an individual element.
 *
 * Build the form element. When creating a form using FAPI #process,
 * note that $element['#value'] is already set.
 *
 * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
 *
 * TODO: For widgets to be actual FAPI 'elements', reusable outside of a
 * 'field' context, they shoudn't rely on $field and $instance. The bits of
 * information needed to adjust the behavior of the 'element' should be
 * extracted in hook_field_widget() above.
 */
function text_textfield_elements_process($element, $form_state, $form) {
  $field = $form['#fields'][$element['#field_name']]['field'];
  $instance = $form['#fields'][$element['#field_name']]['instance'];
  $field_key = $element['#columns'][0];
  $delta = $element['#delta'];

  $element[$field_key] = array(
    '#type' => 'textfield',
    '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
    '#autocomplete_path' => $element['#autocomplete_path'],
    '#size' => $instance['widget']['settings']['size'],
    '#attributes' => array('class' => array('text')),
    '#title' => $element['#title'],
    '#description' => $element['#description'],
    '#required' => $element['#required'],
  );

  $element[$field_key]['#maxlength'] = !empty($field['settings']['max_length']) ? $field['settings']['max_length'] : NULL;

  if (!empty($instance['settings']['text_processing'])) {
    $filter_key  = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format';
    $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
  }

  return $element;
}

/**
 * Process an individual element.
 *
 * Build the form element. When creating a form using FAPI #process,
 * note that $element['#value'] is already set.
 *
 * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
 */
function text_textarea_elements_process($element, $form_state, $form) {
  $field = $form['#fields'][$element['#field_name']]['field'];
  $instance = $form['#fields'][$element['#field_name']]['instance'];
  $field_key = $element['#columns'][0];
  $delta = $element['#delta'];
  $element[$field_key] = array(
    '#type' => 'textarea',
    '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
    '#rows' => $instance['widget']['settings']['rows'],
    '#weight' => 0,
    '#title' => $element['#title'],
    '#description' => $element['#description'],
    '#required' => $element['#required'],
  );

  if (!empty($instance['settings']['text_processing'])) {
    $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format';
    $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
    $element[$field_key]['#text_format'] = $format;
  }

  return $element;
}

/**
 * Process an individual element.
 *
 * Build the form element. When creating a form using FAPI #process,
 * note that $element['#value'] is already set.
 *
 * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
 */
function text_textarea_with_summary_process($element, $form_state, $form) {
  $field = $form['#fields'][$element['#field_name']]['field'];
  $instance = $form['#fields'][$element['#field_name']]['instance'];
  $delta = $element['#delta'];

  $field_key = $element['#columns'][1];
  $display = !empty($element['#value'][$field_key]) || !empty($instance['settings']['display_summary']);
  $element[$field_key] = array(
    '#title' => t('Summary'),
    '#type' => $display ? 'textarea' : 'value',
    '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
    '#rows' => $instance['widget']['settings']['summary_rows'],
    '#weight' => 0,
    '#title' => t('Summary'),
    '#description' => t('Leave blank to use trimmed value of full text as the summary.'),
    '#required' => $element['#required'],
    '#display' => $display,
  );

  $field_key = $element['#columns'][0];
  $element[$field_key] = array(
    '#type' => 'textarea',
    '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
    '#rows' => $instance['widget']['settings']['rows'],
    '#weight' => 1,
    '#title' => $display ? t('Full text') : $element['#title'],
    '#description' => $element['#description'],
    '#required' => $element['#required'],
    '#required' => $instance['required'],
  );

  if (!empty($instance['settings']['text_processing'])) {
    $filter_key  = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format';
    $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
    $element[$field_key]['#text_format'] = $format;
/**
 * Helper function to determine the value for a formatted text widget.
 *
 * '#text_format' puts the format in '[column 0]_format' in incoming values,
 * while we need it in '[column 1]'.
 */
function text_field_widget_formatted_text_value($form, $edit = FALSE) {
  if ($edit !== FALSE) {
    $field_key = $form['#columns'][0];
    $filter_key = (count($form['#columns']) == 2) ? $form['#columns'][1] : 'format';
    $default_key = $field_key . '_format';
    // The format selector uses #access = FALSE if only one format is
    // available. In this case, we don't receive its value, and need to
    // manually set it.
    $edit['format'] = !empty($edit[$default_key]) ? $edit[$default_key] : filter_resolve_format(FILTER_FORMAT_DEFAULT);
    unset($edit[$default_key]);
    return $edit;
  }
}

/**
 * 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_text_textfield($element) {
  return $element['#children'];
}

function theme_text_textarea($element) {
  return $element['#children'];