class TextSummary

Same name and namespace in other branches
  1. main core/modules/text/src/TextSummary.php \Drupal\text\TextSummary

Summary generator for text fields.

Hierarchy

Expanded class hierarchy of TextSummary

4 files declare their use of TextSummary
NodeTokensHooks.php in core/modules/node/src/Hook/NodeTokensHooks.php
text.module in core/modules/text/text.module
TextSummaryTest.php in core/modules/text/tests/src/Kernel/TextSummaryTest.php
TextTrimmedFormatter.php in core/modules/text/src/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php

File

core/modules/text/src/TextSummary.php, line 14

Namespace

Drupal\text
View source
class TextSummary {
  public function __construct(protected readonly EntityTypeManagerInterface $entityTypeManager) {
  }
  
  /**
   * Generates 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 string|\Stringable $text
   *   The content for which a summary will be generated.
   * @param string|null $format
   *   The format of the content. 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 int $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 string|\Stringable
   *   The generated summary.
   */
  public function generate(string|\Stringable $text, string|null $format = NULL, int $size = 600) : string|\Stringable {
    // Find where the delimiter is in the body.
    $delimiter = strpos((string) $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((string) $text, 0, $delimiter);
    }
    // Retrieve the filters of the specified text format, if any.
    if (isset($format)) {
      $filter_format = $this->entityTypeManager
        ->getStorage('filter_format')
        ->load($format);
      // If the specified format does not exist, return nothing.
      // $text is already filtered text, but the remainder of this function will
      // not be able to ensure a sane and secure summary.
      if (!$filter_format || !$filters = $filter_format->filters()) {
        return '';
      }
    }
    // If we have a short body, the entire body is the summary.
    if (mb_strlen((string) $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 the maximum length specified.
    // Initial slice.
    $summary = Unicode::truncate((string) $text, $size);
    // Store the actual length of the UTF8 string -- which might not be the same
    // as $size.
    $max_right_pos = 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_right_pos = $max_right_pos;
    // 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 = [];
    // A paragraph near the end of the sliced summary is most preferable.
    $break_points[] = [
      '</p>' => 0,
    ];
    // If no complete paragraph then treat line breaks as paragraphs.
    $line_breaks = [
      '<br />' => 6,
      '<br>' => 4,
    ];
    // Newline only indicates a line break if line break converter
    // filter is present.
    if (isset($format) && $filters->has('filter_autop') && $filters->get('filter_autop')->status) {
      $line_breaks["\n"] = 1;
    }
    $break_points[] = $line_breaks;
    // If the first paragraph is too long, split at the end of a sentence.
    $break_points[] = [
      '. ' => 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.
        $right_pos = strpos($reversed, strrev($point));
        if ($right_pos !== FALSE) {
          $min_right_pos = min($right_pos + $offset, $min_right_pos);
        }
      }
      // If a break point was found in this group, slice and stop searching.
      if ($min_right_pos !== $max_right_pos) {
        // Don't slice with length 0. Length must be <0 to slice from RHS.
        $summary = $min_right_pos === 0 ? $summary : substr($summary, 0, 0 - $min_right_pos);
        break;

      }
    }
    // If filter_html or filter_htmlcorrector is enabled, normalize the output.
    if (isset($format)) {
      $filter_enabled = function (string $filter) use ($filters) : bool {
        return $filters->has($filter) && $filters->get($filter)->status;
      };
      if ($filter_enabled('filter_html') || $filter_enabled('filter_htmlcorrector')) {
        $summary = Html::normalize($summary);
      }
    }
    return $summary;
  }

}

Members

Title Sort descending Modifiers Object type Summary
TextSummary::generate public function Generates a trimmed, formatted version of a text field value.
TextSummary::__construct public function

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.