You are here

smart_trim.module in Smart Trim 7

Same filename and directory in other branches
  1. 8 smart_trim.module

Module functionality.

File

smart_trim.module
View source
<?php

/**
 * @file
 * Module functionality.
 */
define('SMART_TRIM_NEVER', 0);
define('SMART_TRIM_ALWAYS', 1);
define('SMART_TRIM_IFNEEDED', 2);

/**
 * Implements hook_field_formatter_info().
 */
function smart_trim_field_formatter_info() {
  return array(
    'smart_trim_format' => array(
      'label' => t('Smart trimmed'),
      'field types' => array(
        'text',
        'text_long',
        'text_with_summary',
      ),
      'settings' => array(
        'trim_length' => 300,
        'trim_type' => 'chars',
        'trim_suffix' => '...',
        'trim_link' => FALSE,
        'more_link' => SMART_TRIM_NEVER,
        'more_text' => 'Read more',
        'summary_handler' => 'full',
        'trim_options' => array(),
        'trim_preserve_tags' => '',
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_view().
 *
 * Need a better way to handle doing count by words instead of characters (see
 * views_trim_text().
 */
function smart_trim_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  $settings = $display['settings'];
  switch ($display['type']) {
    case 'smart_trim_format':
      foreach ($items as $delta => $item) {

        // The default behaviour is to use the main body field, but the summary
        // option allows users to use the summary field IFF it is not empty.
        $break_pos = strpos($item['value'], '<!--break-->');
        $has_summary_or_break = !empty($item['summary']) || $break_pos !== FALSE;
        $honor_trim = empty($settings['summary_handler']) || $settings['summary_handler'] != 'full' ? TRUE : FALSE;
        if (!empty($settings['summary_handler']) && $settings['summary_handler'] != 'ignore' && $has_summary_or_break) {
          if (empty($item['summary'])) {
            $output = check_markup(substr($item['value'], 0, $break_pos), $item['format'], $langcode, FALSE);
          }
          else {
            $output = _text_sanitize($instance, $langcode, $item, 'summary');
          }
        }
        else {
          $output = _text_sanitize($instance, $langcode, $item, 'value');
        }

        // Process additional options (currently only HTML on/off).
        if (!empty($settings['trim_options'])) {
          if (!empty($settings['trim_options']['text'])) {

            // Strip tags.
            $preserve_tags = !empty($settings['trim_preserve_tags']) ? $settings['trim_preserve_tags'] : '';
            $output = strip_tags(str_replace('<', ' <', $output), $preserve_tags);

            // Strip out line breaks.
            $output = preg_replace('/\\n|\\r|\\t/m', ' ', $output);

            // Strip out non-breaking spaces.
            $output = str_replace('&nbsp;', ' ', $output);
            $output = str_replace(" ", ' ', $output);

            // Strip out extra spaces.
            $output = trim(preg_replace('/\\s\\s+/', ' ', $output));
          }
        }

        // Make the trim, provided we're honoring trim settings
        // or there's no summary or break available.
        $shortened = FALSE;
        if ($honor_trim || $has_summary_or_break === FALSE) {
          if ($settings['trim_type'] == 'words') {

            // Only bother with this is we have to.
            if ($settings['trim_length'] < str_word_count($output)) {

              // Use \s or use PREG_CLASS_UNICODE_WORD_BOUNDARY?
              $words = preg_split('/\\s/', $output, NULL, PREG_SPLIT_NO_EMPTY);
              $output2 = implode(" ", array_slice($words, 0, $settings['trim_length']));
              $output2 = _filter_htmlcorrector($output2);
            }
            else {
              $output2 = $output;
            }
          }
          else {

            // Use views_trim_text() if available.
            if (module_exists('views')) {
              $output2 = views_trim_text(array(
                'max_length' => $settings['trim_length'],
                'word_boundary' => TRUE,
                'ellipsis' => FALSE,
                'html' => TRUE,
              ), $output);
            }
            else {

              // @see http://api.drupal.org/api/drupal/modules%21field%21modules%21text%21text.module/function/text_summary/7
              // text_summary is smart about looking for paragraphs, sentences,
              // etc, not strictly just length. Uses truncate_utf8 as well.
              $output2 = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $settings['trim_length']);
            }
          }

          // Verify if we actually performed any shortening.
          if (drupal_strlen(drupal_html_to_text($output)) != drupal_strlen(drupal_html_to_text($output2))) {
            $shortened = TRUE;
          }
          $output = $output2;
        }

        // Only include the extension if the text was truncated.
        $extension = '';
        if ($shortened) {
          $extension = filter_xss($settings['trim_suffix']);
        }

        // Don't duplicate period at end of text and beginning of extension.
        if (substr($output, -1, 1) == '.' && substr($extension, 0, 1) == '.') {
          $extension = substr($extension, 1);
        }

        // Final checks if we need to display a more link.
        $needs_more = FALSE;
        switch ($settings['more_link']) {

          // If the user always wants the link, include it.
          case SMART_TRIM_ALWAYS:
            $needs_more = TRUE;
            break;
          case SMART_TRIM_IFNEEDED:
            if ($shortened) {
              $needs_more = TRUE;
            }
            else {

              // If a summary is used (whether truncated or not), and it's
              // different from the main body, we need to show the more link.
              if (!empty($item['summary']) && $settings['summary_handler'] != 'ignore') {
                $summary_output = _text_sanitize($instance, $langcode, $item, 'summary');
                $full_output = _text_sanitize($instance, $langcode, $item, 'value');
                if (strcmp($summary_output, $full_output) != 0) {
                  $needs_more = TRUE;
                }
              }
            }
            break;
        }
        $uri = entity_uri($entity_type, $entity);
        if ($uri && $needs_more) {
          $extension .= l(t('@more_text', array(
            '@more_text' => $settings['more_text'],
          )), $uri['path'], array(
            'html' => TRUE,
            'attributes' => array(
              'class' => array(
                'more-link',
              ),
            ),
          ));
        }
        $output_appended = preg_replace('#^(.*)(\\s?)(</[^>]+>)$#Us', '$1' . $extension . '$3', $output);

        // Check if the regex did anything. if not, append manually.
        if ($output_appended == $output) {
          $output_appended = $output . $extension;
        }

        // If linking to content is selected the trimmed text will have tags
        // removed from the trimmed text. Need to find a more elegant way of
        // doing this without conflicting the other settings.
        if (!empty($settings['trim_link']) && !empty($uri['path'])) {
          $output_appended = strip_tags($output_appended);
          $output = l($output_appended, $uri['path']);
          $element[$delta] = array(
            '#markup' => $output,
          );
          return $element;
        }
        $element[$delta] = array(
          '#markup' => $output_appended,
        );
      }
      break;
  }
  return $element;
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function smart_trim_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $settings = $instance['display'][$view_mode]['settings'];
  $element = array();
  $element['trim_link'] = array(
    '#title' => t('Link text to'),
    '#type' => 'select',
    '#default_value' => $settings['trim_link'],
    '#description' => t('Linking text to content will strip tags entirely from the trimmed text.'),
    '#options' => array(
      0 => t('Nothing'),
      1 => t('Content'),
    ),
  );
  $element['trim_length'] = array(
    '#title' => t('Trim length'),
    '#type' => 'textfield',
    '#size' => 10,
    '#default_value' => $settings['trim_length'],
    // Needs form.inc?
    '#element_validate' => array(
      '_element_validate_integer_positive',
    ),
    '#required' => TRUE,
  );
  $element['trim_type'] = array(
    '#title' => t('Trim units'),
    '#type' => 'select',
    '#options' => array(
      'chars' => t("Characters"),
      'words' => t("Words"),
    ),
    '#default_value' => $settings['trim_type'],
  );
  $element['trim_suffix'] = array(
    '#title' => t('Suffix'),
    '#type' => 'textfield',
    '#size' => 10,
    '#default_value' => $settings['trim_suffix'],
  );
  $element['more_link'] = array(
    '#title' => t('Display more link?'),
    '#type' => 'select',
    '#options' => array(
      SMART_TRIM_NEVER => t('Never'),
      SMART_TRIM_ALWAYS => t('Always'),
      SMART_TRIM_IFNEEDED => t('If needed (when using a summary or trimmed text)'),
    ),
    '#default_value' => $settings['more_link'],
    '#description' => t('Displays a link to the entity (if one exists)'),
  );
  $element['more_text'] = array(
    '#title' => t('More link text'),
    '#type' => 'textfield',
    '#size' => 20,
    '#default_value' => $settings['more_text'],
    '#description' => t('If displaying more link, enter the text for the link.'),
    '#states' => array(
      // Hide this field when $element['more_link'] is set to "Never".
      // @see States Api : drupal_process_states()
      'invisible' => array(
        ':input[name="fields[body][settings_edit_form][settings][more_link]"]' => array(
          'value' => SMART_TRIM_NEVER,
        ),
      ),
    ),
  );
  if ($field['type'] == 'text_with_summary') {
    $element['summary_handler'] = array(
      '#title' => t('Summary'),
      '#type' => 'select',
      '#options' => array(
        'full' => t("Use summary if present, and do not trim"),
        'trim' => t("Use summary if present, honor trim settings"),
        'ignore' => t("Do not use summary"),
      ),
      '#default_value' => $settings['summary_handler'],
    );
  }
  $element['trim_options'] = array(
    '#title' => t('Additional options'),
    '#type' => 'checkboxes',
    '#options' => array(
      'text' => t('Strip HTML'),
    ),
    '#default_value' => empty($settings['trim_options']) ? array() : $settings['trim_options'],
  );
  $element['trim_preserve_tags'] = array(
    '#title' => t('Tags to preserve'),
    '#description' => t('Which tags to preserve if "Strip HTML" is chosen above. Format as "&lt;p&gt;&lt;a&gt;" to preserve p and a tags.'),
    '#type' => 'textfield',
    '#default_value' => $settings['trim_preserve_tags'],
  );
  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function smart_trim_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  $summary = intval($settings['trim_length']) . ' ' . ($settings['trim_type'] == 'chars' ? t('characters') : t('words'));
  if (drupal_strlen(trim($settings['trim_suffix']))) {
    $summary .= " " . t("with suffix");
  }
  switch ($settings['more_link']) {
    case SMART_TRIM_ALWAYS:
      $summary .= ', ' . t('with more link (always)');
      break;
    case SMART_TRIM_IFNEEDED:
      $summary .= ', ' . t('with more link (if needed)');
      break;
  }

  // Trim options summary.
  if (isset($settings['trim_link'])) {
    $summary .= $settings['trim_link'] == 1 ? ' ' . t('linked to content') : '';
  }
  return $summary;
}

/**
 * Implements hook_help().
 */
function smart_trim_help($path, $arg) {
  if ($path == 'admin/help#smart_trim') {
    return '<p>' . t('This module creates a new text field formatter. There are no global configuration options.') . '</p>';
  }
}