You are here

class SuggestionHelper in Autocomplete Search Suggestions 7

Provides helper methods for suggestions.

Hierarchy

Expanded class hierarchy of SuggestionHelper

File

src/SuggestionHelper.php, line 11
Helper methods for the suggestion module.

View source
class SuggestionHelper {
  const C = 8;
  const EXP = 0.5;
  const MAX_DEPTH = 4;
  const MAX_SCORE = 100;
  const MIN_DELTA = 2;
  const MIN_SCORE = 1;

  /**
   * Search the form recursivley for the field and add the autocomplete route.
   *
   * @param array $form
   *   Part or all of a form render array.
   * @param string $field_name
   *   The field name to search for.
   * @param string $level
   *   The current recursion level.
   *
   * @return array
   *   An array of ngrams keys.
   */
  public static function alterElement(array &$form, $field_name, $level = 1) {
    $suxs = FALSE;
    $types = array(
      'search',
      'textfield',
    );
    if ($level > self::MAX_DEPTH) {
      return FALSE;
    }
    foreach ($form as $key => &$element) {
      if ($key == $field_name && !empty($form[$key]['#type']) && in_array($form[$key]['#type'], $types)) {
        $form[$key]['#autocomplete_path'] = 'suggestion/autocomplete';
        $suxs = TRUE;
      }
      elseif (is_array($element)) {
        $suxs = self::alterElement($element, $field_name, $level + 1);
      }
      if ($suxs) {
        break;
      }
    }
    if ($suxs && $level == 1) {
      $form['#submit'][] = 'suggestion_surfer_submit';
    }
    return $suxs;
  }

  /**
   * Transform a string to an array.
   *
   * @param string $txt
   *   The string to process.
   *
   * @return array
   *   An array of atoms.
   */
  public static function atomize($txt = '') {
    $atoms = array();
    $stopwords = self::getStops('stopwords');
    foreach (preg_split('/\\s+/', $txt) as $atom) {
      if (!empty($stopwords[$atom])) {
        continue;
      }
      $atoms[$atom] = $atom;
    }
    return $atoms;
  }

  /**
   * Calculate the ngram's density.
   *
   * @param int $src
   *   The ngram source.
   * @param int $atoms
   *   The number of atoms in the ngram.
   * @param int $qty
   *   The submission count.
   *
   * @return float
   *   The suggestion's score.
   */
  public static function calculateDensity($src = 0, $atoms = 1, $qty = 0) {
    $score = intval($src) * self::C;
    return (double) $score + self::getDelta(self::MAX_SCORE - $score, intval(pow($atoms + $qty, self::EXP)));
  }

  /**
   * Stopword get wrapper.
   *
   * @return mixed
   *   The value of the supplied field.
   */
  public static function getStops() {
    $stopwords =& drupal_static(__CLASS__ . '_' . __FUNCTION__, NULL);
    if (is_array($stopwords)) {
      return $stopwords;
    }
    $file_path = drupal_realpath(variable_get('suggestion_stopword_uri', file_default_scheme() . '://suggestion/suggestion_stopword.txt'));
    $stopwords = file_get_contents($file_path);
    $stopwords = array_fill_keys(preg_split('/\\s*[\\n\\r]+\\s*/s', $stopwords), 1);
    return $stopwords;
  }

  /**
   * Create a suggestion index from content titles.
   *
   * @param int $last_nid
   *   The last node ID processed.
   * @param int $limit
   *   The query limit.
   */
  public static function index($last_nid = 0, $limit = NULL) {
    $count =& drupal_static(__CLASS__ . '_' . __FUNCTION__ . '_count', 0);
    $nid =& drupal_static(__CLASS__ . '_' . __FUNCTION__ . '_nid', 0);
    variable_set('suggestion_synced', TRUE);
    if (!$last_nid) {
      SuggestionStorage::deleteContentSuggestion();
      SuggestionStorage::updateContentSrc();
    }
    $titles = SuggestionStorage::getTitles($last_nid, $limit);
    foreach ($titles as $nid => $title) {
      $count += self::insert($title, SuggestionStorage::CONTENT_BIT);
    }
  }

  /**
   * Add a suggestion.
   *
   * @param string $txt
   *   The title to index.
   * @param int $src
   *   The bits to OR with the current bitmap.
   * @param int $qty
   *   Default quantity.
   *
   * @return int
   *   The number of suggestions inserted.
   */
  public static function insert($txt = '', $src = SuggestionStorage::CONTENT_BIT, $qty = NULL) {
    $count = 0;
    $max = variable_get('suggestion_max', 45);
    $txt = self::tokenize($txt, variable_get('suggestion_min', 4));
    if (!$txt) {
      return 0;
    }
    $atoms = self::atomize($txt);
    foreach (array_keys(self::ngrams($atoms)) as $ngram) {
      if (strlen($ngram) > $max) {
        continue;
      }
      $count = str_word_count($ngram);
      $qty = is_numeric($qty) ? $qty + 1 : SuggestionStorage::getNgramQty($ngram) + 1;
      $src = SuggestionStorage::getBitmap($ngram, $src);
      $key = array(
        'ngram' => $ngram,
      );
      $fields = array(
        'atoms' => $count,
        'density' => self::calculateDensity($src, $count, $qty),
        'qty' => $qty,
        'src' => $src,
      );
      SuggestionStorage::mergeSuggestion($key, $fields);
      $count++;
    }
    return $count;
  }

