View source  
  <?php
namespace Drupal\suggestion;
class SuggestionHelper {
  const C = 8;
  const MAX = 100;
  const MAX_LEVEL = 4;
  const MIN = 2;
  const EXP = 0.5;
  
  public static function alterElement(array &$form, $field_name, $level = 1) {
    $suxs = FALSE;
    $types = [
      'search',
      'textfield',
    ];
    if ($level > self::MAX_LEVEL) {
      return FALSE;
    }
    foreach ($form as $key => &$element) {
      if ($key == $field_name && !empty($form[$key]['#type']) && in_array($form[$key]['#type'], $types)) {
        $form[$key]['#autocomplete_route_name'] = '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;
  }
  
  public static function atomize($txt = '') {
    $atoms = [];
    $stopwords = self::getStops('stopwords');
    foreach (preg_split('/\\s+/', $txt) as $atom) {
      if (!empty($stopwords[$atom])) {
        continue;
      }
      $atoms[$atom] = $atom;
    }
    return $atoms;
  }
  
  public static function calculateDensity($src = 0, $atoms = 1, $qty = 0) {
    $score = intval($src) * self::C;
    return (double) $score + self::getDelta(self::MAX - $score, intval(pow($atoms + $qty, self::EXP)));
  }
  
  public static function getConfig($key = '') {
    $cfg =& drupal_static(__CLASS__ . '_' . __FUNCTION__, \Drupal::configFactory()
      ->get('suggestion.config'));
    return $key ? $cfg
      ->get($key) : (object) $cfg
      ->get();
  }
  
  public static function getStops($key = '') {
    $cfg =& drupal_static(__CLASS__ . '_' . __FUNCTION__, \Drupal::configFactory()
      ->get('suggestion.stopword'));
    return $key ? $cfg
      ->get($key) : (object) $cfg
      ->get();
  }
  
  public static function index($last_nid = 0, $limit = NULL) {
    $count =& drupal_static(__CLASS__ . '_' . __FUNCTION__ . '_count', 0);
    $nid =& drupal_static(__CLASS__ . '_' . __FUNCTION__ . '_nid', 0);
    self::setConfig('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);
    }
  }
  
  public static function insert($txt = '', $src = SuggestionStorage::CONTENT_BIT, $qty = NULL) {
    $count = 0;
    $max = self::getConfig('max');
    $txt = self::tokenize($txt, self::getConfig('min'));
    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 = [
        'ngram' => $ngram,
      ];
      $fields = [
        'atoms' => $count,
        'density' => self::calculateDensity($src, $count, $qty),
        'qty' => $qty,
        'src' => $src,
      ];
      SuggestionStorage::mergeSuggestion($key, $fields);
      $count++;
    }
    return $count;
  }
  
  public static function ngrams(array $atoms = []) {
    $max = self::getConfig('atoms_max');
    $min = self::getConfig('atoms_min');
    $ngrams = [];
    $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;
  }
  
  public static function setConfig($key = '', $val = NULL) {
    $cfg =& drupal_static(__CLASS__ . '_' . __FUNCTION__, \Drupal::configFactory()
      ->getEditable('suggestion.config'));
    return $key ? $cfg
      ->set($key, $val)
      ->save() : NULL;
  }
  
  public static function setStops($key = '', $val = NULL) {
    $cfg =& drupal_static(__CLASS__ . '_' . __FUNCTION__, \Drupal::configFactory()
      ->getEditable('suggestion.stopword'));
    return $key ? $cfg
      ->set($key, $val)
      ->save() : NULL;
  }
  
  public static function srcBits($src = 0) {
    $bits = [];
    if (intval($src) <= 0) {
      return [
        0,
      ];
    }
    foreach (array_keys(SuggestionStorage::getSrcOptions()) as $bit) {
      if ($bit & $src) {
        $bits[] = $bit;
      }
    }
    return $bits;
  }
  
  public static function optionBits(array $bits = []) {
    $src = 0;
    foreach ($bits as $bit) {
      $src |= intval($bit);
    }
    return $src;
  }
  
  public static function tokenize($txt = '', $min = 4) {
    $min--;
    $regx = [
      '/[^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)));
  }
  
  public static function types() {
    $types =& drupal_static(__CLASS__ . '_' . __FUNCTION__, NULL);
    if (is_array($types)) {
      return $types;
    }
    foreach (self::getConfig('types') as $type => $status) {
      if ($status) {
        $types[] = $type;
      }
    }
    return $types ? $types : [];
  }
  
  public static function updateSrc($ngram = '', $src = 0) {
    $obj = SuggestionStorage::getSuggestion($ngram);
    $key = [
      'ngram' => $ngram,
    ];
    $fields = [
      'atoms' => $obj->atoms,
      'density' => self::calculateDensity($src, $obj->atoms, $obj->qty),
      'qty' => $obj->qty,
      'src' => $src,
    ];
    return SuggestionStorage::mergeSuggestion($key, $fields);
  }
  
  protected static function getDelta($delta, $n) {
    if ($delta < self::MIN || !$n) {
      return 0;
    }
    $x = pow($delta, self::EXP);
    return $x + self::getDelta($delta - $x, $n - 1);
  }
}