PageRenderTime 30ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/chatbot/core/aiml/find_aiml.php

https://gitlab.com/tutaalexandr/bot_local
PHP | 1036 lines | 729 code | 77 blank | 230 comment | 122 complexity | be5380aed5a3abf5318157974a425f65 MD5 | raw file
  1. <?php
  2. /***************************************
  3. * http://www.program-o.com
  4. * PROGRAM O
  5. * Version: 2.4.8
  6. * FILE: find_aiml.php
  7. * AUTHOR: Elizabeth Perreau and Dave Morton
  8. * DATE: FEB 01 2016
  9. * DETAILS: Contains functions that find and score the most likely AIML match from the database
  10. ***************************************/
  11. /**
  12. * Gets the last word from a sentence
  13. *
  14. * @param string $sentence
  15. * @return string
  16. **/
  17. function get_last_word($sentence)
  18. {
  19. $wordArr = explode(' ', $sentence);
  20. $last_word = trim($wordArr[count($wordArr) - 1]);
  21. runDebug(__FILE__, __FUNCTION__, __LINE__, "Sentence: $sentence. Last word:$last_word", 4);
  22. return $last_word;
  23. }
  24. /**
  25. * Gets the first word from a sentence
  26. * @param string $sentence
  27. * @return string
  28. **/
  29. function get_first_word($sentence)
  30. {
  31. $wordArr = explode(' ', $sentence);
  32. $first_word = trim($wordArr[0]);
  33. runDebug(__FILE__, __FUNCTION__, __LINE__, "Sentence: $sentence. First word:$first_word", 4);
  34. return $first_word;
  35. }
  36. /**
  37. * Gets an input sentence from the user and a aiml tag and creates an sql like pattern
  38. * @param string $sentence
  39. * @param string $field
  40. * @return string
  41. **/
  42. function make_like_pattern($sentence, $field)
  43. {
  44. runDebug(__FILE__, __FUNCTION__, __LINE__, "Making a like pattern to use in the sql", 4);
  45. runDebug(__FILE__, __FUNCTION__, __LINE__, "Transforming $field: " . print_r($sentence, true), 4);
  46. $sql_like_pattern = "\n";
  47. $i = 0;
  48. //if the sentence is contained in an array extract the actual text sentence
  49. if (is_array($sentence))
  50. {
  51. $sentence = implode_recursive(' ', $sentence, __FILE__, __FUNCTION__, __LINE__);
  52. }
  53. $words = explode(" ", $sentence);
  54. runDebug(__FILE__, __FUNCTION__, __LINE__, "word list:\n" . print_r($words, true), 4);
  55. $count_words = count($words) - 1;
  56. $first_word = $words[0];
  57. $last_word = $words[$count_words];
  58. $tmpLike = '';
  59. //$sql_like_pattern .= " `$field` like '$first_word % $last_word'";// OR `$field` like '$first_word %' OR `$field` like '% $last_word'";
  60. $sql_like_pattern .= " `$field` like '$first_word % $last_word' OR\n";
  61. $sql_like_pattern .= " `$field` like '$first_word %' OR\n";
  62. $wordsArr = explode(" ",$sentence);
  63. $totalWordCount = count($wordsArr);
  64. $likePatternArr = array();
  65. for($i=0;$i<$totalWordCount;$i++){
  66. $twoUp = $i+2;
  67. $oneUp = $i+1;
  68. $oneDown = $i-1;
  69. if(isset($wordsArr[$twoUp])){
  70. $middleWord = $wordsArr[$oneUp];
  71. $likePatternArr[] = "(`$field` LIKE '% ".$middleWord." %')";
  72. }
  73. if($oneDown>=0){
  74. $likePatternOneArr = $wordsArr;
  75. $likePatternOneArr[$i]='%';
  76. $likePatternOne = implode(' ',$likePatternOneArr);
  77. $likePatternArr[] = "(`$field` LIKE '". trim(strstr($likePatternOne,'%',true))." %')";
  78. }
  79. if($oneUp<$totalWordCount){
  80. $likePatternOneArr = $wordsArr;
  81. $likePatternOneArr[$i]='%';
  82. $likePatternOne = implode(' ',$likePatternOneArr);
  83. $likePatternArr[] = "(`$field` LIKE '". trim(strstr($likePatternOne,'%',false))."' )";
  84. }
  85. }
  86. $newSqlPatterns = implode(' OR ', $likePatternArr).' OR ';
  87. $sql_like_pattern .= $newSqlPatterns;
  88. runDebug(__FILE__, __FUNCTION__, __LINE__, "returning like pattern:\n$sql_like_pattern", 4);
  89. return rtrim($sql_like_pattern) . ' ';
  90. }
  91. /**
  92. * Counts the words in a sentence
  93. * @param string $sentence
  94. * @return int
  95. **/
  96. function wordsCount_inSentence($sentence)
  97. {
  98. $wordArr = explode(" ", $sentence);
  99. $wordCount = count($wordArr);
  100. runDebug(__FILE__, __FUNCTION__, __LINE__, "Sentence: $sentence numWords:$wordCount", 4);
  101. return $wordCount;
  102. }
  103. /**
  104. * Takes all the sql results passed to the function and filters out the irrelevant ones
  105. *
  106. * @param array $convoArr
  107. * @param array $allrows
  108. * @param string $lookingfor
  109. * @internal param string $current_thatpattern
  110. * @internal param string $current_topic
  111. * @return array
  112. **/
  113. function unset_all_bad_pattern_matches($convoArr, $allrows, $lookingfor)
  114. {
  115. global $error_response;
  116. $lookingfor_lc = make_lc($lookingfor);
  117. $current_topic = get_topic($convoArr);
  118. $current_thatpattern = (isset ($convoArr['that'][1][1])) ? $convoArr['that'][1][1] : '';
  119. //file_put_contents(_LOG_PATH_ . 'allrows.txt', print_r($allrows, true));
  120. runDebug(__FILE__, __FUNCTION__, __LINE__, "Current THAT = $current_thatpattern", 1);
  121. $default_pattern = $convoArr['conversation']['default_aiml_pattern'];
  122. $relevantRow = array();
  123. //if default pattern keep
  124. //if direct pattern match keep
  125. //if wildcard or direct pattern match and direct or wildcard thatpattern match keep
  126. //if wildcard pattern matches found aiml keep
  127. //the end......
  128. runDebug(__FILE__, __FUNCTION__, __LINE__, "NEW FUNC Searching through " . count($allrows) . " rows to unset bad matches", 4);
  129. if (($allrows[0]['pattern'] == "no results") && (count($allrows) == 1))
  130. {
  131. $tmp_rows[0] = $allrows[0];
  132. $tmp_rows[0]['score'] = 1;
  133. runDebug(__FILE__, __FUNCTION__, __LINE__, "Returning error as no results where found", 1);
  134. return $tmp_rows;
  135. }
  136. //loop through the results array
  137. runDebug(__FILE__, __FUNCTION__, __LINE__,'Blue 5 to Blue leader. Starting my run now!', 4);
  138. $i = 0;
  139. foreach ($allrows as $all => $subrow)
  140. {
  141. //get the pattern
  142. $aiml_pattern = make_lc($subrow['pattern']);
  143. $aiml_pattern_wildcards = build_wildcard_RegEx($aiml_pattern);
  144. $default_pattern_lc = make_lc($default_pattern);
  145. //get the that pattern
  146. $aiml_thatpattern = make_lc($subrow['thatpattern']);
  147. $current_thatpattern = make_lc($current_thatpattern);
  148. //get topic pattern
  149. $topicMatch = FALSE;
  150. $aiml_topic = make_lc(trim($subrow['topic']));
  151. $current_topic_lc = make_lc($current_topic);
  152. #Check for a matching topic
  153. $aiml_topic_wildcards = (!empty($aiml_topic)) ? build_wildcard_RegEx($aiml_topic) : '';
  154. if ($aiml_topic == '')
  155. {
  156. $topicMatch = TRUE;
  157. }
  158. elseif (($aiml_topic == $current_topic_lc))
  159. {
  160. $topicMatch = TRUE;
  161. }
  162. elseif (!empty($aiml_topic_wildcards))
  163. {
  164. preg_match($aiml_topic_wildcards, $current_topic_lc, $matches);
  165. $topicMatch = (count($matches) > 0) ? true : false;
  166. }
  167. else
  168. {
  169. $topicMatch = FALSE;
  170. }
  171. # check for a matching pattern
  172. preg_match($aiml_pattern_wildcards, $lookingfor, $matches);
  173. $aiml_patternmatch = (count($matches) > 0) ? true : false;
  174. # look for a thatpattern match
  175. $aiml_thatpattern_wildcards = (!empty($aiml_thatpattern)) ? build_wildcard_RegEx($aiml_thatpattern) : '';
  176. $aiml_thatpattern_wc_matches = (!empty($aiml_thatpattern_wildcards)) ? preg_match_all($aiml_thatpattern_wildcards,$current_thatpattern) : 0;
  177. switch (true) {
  178. case ($aiml_thatpattern_wc_matches > 0):
  179. case ($current_thatpattern == $aiml_thatpattern):
  180. $aiml_thatpatternmatch = true;
  181. break;
  182. default:
  183. $aiml_thatpatternmatch = false;
  184. }
  185. if ($aiml_pattern == $default_pattern_lc)
  186. {
  187. //if it is a direct match with our default pattern then add to tmp_rows
  188. $tmp_rows[$i]['score'] = 1;
  189. $tmp_rows[$i]['track_score'] = "default pick up line ($aiml_pattern = $default_pattern) ";
  190. }
  191. elseif ((!$aiml_thatpattern_wildcards) && ($aiml_patternmatch)) // no thatpattern and a pattern match keep
  192. {
  193. $tmp_rows[$i]['score'] = 1;
  194. $tmp_rows[$i]['track_score'] = " no thatpattern in result and a pattern match";
  195. }
  196. elseif (($aiml_thatpattern_wildcards) && ($aiml_thatpatternmatch) && ($aiml_patternmatch)) //pattern match and a wildcard match on the thatpattern keep
  197. {
  198. $tmp_rows[$i]['score'] = 1;
  199. $tmp_rows[$i]['track_score'] = " thatpattern wildcard match and a pattern match";
  200. }
  201. elseif (($aiml_thatpatternmatch) && ($aiml_patternmatch)) //pattern match and a generic match on the thatpattern keep
  202. {
  203. $tmp_rows[$i]['score'] = 1;
  204. $tmp_rows[$i]['track_score'] = " thatpattern match and a pattern match";
  205. }
  206. elseif ($aiml_pattern == $lookingfor_lc)
  207. {
  208. $tmp_rows[$i]['score'] = 1;
  209. $tmp_rows[$i]['track_score'] = " direct pattern match";
  210. }
  211. else
  212. {
  213. $tmp_rows[$i]['score'] = - 1;
  214. $tmp_rows[$i]['track_score'] = "dismissing nothing is matched";
  215. }
  216. if ($topicMatch === FALSE)
  217. {
  218. $tmp_rows[$i]['score'] = - 1;
  219. $tmp_rows[$i]['track_score'] = "dismissing wrong topic";
  220. }
  221. if ($tmp_rows[$i]['score'] >= 0)
  222. {
  223. $relevantRow[] = $subrow;
  224. }
  225. $i++;
  226. }
  227. $rrCount = count($relevantRow);
  228. if ($rrCount == 0)
  229. {
  230. $i = 0;
  231. runDebug(__FILE__, __FUNCTION__, __LINE__, "Error: FOUND NO AIML matches in DB", 1);
  232. $relevantRow[$i]['aiml_id'] = "-1";
  233. $relevantRow[$i]['bot_id'] = "-1";
  234. $relevantRow[$i]['pattern'] = "no results";
  235. $relevantRow[$i]['thatpattern'] = '';
  236. $relevantRow[$i]['topic'] = '';
  237. $relevantRow[$i]['score'] = 0;
  238. }
  239. sort2DArray("show top scoring aiml matches", $relevantRow, "good matches", 1, 10);
  240. runDebug(__FILE__, __FUNCTION__, __LINE__, "Found " . count($relevantRow) . " relevant rows", 4);
  241. //file_put_contents(_LOG_PATH_ . 'relevantRow.txt', print_r($relevantRow, true));
  242. return $relevantRow;
  243. }
  244. /**
  245. * Takes a sentence and converts AIML wildcards to Regular Expression wildcards
  246. * so that it can be matched in php using pcre search functions
  247. *
  248. * @param string $item
  249. * @return string
  250. **/
  251. function build_wildcard_RegEx($item)
  252. {
  253. $item = trim($item);
  254. $item = str_replace("*", ")(.*)(", $item);
  255. $item = str_replace("_", ")(.*)(", $item);
  256. $item = str_replace("+", "\+", $item);
  257. $item = "(" . str_replace(" ", "\s", $item) . ")";
  258. $item = str_replace("()", '', $item);
  259. $matchme = "/^" . $item . "$/ui";
  260. return $matchme;
  261. }
  262. /**
  263. * Takes all the relevant sql results and scores them to find the most likely match with the aiml
  264. *
  265. * @param array $convoArr
  266. * @param array $allrows
  267. * @param string $pattern
  268. * @internal param int $bot_parent_id
  269. * @internal param string $current_thatpattern
  270. * @internal param string $current_topic
  271. * @return array
  272. **/
  273. function score_matches($convoArr, $allrows, $pattern)
  274. {
  275. global $common_words_array;
  276. runDebug(__FILE__, __FUNCTION__, __LINE__, "Scoring the matches.", 4);
  277. # obtain some values to test
  278. $topic = get_topic($convoArr);
  279. $that = (isset ($convoArr['that'][1][1])) ? $convoArr['that'][1][1] : '';
  280. $default_pattern = $convoArr['conversation']['default_aiml_pattern'];
  281. $bot_parent_id = $convoArr['conversation']['bot_parent_id'];
  282. $bot_id = $convoArr['conversation']['bot_id'];
  283. # set the scores for each type of word or sentence to be used in this function
  284. # full pattern match scores:
  285. $this_bot_match = 250;
  286. $underscore_match = 100;
  287. $topic_underscore_match = 80;
  288. $topic_direct_match = 50;
  289. $topic_star_match = 10;
  290. $thatpattern_underscore_match = 45;
  291. $thatpattern_direct_match = 15;
  292. $thatpattern_star_match = 2;
  293. $direct_pattern_match = 10;
  294. $pattern_direct_match = 7;
  295. $pattern_star_match = 1;
  296. $default_pattern_match = 5;
  297. # individual word match scores:
  298. $uncommon_word_match = 8;
  299. $common_word_match = 1;
  300. $direct_word_match = 2;
  301. $underscore_word_match = 25;
  302. $star_word_match = 1;
  303. $rejected = -1;
  304. # loop through all relevant results
  305. foreach ($allrows as $all => $subrow)
  306. {
  307. $category_bot_id = isset($subrow['bot_id']) ? $subrow['bot_id'] : 1;
  308. $category_topic = $subrow['topic'];
  309. $category_thatpattern = $subrow['thatpattern'];
  310. $category_pattern = $subrow['pattern'];
  311. $check_pattern_words = true;
  312. # make it all lower case, to make it easier to test, and do it using mbstring functions if possible
  313. $category_pattern_lc = make_lc($category_pattern);
  314. $category_thatpattern_lc = make_lc($category_thatpattern);
  315. $category_topic_lc = make_lc($category_topic);
  316. $default_pattern_lc = make_lc($default_pattern);
  317. $pattern_lc = make_lc($pattern);
  318. $topic_lc = make_lc($topic);
  319. $that_lc = make_lc($that);
  320. // Start scoring here
  321. $current_score = 0;
  322. $track_matches = '';
  323. # 1.) Check for current bot, rather than parent
  324. if ($category_bot_id == $bot_id)
  325. {
  326. $current_score += $this_bot_match;
  327. $track_matches .= 'current bot, ';
  328. }
  329. elseif ($category_bot_id == $bot_parent_id)
  330. {
  331. $current_score += 0;
  332. $track_matches .= 'parent bot, ';
  333. }
  334. else # if it's not the current bot and not the parent bot, then reject it and log a debug message (this should never happen)
  335. {
  336. $current_score = $rejected;
  337. runDebug(__FILE__, __FUNCTION__, __LINE__,'Found an error trying to identify the chatbot.', 1);
  338. unset($allrows[$all]);
  339. continue;
  340. }
  341. # 2.) test for a non-empty current topic
  342. if (!empty($topic))
  343. {
  344. # 2a.) test for a non-empty topic in the current category
  345. if (empty($category_topic) || $category_topic == '*')
  346. {
  347. // take no action, as we're not looking for a topic here
  348. $track_matches .= 'no topic to match, ';
  349. }
  350. else
  351. {
  352. # 2b.) create a RegEx to test for underscore matches
  353. if (strpos($category_topic, '_') !== false)
  354. {
  355. $regEx = str_replace('_','(.*)', $category_topic);
  356. if ($regEx != $category_topic && preg_match("/$regEx/",$topic) === 1)
  357. {
  358. $current_score += $topic_underscore_match;
  359. $track_matches .= 'topic match with underscore, ';
  360. }
  361. }
  362. # 2c.) Check for a direct topic match
  363. elseif ($topic == $category_topic)
  364. {
  365. $current_score += $topic_direct_match;
  366. $track_matches .= 'direct topic match, ';
  367. }
  368. # 2d.) Check topic for a star wildcard match
  369. else
  370. {
  371. $regEx = str_replace(array('*','_'), '(.*)', $category_topic);
  372. if (preg_match("/$regEx/", $topic))
  373. {
  374. $current_score += $topic_star_match;
  375. $track_matches .= 'topic match with wildcards';
  376. }
  377. }
  378. }
  379. } # end topic testing
  380. # 3.) test for a category thatpattern
  381. if (empty($category_thatpattern) || $category_thatpattern == '*')
  382. {
  383. $current_score += 1;
  384. $track_matches .= 'no thatpattern to match, ';
  385. }
  386. else
  387. {
  388. if (strpos($category_thatpattern, '_') !== false)
  389. {
  390. # 3a.) Create a RegEx to search for underscore wildcards
  391. $regEx = str_replace('_','(.*)', $category_thatpattern);
  392. if ($regEx !== $category_thatpattern && preg_match("/$regEx/i",$that) === 1)
  393. {
  394. $current_score += $thatpattern_underscore_match;
  395. $track_matches .= 'thatpattern match with underscore, ';
  396. }
  397. }
  398. # 3b.) direct thatpattern match
  399. elseif ($that_lc == $category_thatpattern_lc)
  400. {
  401. $current_score += $thatpattern_direct_match;
  402. $track_matches .= 'direct thatpattern match, ';
  403. }
  404. # 3c.) thatpattern star matches
  405. else
  406. {
  407. $regEx = str_replace(array('*','_'), '(.*)', $category_thatpattern);
  408. if (preg_match("/$regEx/", $that))
  409. {
  410. $current_score += $thatpattern_star_match;
  411. $track_matches .= 'thatpattern match with star, ';
  412. }
  413. }
  414. } # end thatpattern testing
  415. # 4.) pattern testing
  416. # 4a.) Create a RegEx to search for underscore wildcards
  417. if (strpos($category_pattern, '_') !== false)
  418. {
  419. $regEx = str_replace('_','(.*)', $category_pattern);
  420. //save_file(_LOG_PATH_ . 'regex.txt', "$regEx\n", true);
  421. if ($regEx != $category_pattern && preg_match("/$regEx/",$pattern) === 1)
  422. {
  423. $current_score += $underscore_match;
  424. $track_matches .= 'pattern match with underscore, ';
  425. }
  426. }
  427. # 4b.) direct pattern match
  428. elseif ($pattern == $category_pattern)
  429. {
  430. $current_score += $pattern_direct_match;
  431. $track_matches .= 'direct pattern match, ';
  432. //$check_pattern_words = false;
  433. }
  434. # 4c.) pattern star matches
  435. else
  436. {
  437. $regEx = str_replace(array('*','_'), '(.*?)', $category_pattern);
  438. if ($category_pattern == '*')
  439. {
  440. $current_score += $pattern_star_match;
  441. $track_matches .= 'pattern star match, ';
  442. $check_pattern_words = false;
  443. }
  444. elseif ($regEx != $category_pattern && (($category_pattern != '*') || ($category_pattern != '_'))&& preg_match("/$regEx/", $pattern) != 0)
  445. {
  446. }
  447. } # end pattern testing
  448. # 4d.) See if the current category is the default category
  449. if ($category_pattern == $default_pattern_lc)
  450. {
  451. runDebug(__FILE__, __FUNCTION__, __LINE__,'This category is the default pattern!', 4);
  452. $current_score += $default_pattern_match;
  453. $track_matches .= 'default pattern match, ';
  454. $check_pattern_words = false;
  455. }
  456. #5.) check to see if we need to score word by word
  457. if ($check_pattern_words && $category_pattern_lc != $default_pattern_lc)
  458. {
  459. # 5a.) first, a little setup
  460. $pattern_lc = make_lc($pattern);
  461. $category_pattern_lc = make_lc($category_pattern);
  462. $pattern_words = explode(" ", $pattern_lc);
  463. # 5b.) break the input pattern into an array of individual words and iterate through the array
  464. $category_pattern_words = explode(" ", $category_pattern_lc);
  465. foreach ($category_pattern_words as $word)
  466. {
  467. $word = trim($word);
  468. switch (true)
  469. {
  470. case ($word === '_'):
  471. $current_score += $underscore_word_match;
  472. $track_matches .= 'underscore word match, ';
  473. break;
  474. case ($word === '*'):
  475. $current_score += $star_word_match;
  476. $track_matches .= 'star word match, ';
  477. break;
  478. case (in_array($word, $pattern_words)):
  479. $current_score += $direct_word_match;
  480. $track_matches .= "direct word match: $word, ";
  481. break;
  482. case (in_array($word, $common_words_array)):
  483. $current_score += $common_word_match;
  484. $track_matches .= "common word match: $word, ";
  485. break;
  486. default:
  487. $current_score += $uncommon_word_match;
  488. $track_matches .= "uncommon word match: $word, ";
  489. }
  490. }
  491. }
  492. $allrows[$all]['score'] += $current_score;
  493. $allrows[$all]['track_score'] = rtrim($track_matches, ', ');
  494. }
  495. //runDebug(__FILE__, __FUNCTION__, __LINE__, "Unsorted array \$allrows:\n" . print_r($allrows, true), 4);
  496. $allrows = sort2DArray("show top scoring aiml matches", $allrows, "score", 1, 10);
  497. runDebug(__FILE__, __FUNCTION__, __LINE__, "Sorted array \$allrows:\n" . print_r($allrows, true), 4);
  498. return $allrows;
  499. }
  500. /**
  501. * Small helper function to sort a 2d array
  502. *
  503. * @param string $opName
  504. * @param array $thisArr
  505. * @param $sortByItem
  506. * @param $sortAsc
  507. * @param $limit
  508. * @return void;
  509. **/
  510. function sort2DArray($opName, $thisArr, $sortByItem, $sortAsc = 1, $limit = 10)
  511. {
  512. $thisCount = count($thisArr);
  513. /** @noinspection PhpUnusedLocalVariableInspection */
  514. $showLimit = ($thisCount < $limit) ? $thisCount : $limit;
  515. $i = 0;
  516. $tmpSortArr = array();
  517. $resArr = array();
  518. /** @noinspection PhpUnusedLocalVariableInspection */
  519. $last_high_score = 0;
  520. //loop through the results and put in tmp array to sort
  521. foreach ($thisArr as $all => $subrow)
  522. {
  523. if (isset ($subrow[$sortByItem]))
  524. {
  525. //$tmpSortArr[$subrow[$sortByItem]] = $subrow[$sortByItem];
  526. $tmpSortArr[] = $subrow[$sortByItem];
  527. }
  528. }
  529. //sort the results
  530. if ($sortAsc == 1)
  531. {
  532. //ascending
  533. arsort($tmpSortArr);
  534. }
  535. else
  536. {
  537. //descending
  538. asort($tmpSortArr);
  539. }
  540. //loop through scores
  541. foreach ($tmpSortArr as $sortedKey => $idValue)
  542. {
  543. $resArr[] = $thisArr[$sortedKey];
  544. }
  545. //get the limited top results
  546. $outArr = array_slice($resArr, 0, $limit);
  547. return $outArr;
  548. }
  549. /**
  550. * function get_highest_scoring_row()
  551. * This function takes all the relevant and scored aiml results
  552. * and saves the highest scoring rows
  553. * @param array $convoArr - the conversation array
  554. * @param array $allrows - all the results
  555. * @param string $lookingfor - the user input
  556. * @return array bestResponseArr - best response and its parts (topic etc)
  557. **/
  558. function get_highest_scoring_row(& $convoArr, $allrows, $lookingfor)
  559. {
  560. $bestResponse = array();
  561. $last_high_score = 0;
  562. $tmpArr = array();
  563. //loop through the results
  564. foreach ($allrows as $all => $subrow)
  565. {
  566. if (!isset ($subrow['score']))
  567. {
  568. continue;
  569. }
  570. elseif ($subrow['score'] > $last_high_score)
  571. {
  572. //if higher than last score then reset tmp array and store this result
  573. $tmpArr = array($subrow);
  574. $last_high_score = $subrow['score'];
  575. }
  576. elseif ($subrow['score'] == $last_high_score)
  577. {
  578. //if same score as current high score add to array
  579. $tmpArr[] = $subrow;
  580. }
  581. }
  582. //there may be any number of results with the same score so pick any random one
  583. $bestResponse = (count($tmpArr) > 0) ? $tmpArr[array_rand($tmpArr)] : false;
  584. if (false !== $bestResponse) $bestResponse['template'] = get_winning_category($convoArr, $bestResponse['aiml_id']);
  585. $cRes = count($tmpArr);
  586. runDebug(__FILE__, __FUNCTION__, __LINE__, "Best Responses: " . print_r($tmpArr, true), 4);
  587. runDebug(__FILE__, __FUNCTION__, __LINE__, "Will use randomly picked best response chosen out of $cRes responses with same score: " . $bestResponse['aiml_id'] . " - " . $bestResponse['pattern'], 2);
  588. //return the best response
  589. return $bestResponse;
  590. }
  591. /**
  592. * function get_winning_category
  593. * Retrieves the AIML template from the selected DB entry
  594. *
  595. * @param $convoArr
  596. * @param array $id - the id number of the AIML category to get
  597. * @return string $template - the value of the `template` field from the chosen DB entry
  598. */
  599. function get_winning_category(& $convoArr, $id)
  600. {
  601. runDebug(__FILE__, __FUNCTION__, __LINE__, "And the winner is... $id!", 2);
  602. global $dbConn, $dbn, $error_response;
  603. $sql = "SELECT `template` , `button` from `$dbn`.`aiml` where `id` = $id limit 1;";
  604. $row = db_fetch($sql, null, __FILE__, __FUNCTION__, __LINE__);
  605. if ($row)
  606. {
  607. $template = $row['template'];
  608. $convoArr['aiml']['template_id'] = $id;
  609. $convoArr['aiml']['button'] =$row['button'];
  610. }
  611. else
  612. {
  613. $template = $error_response;
  614. }
  615. runDebug(__FILE__, __FUNCTION__, __LINE__, "Returning the AIML template for id# $id. Value:\n'$template'", 4);
  616. return $template;
  617. }
  618. /**
  619. * function get_convo_var()
  620. * This function takes fetches a variable from the conversation array
  621. *
  622. * @param array $convoArr - conversation array
  623. * @param string $index_1 - convoArr[$index_1]
  624. * @param string $index_2 - convoArr[$index_1][$index_2]
  625. * @param int|string $index_3 - convoArr[$index_1][$index_2][$index_3]
  626. * @param int|string $index_4 - convoArr[$index_1][$index_2][$index_3][$index_4]
  627. * @return string $value - the value of the element
  628. *
  629. *
  630. * examples
  631. *
  632. * $convoArr['conversation']['bot_id'] = $convoArr['conversation']['bot_id']
  633. * $convoArr['that'][1][1] = get_convo_var($convoArr,'that','',1,1)
  634. */
  635. function get_convo_var($convoArr, $index_1, $index_2 = '', $index_3 = '', $index_4 = '')
  636. {
  637. if ($index_2 == '')
  638. $index_2 = "~NULL~";
  639. if ($index_3 == '')
  640. $index_3 = 1;
  641. if ($index_4 == '')
  642. $index_4 = 1;
  643. runDebug(__FILE__, __FUNCTION__, __LINE__, "Get from ConvoArr [$index_1][$index_2][$index_3][$index_4]", 4);
  644. if ((isset ($convoArr[$index_1])) && (!is_array($convoArr[$index_1])) && ($convoArr[$index_1] != ''))
  645. {
  646. $value = $convoArr[$index_1];
  647. }
  648. elseif ((isset ($convoArr[$index_1][$index_3])) && (!is_array($convoArr[$index_1][$index_3])) && ($convoArr[$index_1][$index_3] != ''))
  649. {
  650. $value = $convoArr[$index_1][$index_3];
  651. }
  652. elseif ((isset ($convoArr[$index_1][$index_3][$index_4])) && (!is_array($convoArr[$index_1][$index_3][$index_4])) && ($convoArr[$index_1][$index_3][$index_4] != ''))
  653. {
  654. $value = $convoArr[$index_1][$index_3][$index_4];
  655. }
  656. elseif ((isset ($convoArr[$index_1][$index_2])) && (!is_array($convoArr[$index_1][$index_2])) && ($convoArr[$index_1][$index_2] != ''))
  657. {
  658. $value = $convoArr[$index_1][$index_2];
  659. }
  660. elseif ((isset ($convoArr[$index_1][$index_2][$index_3])) && (!is_array($convoArr[$index_1][$index_2][$index_3])) && ($convoArr[$index_1][$index_2][$index_3] != ''))
  661. {
  662. $value = $convoArr[$index_1][$index_2][$index_3];
  663. }
  664. elseif ((isset ($convoArr[$index_1][$index_2][$index_3][$index_4])) && (!is_array($convoArr[$index_1][$index_2][$index_3][$index_4])) && ($convoArr[$index_1][$index_2][$index_3][$index_4] != ''))
  665. {
  666. $value = $convoArr[$index_1][$index_2][$index_3][$index_4];
  667. }
  668. else
  669. {
  670. $value = '';
  671. }
  672. runDebug(__FILE__, __FUNCTION__, __LINE__, "Get from ConvoArr [$index_1][$index_2][$index_3][$index_4] FOUND: ConvoArr Value = '$value'", 4);
  673. return $value;
  674. }
  675. /**
  676. * Function: get_client_property()
  677. * Summary: Extracts a value from the the client properties subarray within the main conversation array
  678. * @param Array $convoArr - the main conversation array
  679. * @param String $name - the key of the value to extract from client properties
  680. * @return String $response - the value of the client property
  681. **/
  682. function get_client_property($convoArr, $name)
  683. {
  684. runDebug(__FILE__, __FUNCTION__, __LINE__, 'Rummaging through the DB and stuff for a client property.', 2);
  685. runDebug(__FILE__, __FUNCTION__, __LINE__, "Looking for client property '$name'", 2);
  686. global $dbConn, $dbn;
  687. If (isset ($convoArr['client_properties'][$name]))
  688. {
  689. $value = $convoArr['client_properties'][$name];
  690. runDebug(__FILE__, __FUNCTION__, __LINE__, "Found client property '$name' in the conversation array. Returning '$value'", 2);
  691. return $convoArr['client_properties'][$name];
  692. }
  693. runDebug(__FILE__, __FUNCTION__, __LINE__, "Client property '$name' not found in the conversation array. Searching the DB.", 2);
  694. $user_id = $convoArr['conversation']['user_id'];
  695. $bot_id = $convoArr['conversation']['bot_id'];
  696. $sql = "select `value` from `$dbn`.`client_properties` where `user_id` = $user_id and `bot_id` = $bot_id and `name` = '$name' limit 1;";
  697. runDebug(__FILE__, __FUNCTION__, __LINE__, "Querying the client_properties table for $name. SQL:\n$sql", 3);
  698. $row = db_fetch($sql, null, __FILE__, __FUNCTION__, __LINE__);
  699. $rowCount = count($row);
  700. if ($rowCount != 0)
  701. {
  702. $response = trim($row['value']);
  703. $convoArr['client_properties'][$name] = $response;
  704. runDebug(__FILE__, __FUNCTION__, __LINE__, "Found client property '$name' in the DB. Adding it to the conversation array and returning '$response'", 2);
  705. }
  706. else $response = 'undefined';
  707. return $response;
  708. }
  709. /**
  710. * function find_userdefined_aiml()
  711. * This function searches the user defined aiml patterns first
  712. * It will show an unmoderated response if the user_id's match
  713. * Or if you wish to approve a response to everyone set the user_id to -1
  714. * @param array $convoArr - conversation array
  715. * @return array allrows
  716. **/
  717. function find_userdefined_aiml($convoArr)
  718. {
  719. runDebug(__FILE__, __FUNCTION__, __LINE__, 'Looking for user defined responses', 4);
  720. global $dbn, $dbConn;
  721. $i = 0;
  722. $allrows = array();
  723. $bot_id = $convoArr['conversation']['bot_id'];
  724. $user_id = $convoArr['conversation']['user_id'];
  725. $lookingfor = $convoArr['aiml']['lookingfor'];
  726. //build sql
  727. $sql = "SELECT * FROM `$dbn`.`aiml_userdefined` WHERE
  728. `bot_id` = '$bot_id' AND
  729. (`user_id` = '$user_id' OR `user_id` = '-1') AND
  730. `pattern` = '$lookingfor'";
  731. runDebug(__FILE__, __FUNCTION__, __LINE__, "User defined SQL: $sql", 3);
  732. $result = db_fetchAll($sql, null, __FILE__, __FUNCTION__, __LINE__);
  733. $num_rows = count($result);
  734. //if there is a result get it
  735. if (($result) && ($num_rows > 0))
  736. {
  737. //loop through results
  738. foreach ($result as $row)
  739. {
  740. $allrows['pattern'] = $row['pattern'];
  741. $allrows['thatpattern'] = $row['thatpattern'];
  742. $allrows['template'] = $row['template'];
  743. $allrows['topic'] = $row['topic'];
  744. $i++;
  745. }
  746. }
  747. runDebug(__FILE__, __FUNCTION__, __LINE__, "User defined rows found: '$i'", 2);
  748. //return rows
  749. return $allrows;
  750. }
  751. /**
  752. * function get_aiml_to_parse()
  753. * This function controls all the process to match the aiml in the db to the user input
  754. * @param array $convoArr - conversation array
  755. * @return array $convoArr
  756. **/
  757. function get_aiml_to_parse($convoArr)
  758. {
  759. runDebug(__FILE__, __FUNCTION__, __LINE__, "Running all functions to get the correct aiml from the DB", 4);
  760. $lookingfor = $convoArr['aiml']['lookingfor'];
  761. $current_thatpattern = (isset ($convoArr['that'][1][1])) ? $convoArr['that'][1][1] : '';
  762. $current_topic = get_topic($convoArr);
  763. $aiml_pattern = $convoArr['conversation']['default_aiml_pattern'];
  764. $bot_parent_id = $convoArr['conversation']['bot_parent_id'];
  765. $raw_that = (isset ($convoArr['that'])) ? print_r($convoArr['that'], true) : '';
  766. //check if match in user defined aiml
  767. $allrows = find_userdefined_aiml($convoArr);
  768. //if there is no match in the user defined aiml table
  769. if ((!isset ($allrows)) || (count($allrows) <= 0))
  770. {
  771. //look for a match in the normal aiml tbl
  772. $allrows = find_aiml_matches($convoArr);
  773. //unset all irrelvant matches
  774. $allrows = unset_all_bad_pattern_matches($convoArr, $allrows, $lookingfor);
  775. //score the relevant matches
  776. $allrows = score_matches($convoArr, $allrows, $lookingfor);
  777. //get the highest
  778. $allrows = get_highest_scoring_row($convoArr, $allrows, $lookingfor);
  779. //READY FOR v2.5 do not uncomment will not work
  780. //check if this is an unknown input and place in the unknown_inputs table if true
  781. //check_and_add_unknown_inputs($allrows,$convoArr);
  782. }
  783. //Now we have the results put into the conversation array
  784. $convoArr['aiml']['pattern'] = $allrows['pattern'];
  785. $convoArr['aiml']['thatpattern'] = $allrows['thatpattern'];
  786. $convoArr['aiml']['template'] = $allrows['template'];
  787. $convoArr['aiml']['html_template'] = '';
  788. $convoArr['aiml']['topic'] = $allrows['topic'];
  789. $convoArr['aiml']['score'] = $allrows['score'];
  790. $convoArr['aiml']['aiml_id'] = $allrows['aiml_id'];
  791. //return
  792. runDebug(__FILE__, __FUNCTION__, __LINE__, "Will be parsing id:" . $allrows['aiml_id'] . " (" . $allrows['pattern'] . ")", 4);
  793. return $convoArr;
  794. }
  795. /**
  796. * function check_and_add_unknown_inputs()
  797. * READY FOR v2.5
  798. * This function adds inputs without a response to the unknown_inputs table
  799. * @param array $allrows - the highest scoring return rows
  800. * @param array $convoArr - conversation array
  801. * @return void
  802. **/
  803. function check_and_add_unknown_inputs($allrows, $convoArr)
  804. {
  805. if ($allrows['pattern'] == $convoArr['conversation']['default_aiml_pattern'])
  806. {
  807. global $dbConn, $dbn;
  808. runDebug(__FILE__, __FUNCTION__, __LINE__, "Adding unknown input", 2);
  809. runDebug(__FILE__, __FUNCTION__, __LINE__, "Pattern: " . $convoArr['aiml']['lookingfor'], 2);
  810. $pattern = trim(normalize_text($convoArr['aiml']['lookingfor']));
  811. $pattern = $pattern . " ";
  812. $u_id = $convoArr['conversation']['user_id'];
  813. $bot_id = $convoArr['conversation']['bot_id'];
  814. $sql = "INSERT INTO `$dbn`.`unknown_inputs`
  815. VALUES
  816. (NULL, '" . $pattern . "','$bot_id','$u_id',NOW())";
  817. runDebug(__FILE__, __FUNCTION__, __LINE__, "Unknown Input SQL: $sql", 3);
  818. $sth = $dbConn->prepare($sql);
  819. $sth->execute();
  820. }
  821. }
  822. /**
  823. * function find_aiml_matches()
  824. * This function builds the sql to use to get a match from the tbl
  825. * @param array $convoArr - conversation array
  826. * @return array $convoArr
  827. **/
  828. function find_aiml_matches($convoArr)
  829. {
  830. global $dbConn, $dbn, $error_response, $use_parent_bot;
  831. runDebug(__FILE__, __FUNCTION__, __LINE__, "Finding the aiml matches from the DB", 4);
  832. $i = 0;
  833. //TODO convert to get_it
  834. $bot_id = $convoArr['conversation']['bot_id'];
  835. $bot_parent_id = $convoArr['conversation']['bot_parent_id'];
  836. $default_aiml_pattern = $convoArr['conversation']['default_aiml_pattern'];
  837. #$lookingfor = get_convo_var($convoArr,"aiml","lookingfor");
  838. $convoArr['aiml']['lookingfor'] = str_replace(' ', ' ', $convoArr['aiml']['lookingfor']);
  839. $lookingfor = trim(strtoupper($convoArr['aiml']['lookingfor']));
  840. //get the first and last words of the cleaned user input
  841. $lastInputWord = get_last_word($lookingfor);
  842. $firstInputWord = get_first_word($lookingfor);
  843. //get the stored topic
  844. $storedtopic = get_topic($convoArr);
  845. runDebug(__FILE__, __FUNCTION__, __LINE__,"Stored topic = '$storedtopic'", 4);
  846. //get the cleaned user input
  847. $lastthat = (isset ($convoArr['that'][1][1])) ? $convoArr['that'][1][1] : '';
  848. //build like patterns
  849. if ($lastthat != '')
  850. {
  851. $thatPatternSQL = " OR " . make_like_pattern($lastthat, 'thatpattern');
  852. $thatPatternSQL = rtrim($thatPatternSQL, ' OR');
  853. }
  854. else
  855. {
  856. $thatPattern = '';
  857. $thatPatternSQL = '';
  858. }
  859. //get the word count
  860. $word_count = wordsCount_inSentence($lookingfor);
  861. if ($bot_parent_id != 0 and $bot_parent_id != $bot_id)
  862. {
  863. $sql_bot_select = " (bot_id = '$bot_id' OR bot_id = '$bot_parent_id') ";
  864. }
  865. else
  866. {
  867. $sql_bot_select = " bot_id = '$bot_id' ";
  868. }
  869. if (!empty($storedtopic))
  870. {
  871. $topic_select = "AND (`topic`='' OR `topic`='$storedtopic')";
  872. }
  873. else
  874. {
  875. $topic_select = '';
  876. }
  877. if ($word_count == 1)
  878. {
  879. //if there is one word do this
  880. $sql = "SELECT `id`, `pattern`, `thatpattern`, `topic` FROM `$dbn`.`aiml` WHERE
  881. $sql_bot_select AND (
  882. `pattern` = '_' OR
  883. `pattern` = '*' OR
  884. `pattern` = '$lookingfor' OR
  885. `pattern` = '$default_aiml_pattern'
  886. $thatPatternSQL
  887. ) $topic_select order by `topic` desc, `pattern` asc, `thatpattern` asc,`id` asc;";
  888. }
  889. else
  890. {
  891. //otherwise do this
  892. $sql_add = make_like_pattern($lookingfor, 'pattern');
  893. $sql = "SELECT `id`, `bot_id`, `pattern`, `thatpattern`, `topic` FROM `$dbn`.`aiml` WHERE
  894. $sql_bot_select AND (
  895. `pattern` = '_' OR
  896. `pattern` = '*' OR
  897. `pattern` = '$lookingfor' OR $sql_add
  898. `pattern` = '$default_aiml_pattern'
  899. $thatPatternSQL
  900. ) $topic_select
  901. order by `topic` desc, `pattern` asc, `thatpattern` asc,`id` asc;";
  902. }
  903. runDebug(__FILE__, __FUNCTION__, __LINE__, "Core Match AIML sql: $sql", 3);
  904. $result = db_fetchAll($sql, null, __FILE__, __FUNCTION__, __LINE__);
  905. $num_rows = count($result);
  906. if (($result) && ($num_rows > 0))
  907. {
  908. $tmp_rows = number_format($num_rows);
  909. runDebug(__FILE__, __FUNCTION__, __LINE__, "FOUND: ($num_rows) potential AIML matches", 2);
  910. $tmp_content = date('H:i:s') . ": SQL:\n$sql\nRows = $tmp_rows\n\n";
  911. //loop through results
  912. foreach ($result as $row)
  913. {
  914. $row['aiml_id'] = $row['id'];
  915. $row['bot_id'] = $bot_id;
  916. $row['score'] = 0;
  917. $row['track_score'] = '';
  918. $allrows[] = $row;
  919. $mu = memory_get_usage(true);
  920. if ($mu >= MEM_TRIGGER)
  921. {
  922. runDebug(__FILE__, __FUNCTION__, __LINE__, 'Current operation exceeds memory threshold. Aborting data retrieval.', 0);
  923. break;
  924. }
  925. }
  926. }
  927. else
  928. {
  929. runDebug(__FILE__, __FUNCTION__, __LINE__, "Error: FOUND NO AIML matches in DB", 1);
  930. $allrows[$i]['aiml_id'] = "-1";
  931. $allrows[$i]['bot_id'] = "-1";
  932. $allrows[$i]['pattern'] = "no results";
  933. $allrows[$i]['thatpattern'] = '';
  934. $allrows[$i]['topic'] = '';
  935. }
  936. return $allrows;
  937. }
  938. /** get_topic()
  939. * Extracts the current topic directly from the database
  940. *
  941. * @param Array $convoArr - the conversation array
  942. * returns String $retval - the topic
  943. * *@return string
  944. */
  945. function get_topic($convoArr)
  946. {
  947. global $dbConn, $dbn, $bot_id;
  948. $bot_id = (!empty($convoArr['conversation']['bot_id'])) ? $convoArr['conversation']['bot_id'] : $bot_id;
  949. $user_id = $convoArr['conversation']['user_id'];
  950. $sql = "SELECT `value` FROM `client_properties` WHERE `user_id` = $user_id AND `bot_id` = $bot_id and `name` = 'topic';";
  951. $row = db_fetch($sql, null, __FILE__, __FUNCTION__, __LINE__);
  952. $num_rows = count($row);
  953. $retval = ($num_rows == 0) ? '' : $row['value'];
  954. return $retval;
  955. }
  956. function make_lc($txt)
  957. {
  958. return (IS_MB_ENABLED) ? mb_strtolower($txt) : strtolower($txt);
  959. }