  /**
   * Build a set of ngrams from the set of atoms.
   *
   * @param array $atoms
   *   An array of strings.
   *
   * @return array
   *   An array of ngrams keys.
   */
  public static function ngrams(array $atoms = array()) {
    $max = variable_get('suggestion_atoms_max', 6);
    $min = variable_get('suggestion_atoms_min', 6);
    $ngrams = array();
    $count = count($atoms) - $min;
    for ($i = 0; $i <= $count; $i++) {
      for ($j = $min; $j <= $max; $j++) {
        $ngrams[implode(' ', array_slice($atoms, $i, $j))] = 1;
      }
    }
    $atoms = array_reverse($atoms);
    for ($i = 0; $i <= $count; $i++) {
      for ($j = $min; $j <= $max; $j++) {
        $ngrams[implode(' ', array_slice($atoms, $i, $j))] = 1;
      }
    }
    return $ngrams;
  }

  /**
   * Convert an array of submitted form options to a bitmap.
   *
   * @param array $bits
   *   An array of form options.
   *
   * @return int
   *   The option values bitwise OR.
   */
  public static function optionBits(array $bits = array()) {
    $src = 0;
    foreach ($bits as $bit) {
      $src |= intval($bit);
    }
    return $src;
  }

  /**
   * Build an array of defalut options from bitmaps.
   *
   * @param int $src
   *   The ngrams source bitmap.
   *
   * @return array
   *   An array of default option.
   */
  public static function srcBits($src = 0) {
    $bits = array();
    if (intval($src) <= 0) {
      return array(
        0,
      );
    }
    foreach (array_keys(SuggestionStorage::getSrcOptions()) as $bit) {
      if ($bit & $src) {
        $bits[] = $bit;
      }
    }
    return $bits;
  }

  /**
   * Tokenize the text into space separated lowercase strings.
   *
   * @param string $txt
   *   The text to process.
   * @param int $min
   *   The minimum number of characters in a token.
   *
   * @return string
   *   The tokenized string.
   */
  public static function tokenize($txt = '', $min = 4) {
    $min--;
    $regx = array(
      '/[^a-z]+/s' => ' ',
      '/\\b(\\w{1,' . $min . '})\\b/s' => '',
      '/\\s\\s+/s' => ' ',
      '/^\\s+|\\s+$/s' => '',
    );
    return preg_replace(array_keys($regx), array_values($regx), strtolower(trim($txt)));
  }

  /**
   * Build an array of content types used in auto-complete.
   *
   * @return array
   *   An array of enabled content types.
   */
  public static function types() {
    $types =& drupal_static(__CLASS__ . '_' . __FUNCTION__, NULL);
    if (is_array($types)) {
      return $types;
    }
    foreach (variable_get('suggestion_types', array()) as $type => $status) {
      if ($status) {
        $types[] = $type;
      }
    }
    return $types ? $types : array();
  }

  /**
   * Calculate the ngram's density.
   *
   * @param string $ngram
   *   The ngram to update.
   * @param int $src
   *   The ngram source.
   *
   * @return object
   *   A Merge object.
   */
  public static function updateSrc($ngram = '', $src = 0) {
    $obj = SuggestionStorage::getSuggestion($ngram);
    $key = array(
      'ngram' => $ngram,
    );
    $fields = array(
      'atoms' => $obj->atoms,
      'density' => self::calculateDensity($src, $obj->atoms, $obj->qty),
      'qty' => $obj->qty,
      'src' => $src,
    );
    return SuggestionStorage::mergeSuggestion($key, $fields);
  }

  /**
   * Estimate the performance.
   *
   * @param float $delta
   *   The current performance remainder.
   * @param int $n
   *   The summation N - 1.
   *
   * @return float
   *   The suggestion's remainder summation.
   */
  protected static function getDelta($delta, $n) {
    if ($delta < self::MIN_DELTA || !$n) {
      return 0;
    }
    $x = pow($delta, self::EXP);
    return $x + self::getDelta($delta - $x, $n - 1);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
SuggestionHelper::alterElement public static function Search the form recursivley for the field and add the autocomplete route.
SuggestionHelper::atomize public static function Transform a string to an array.
SuggestionHelper::C constant
SuggestionHelper::calculateDensity public static function Calculate the ngram's density.
SuggestionHelper::EXP constant
SuggestionHelper::getDelta protected static function Estimate the performance.
SuggestionHelper::getStops public static function Stopword get wrapper.
SuggestionHelper::index public static function Create a suggestion index from content titles.
SuggestionHelper::insert public static function Add a suggestion.
SuggestionHelper::MAX_DEPTH constant
SuggestionHelper::MAX_SCORE constant
SuggestionHelper::MIN_DELTA constant
SuggestionHelper::MIN_SCORE constant
SuggestionHelper::ngrams public static function Build a set of ngrams from the set of atoms.
SuggestionHelper::optionBits public static function Convert an array of submitted form options to a bitmap.
SuggestionHelper::srcBits public static function Build an array of defalut options from bitmaps.
SuggestionHelper::tokenize public static function Tokenize the text into space separated lowercase strings.
SuggestionHelper::types public static function Build an array of content types used in auto-complete.
SuggestionHelper::updateSrc public static function Calculate the ngram's density.