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

/assets/snippets/ajaxSearch/classes/ajaxSearchResults.class.inc.php

https://github.com/garryn/evolution
PHP | 1319 lines | 1090 code | 80 blank | 149 comment | 250 complexity | f7abbea753236699220e6da449fbf673 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /* -----------------------------------------------------------------------------
  3. * Snippet: AjaxSearch
  4. * -----------------------------------------------------------------------------
  5. * @package AjaxSearchResults
  6. *
  7. * @author Coroico - www.modx.wangba.fr
  8. * @version 1.9.0
  9. * @date 18/05/2010
  10. *
  11. * Purpose:
  12. * The AjaxSearchResults class contains all functions and data used to manage Results
  13. *
  14. */
  15. define('GROUP_CONCAT_LENGTH', 4096); // maximum length of the group concat
  16. class AjaxSearchResults {
  17. // public variables
  18. var $asCfg = null;
  19. var $asCtrl = null;
  20. var $asOutput = null;
  21. var $asUtil = null;
  22. var $asLog = null;
  23. var $dbg = false;
  24. var $dbgRes = false;
  25. var $log;
  26. var $groupResults = array();
  27. var $extractNb;
  28. var $withExtract;
  29. // private variables
  30. var $_siteList;
  31. var $_subsiteList;
  32. var $_groupMixedResults = array();
  33. var $_extractFields = array();
  34. var $_asRequest;
  35. var $_array_key, $_filtertype, $_filterValue;
  36. var $_idType;
  37. var $_pardoc;
  38. var $_depth;
  39. /*
  40. * Constructs the ajaxSearchResults object
  41. *
  42. * @access public
  43. */
  44. function AjaxSearchResults() {
  45. }
  46. /*
  47. * Initializes the class into the proper context
  48. *
  49. * @access public
  50. * @param AjaxSearchConfig &$asCfg configuration context
  51. * @param AjaxSearchCtrl &$asCtrl controler instance
  52. * @param AjaxSearchOutput &$asOutput ouput instance
  53. * @param AjaxSearchUtil &$asUtil debug instance
  54. * @param boolean $dbg debug flag
  55. * @param boolean $dbgRes debug flag for results
  56. */
  57. function init(&$asCfg, &$asCtrl, &$asOutput, &$asUtil){
  58. $this->asCfg =& $asCfg;
  59. $this->asCtrl =& $asCtrl;
  60. $this->asOutput =& $asOutput;
  61. $this->asUtil =& $asUtil;
  62. $this->dbg = $asUtil->dbg;
  63. $this->dbgRes = $asUtil->dbgRes;
  64. }
  65. /*
  66. * Get search results
  67. *
  68. * @access public
  69. * @param string &$msgErr message error
  70. * @return boolean true if ok
  71. */
  72. function getSearchResults(&$msgErr) {
  73. global $modx;
  74. $results = array();
  75. include_once AS_PATH . "classes/ajaxSearchRequest.class.inc.php";
  76. if (class_exists('AjaxSearchRequest')) {
  77. $this->_asRequest = new AjaxSearchRequest($this->asUtil,$this->asCfg->pgCharset);
  78. }
  79. if (!$this->_getSiteList($msgErr)) return false;
  80. foreach ($this->_siteList as $site) {
  81. if (!$this->_getSubsiteList($site,$msgErr)) return false;
  82. foreach ($this->_subsiteList as $subsite) {
  83. if (!$this->_getSubsiteParams($site, $subsite,$msgErr)) return false;
  84. if (!$this->_checkParams($msgErr)) return false;
  85. $this->asCfg->saveConfig($site, $subsite);
  86. if ($this->asCfg->cfg['showResults']) {
  87. $this->asOutput->initClassVariables();
  88. $bsf = $this->_doBeforeSearchFilter();
  89. $results = $this->_asRequest->doSearch($this->asCtrl->searchString, $this->asCtrl->advSearch, $this->asCfg->cfg, $bsf, $this->asCtrl->fClause);
  90. $results = $this->_doFilter($results, $this->asCtrl->searchString, $this->asCtrl->advSearch);
  91. $this->_setSearchResults($site, $subsite, $results);
  92. }
  93. }
  94. }
  95. $this->asCfg->restoreConfig(DEFAULT_SITE, DEFAULT_SUBSITE);
  96. $this->_sortMixedResults();
  97. if ($this->dbgRes) $this->asUtil->dbgRecord($this->asCfg->scfg, "AjaxSearch - scfg");
  98. if ($this->dbgRes) $this->asUtil->dbgRecord($this->groupResults, "AjaxSearch - group results");
  99. if ($this->dbgRes) $this->asUtil->dbgRecord($this->_groupMixedResults, "AjaxSearch - group mixed results");
  100. return true;
  101. }
  102. /*
  103. * Get the list of sites from snippet call
  104. */
  105. function _getSiteList(&$msgErr) {
  106. $siteList = array();
  107. if ($this->asCtrl->forThisAs) {
  108. if ($this->asCfg->cfg['sites']) $siteList = explode(',', $this->asCfg->cfg['sites']);
  109. else $siteList[0] = DEFAULT_SITE;
  110. }
  111. if ($this->dbgRes) $this->asUtil->dbgRecord($siteList, "getSiteList - siteList");
  112. $this->_siteList = $siteList;
  113. return true;
  114. }
  115. /*
  116. * Get the list of subsites from $_POST['subsearch'] and $_GET['subsearch']
  117. */
  118. function _getSubsiteList($site, &$msgErr) {
  119. $subsiteList = array();
  120. if ($this->asCtrl->forThisAs) {
  121. if ($this->asCtrl->subSearch) $subsiteList = explode(',', $this->asCtrl->subSearch);
  122. else $subsiteList[0] = DEFAULT_SUBSITE;
  123. }
  124. if ($this->dbgRes) $this->asUtil->dbgRecord($subsiteList, "getSubsiteList - subsiteList");
  125. $this->_subsiteList = $subsiteList;
  126. return true;
  127. }
  128. /*
  129. * Get the parameters for each subsite
  130. */
  131. function _getSubsiteParams($site, $subsite, &$msgErr) {
  132. $msgErr = '';
  133. if ($site != DEFAULT_SITE) {
  134. $siteConfigFunction = SITE_CONFIG;
  135. if (!function_exists($siteConfigFunction)) {
  136. $msgErr = '<br /><h3>AjaxSearch error: search function ' . $siteConfigFunction . ' not defined in the configuration file: ' . $this->asCfg->cfg['config'] . ' !</h3><br />';
  137. return false;
  138. }
  139. else {
  140. $sitecfg = $siteConfigFunction($site);
  141. if (!count($sitecfg)) {
  142. $msgErr = '<br /><h3>AjaxSearch error: Site ' .$site . ' not defined in the configuration file: ' . $this->asCfg->cfg['config'] . ' !</h3><br />';
  143. return false;
  144. }
  145. }
  146. }
  147. if ($subsite != DEFAULT_SUBSITE) {
  148. $subsiteConfigFunction = SUBSITE_CONFIG;
  149. if (!function_exists($subsiteConfigFunction)) {
  150. $msgErr = '<br /><h3>AjaxSearch error: search function ' . $subsiteConfigFunction . ' not defined in the configuration file: ' . $this->asCfg->cfg['config'] . ' !</h3><br />';
  151. return false;
  152. }
  153. else {
  154. $subsitecfg = $subsiteConfigFunction($site,$subsite);
  155. if (!count($subsitecfg)) {
  156. $msgErr = '<br /><h3>AjaxSearch error: Subsite ' .$subsite . ' of ' . $site . 'not defined in the configuration file: ' . $this->asCfg->cfg['config'] . ' !</h3><br />';
  157. return false;
  158. }
  159. }
  160. }
  161. $this->asCfg->cfg = array_merge($this->asCfg->bcfg, (array)$sitecfg, (array)$subsitecfg);
  162. return true;
  163. }
  164. /*
  165. * Check or not search params
  166. */
  167. function _checkParams(&$msgErr) {
  168. global $modx;
  169. $msgErr = '';
  170. if ($this->asCtrl->forThisAs) {
  171. if (isset($this->asCfg->cfg['extractLength'])) {
  172. if ($this->asCfg->cfg['extractLength'] == 0) $this->asCfg->cfg['extract'] = 0;
  173. if ($this->asCfg->cfg['extractLength'] < EXTRACT_MIN) $this->asCfg->cfg['extractLength'] = EXTRACT_MIN;
  174. if ($this->asCfg->cfg['extractLength'] > EXTRACT_MAX) $this->asCfg->cfg['extractLength'] = EXTRACT_MAX;
  175. }
  176. if (isset($this->asCfg->cfg['extract'])) {
  177. $extr = explode(':', $this->asCfg->cfg['extract']);
  178. if (($extr[0] == '') || (!is_numeric($extr[0]))) $extr[0] = 0;
  179. if (($extr[1] == '') || (is_numeric($extr[1]))) $extr[1] = 'content';
  180. $this->asCfg->cfg['extract'] = $extr[0] . ":" . $extr[1];
  181. }
  182. if (isset($this->asCfg->cfg['opacity'])) {
  183. if ($this->asCfg->cfg['opacity'] < 0.) $this->asCfg->cfg['opacity'] = 0.;
  184. if ($this->asCfg->cfg['opacity'] > 1.) $this->asCfg->cfg['opacity'] = 1.;
  185. }
  186. // check that the tables where to do the search exist
  187. if (isset($this->asCfg->cfg['whereSearch'])) {
  188. $tables_array = explode('|', $this->asCfg->cfg['whereSearch']);
  189. foreach ($tables_array as $table) {
  190. $fields_array = explode(':', $table);
  191. $tbcode = $fields_array[0];
  192. if (($tbcode != 'content') && ($tbcode != 'tv') && ($tbcode != 'jot') && ($tbcode != 'maxigallery') && !function_exists($tbcode)) {
  193. $msgErr = "<br /><h3>AjaxSearch error: table $tbcode not defined in the configuration file: " . $this->asCfg->cfg['config'] . " !</h3><br />";
  194. return false;
  195. }
  196. }
  197. }
  198. // check the list of tvs enabled with "withTvs"
  199. if ((isset($this->asCfg->cfg['withTvs'])) && ($this->asCfg->cfg['withTvs'])){
  200. $tv_array = explode(':', $this->asCfg->cfg['withTvs']);
  201. $tvSign = $tv_array[0];
  202. if (($tvSign != '+') && ($tvSign != '-')) {
  203. $tvList = $tvSign;
  204. $tvSign = '+';
  205. }
  206. else {
  207. if (isset($tv_array[1])) $tvList = $tv_array[1];
  208. else $tvList = '';
  209. }
  210. if (!$this->_validListTvs($tvList, $msgErr)) return False;
  211. $this->asCfg->cfg['withTvs'] = ($tvList) ? $tvSign . ':' . $tvList : $tvSign;
  212. }
  213. // check the list of tvs enabled with "phxTvs" - filter the tv already enabled by withTvs
  214. if ((isset($this->asCfg->cfg['withTvs'])) && ($this->asCfg->cfg['phxTvs'])){
  215. unset($tv_array);
  216. $tv_array = explode(':', $this->asCfg->cfg['phxTvs']);
  217. $tvSign = $tv_array[0];
  218. if (($tvSign != '+') && ($tvSign != '-')) {
  219. $tvList = $tvSign;
  220. $tvSign = '+';
  221. }
  222. else {
  223. if (isset($tv_array[1])) $tvList = $tv_array[1];
  224. else $tvList = '';
  225. }
  226. if (!$this->_validListTvs($tvList, $msgErr)) return False;
  227. $this->asCfg->cfg['phxTvs'] = ($tvList) ? $tvSign . ':' . $tvList : $tvSign;
  228. }
  229. if (isset($this->asCfg->cfg['hideMenu'])) {
  230. $this->asCfg->cfg['hideMenu'] = (($this->asCfg->cfg['hideMenu'] < 0) || ($this->asCfg->cfg['hideMenu'] > 2)) ? 2 : $this->asCfg->cfg['hideMenu'];
  231. }
  232. if (isset($this->asCfg->cfg['hideLink'])) {
  233. $this->asCfg->cfg['hideLink'] = (($this->asCfg->cfg['hideLink'] < 0) || ($this->asCfg->cfg['hideLink'] > 1)) ? 1 : $this->asCfg->cfg['hideLink'];
  234. }
  235. $this->_idType = ($this->asCfg->cfg['documents'] != "") ? "documents" : "parents";
  236. $this->_pardoc = ($this->_idType == "parents") ? $this->asCfg->cfg['parents'] : $this->asCfg->cfg['documents'];
  237. $this->_depth = $this->asCfg->cfg['depth'];
  238. $this->asCfg->cfg['docgrp'] = '';
  239. if ($docgrp = $modx->getUserDocGroups()) $this->asCfg->cfg['docgrp'] = implode(",", $docgrp);
  240. if (isset($this->asCfg->cfg['filter'])) {
  241. }
  242. } else {
  243. $this->asCfg->cfg['showResults'] = false;
  244. }
  245. return true;
  246. }
  247. /*
  248. * Set up search results
  249. */
  250. function _setSearchResults($site, $subsite, $rs) {
  251. global $modx;
  252. $nbrs = count($rs);
  253. if (!$nbrs) return false;
  254. $categConfigFunction = CATEG_CONFIG;
  255. $this->_initExtractVariables();
  256. $display = $this->asCfg->cfg['display'];
  257. $select = $this->_asRequest->asSelect;
  258. if (($display == MIXED)) {
  259. $this->asCfg->chooseConfig(DEFAULT_SITE, $DEFAULT_SUBSITE, $display);
  260. if (!isset($this->_groupMixedResults['length'])) {
  261. $this->_groupMixedResults = $this->_setHeaderGroupResults(MIXED_SITES, $subsite, $display, 'N/A', $select, $nbrs);
  262. } else $this->_groupMixedResults['length']+= $nbrs;
  263. $order_array = explode(',', $this->asCfg->cfg['order']);
  264. $order = $order_array[0];
  265. for($i=0;$i<$nbrs;$i++){
  266. $rs[$i]['order'] = $rs[$i][$order];
  267. $this->_groupMixedResults['results'][] = $rs[$i];
  268. }
  269. if ($this->dbgRes) $this->asUtil->dbgRecord($this->_groupMixedResults[$ig], "AjaxSearch - group mixed results");
  270. }
  271. else {
  272. if ($this->asCfg->cfg['category']) {
  273. $categ = '---';
  274. $cfunc = function_exists($categConfigFunction);
  275. $ic = 0;
  276. for ($i = 0;$i < $nbrs;$i++) {
  277. $newCateg = trim($rs[$i]['category']);
  278. if ($newCateg != $categ) {
  279. $display = UNMIXED;
  280. $cfg = NULL;
  281. if ($cfunc){
  282. $cfg = $categConfigFunction($site,$newCateg);
  283. if (isset($cfg['display'])) $display = $cfg['display'];
  284. }
  285. if ($ic>0) $ctg[$ic-1]['end'] = $i-1;
  286. $ctg[] = array('categ' => $newCateg, 'start' => $i, 'end' => 0, 'display' => $display, 'cfg' => $cfg);
  287. $ic++;
  288. }
  289. $categ = $newCateg;
  290. }
  291. if ($ic>0) $ctg[$ic-1]['end'] = $i-1;
  292. $nbc = count($ctg);
  293. $ig0 = count($this->groupResults);
  294. for ($i = 0;$i < $nbc;$i++) {
  295. $categ = $ctg[$i]['categ'];
  296. $categ = ($categ) ? $categ : UNCATEGORIZED;
  297. $display = $ctg[$i]['display'];
  298. $start = $ctg[$i]['start'];
  299. $nbrsg = $ctg[$i]['end'] - $ctg[$i]['start'] + 1;
  300. $cfg = $ctg[$i]['cfg'];
  301. if ($display == UNMIXED) {
  302. $ig = count($this->groupResults);
  303. $this->asCfg->addConfigFromCateg($site, $categ, $cfg);
  304. $this->asCfg->chooseConfig($site, $categ, $display);
  305. $ucfg = $this->asCfg->setAsCall($this->asCfg->getUserConfig());
  306. $this->groupResults[$ig] = $this->_setHeaderGroupResults($site, $categ, $display, $ucfg, $select, $nbrsg);
  307. $grpresults = array_slice($rs,$start,$nbrsg);
  308. $this->groupResults[$ig]['results'] = $this->_sortResultsByRank($this->asCtrl->searchString, $this->asCtrl->advSearch, $grpresults, $nbrsg);
  309. $this->nbGroups = $ig + 1;
  310. $this->asCfg->restoreConfig($site, DEFAULT_SUBSITE);
  311. if ($this->dbgRes) $this->asUtil->dbgRecord($this->groupResults[$ig], "AjaxSearch - group results");
  312. }
  313. else {
  314. if (!isset($this->_groupMixedResults['length'])) {
  315. $this->_groupMixedResults = $this->_setHeaderGroupResults(NO_NAME, $subsite, $display, 'N/A', 'N/A', $nbrsg);
  316. } else $this->_groupMixedResults['length']+= $nbrsg;
  317. $order_array = explode(',', $this->asCfg->cfg['order']);
  318. $order = $order_array[0];
  319. for($j=0;$j<$nbrsg;$j++) {
  320. $grpresults[$j]['order'] = $grpresults[$j][$order];
  321. $this->_groupMixedResults['results'][] = $rs[$j];
  322. }
  323. if ($this->dbgRes) $this->asUtil->dbgRecord($this->groupResults[$ig], "AjaxSearch - group results");
  324. }
  325. }
  326. }
  327. else {
  328. $ig = count($this->groupResults);
  329. $ucfg = $this->asCfg->setAsCall($this->asCfg->getUserConfig());
  330. $this->groupResults[$ig] = $this->_setHeaderGroupResults($site, $subsite, $display, $ucfg, $select, $nbrs);
  331. $row = array();
  332. if ($this->dbgRes) $this->asUtil->dbgRecord($rs, "AjaxSearch - rs");
  333. $rs = $this->_sortResultsByRank($this->asCtrl->searchString, $this->asCtrl->advSearch, $rs, $nbrs);
  334. $this->groupResults[$ig]['results'] = $rs;
  335. $this->nbGroups = $ig + 1;
  336. }
  337. unset($ctg);
  338. unset($rs);
  339. }
  340. $this->nbResults+= $nbrs;
  341. }
  342. /*
  343. * Initialize the Extract variables
  344. */
  345. function _initExtractVariables() {
  346. list($nbExtr,$lstFlds) = explode(':', $this->asCfg->cfg['extract']);
  347. $this->extractNb = $nbExtr;
  348. $this->_extractFields = explode(',', $lstFlds);
  349. $this->withExtract+= $this->extractNb;
  350. }
  351. /*
  352. * Set the header of group of results
  353. */
  354. function _setHeaderGroupResults($site, $subsite, $display, $ucfg, $select, $length) {
  355. $headerGroupResults = array();
  356. $headerGroupResults['site'] = $site;
  357. $headerGroupResults['subsite'] = $subsite;
  358. $headerGroupResults['display'] = $display;
  359. $headerGroupResults['offset'] = 0;
  360. $headerGroupResults['ucfg'] = $ucfg;
  361. $headerGroupResults['select'] = $select;
  362. $headerGroupResults['length'] = $length;
  363. $headerGroupResults['found'] = '';
  364. return $headerGroupResults;
  365. }
  366. /*
  367. * Sort results by rank value
  368. */
  369. function _sortResultsByRank($searchString, $advSearch, $results, $nbrs) {
  370. $rkFields = array();
  371. if ($this->asCfg->cfg['rank']) {
  372. $searchString = strtolower($searchString);
  373. $rkParam = explode(',', $this->asCfg->cfg['rank']);
  374. foreach ($rkParam as $rk) {
  375. $rankParam = explode(':', $rk);
  376. $name = $rankParam[0];
  377. $weight = (isset($rankParam[1]) ? $rankParam[1] : 1);
  378. $rkFields[] = array('name' => $name, 'weight' => $weight);
  379. }
  380. for ($i = 0;$i < $nbrs;$i++) {
  381. $results[$i]['rank'] = 0;
  382. foreach ($rkFields as $rf) {
  383. $results[$i]['rank']+= $this->_getRank($searchString, $advSearch, $results[$i][$rf['name']], $rf['weight']);
  384. }
  385. }
  386. if ($nbrs >1) {
  387. $i = 0;
  388. foreach ($results as $key => $row) {
  389. $category[$key] = $row['category'];
  390. $rank[$key] = $row['rank'];
  391. $ascOrder[$key] = $i++;
  392. }
  393. array_multisort($category, SORT_ASC, $rank, SORT_DESC, $ascOrder, SORT_ASC, $results);
  394. }
  395. }
  396. return $results;
  397. }
  398. /*
  399. * Get the rank value
  400. */
  401. function _getRank($searchString, $advSearch, $field, $weight) {
  402. $search = array();
  403. $rank = 0;
  404. if ($searchString && ($advSearch != NOWORDS)) {
  405. switch ($advSearch) {
  406. case EXACTPHRASE:
  407. $search[0] = $searchString;
  408. break;
  409. case ALLWORDS:
  410. case ONEWORD:
  411. $search = explode(" ", $searchString);
  412. }
  413. $field = $this->cleanText($field, $this->asCfg->cfg['stripOutput']);
  414. if (($this->asCfg->dbCharset == 'utf8') && ($this->asCfg->cfg['mbstring'])) {
  415. $field = mb_strtolower($field);
  416. foreach ($search as $srch) $rank+= mb_substr_count($field, $srch);
  417. } else {
  418. $field = strtolower($field);
  419. foreach ($search as $srch) $rank+= substr_count($field, $srch);
  420. }
  421. $rank = $rank * $weight;
  422. }
  423. return $rank;
  424. }
  425. /*
  426. * Sort noName results by order
  427. */
  428. function _sortMixedResults() {
  429. if (isset($this->_groupMixedResults['results'])) {
  430. foreach ($this->_groupMixedResults['results'] as $key => $row) {
  431. $order[$key] = $row['order'];
  432. }
  433. array_multisort($order, SORT_ASC, $this->_groupMixedResults['results']);
  434. $this->groupResults[] = $this->_groupMixedResults;
  435. $this->nbGroups++;
  436. if ($this->dbgRes) $this->asUtil->dbgRecord($this->_groupMixedResults['results'], "AjaxSearch - sorted noName results");
  437. }
  438. }
  439. /*
  440. * Check the validity of a value separated list of TVs name
  441. */
  442. function _validListTvs($listTvs, &$msgErr) {
  443. global $modx;
  444. if ($listTvs) {
  445. $tvs = explode(',', $listTvs);
  446. $tblName = $modx->getFullTableName('site_tmplvars');
  447. foreach ($tvs as $tv) {
  448. $tplRS = $modx->db->select('id', $tblName, 'name="' . $tv . '"');
  449. if (!$modx->db->getRecordCount($tplRS)) {
  450. $msgErr = "<br /><h3>AjaxSearch error: tv $tv not defined - Check your withTvs parameter !</h3><br />";
  451. return false;
  452. }
  453. }
  454. }
  455. return true;
  456. }
  457. /*
  458. * Returns extracts with highlighted searchterms
  459. */
  460. function _getExtract($text, $searchString, $advSearch, $highlightClass, &$nbExtr) {
  461. $finalExtract = '';
  462. if (($text !== '') && ($searchString !== '') && ($this->extractNb > 0) && ($advSearch !== NOWORDS)) {
  463. $extracts = array();
  464. if (($this->asCfg->dbCharset == 'utf8') && ($this->asCfg->cfg['mbstring'])) {
  465. $text = $this->_html_entity_decode($text, ENT_QUOTES, 'UTF-8');
  466. $mbStrpos = 'mb_strpos';
  467. $mbStrlen = 'mb_strlen';
  468. $mbStrtolower = 'mb_strtolower';
  469. $mbSubstr = 'mb_substr';
  470. $mbStrrpos = 'mb_strrpos';
  471. mb_internal_encoding('UTF-8');
  472. } else {
  473. $text = html_entity_decode($text, ENT_QUOTES);
  474. $mbStrpos = 'strpos';
  475. $mbStrlen = 'strlen';
  476. $mbStrtolower = 'strtolower';
  477. $mbSubstr = 'substr';
  478. $mbStrrpos = 'strrpos';
  479. }
  480. $rank = 0;
  481. // $lookAhead = '(?![^<]+>)';
  482. $pcreModifier = $this->asCfg->pcreModifier;
  483. $textLength = $mbStrlen($text);
  484. $extractLength = $this->asCfg->cfg['extractLength'];
  485. $extractLength2 = $extractLength / 2;
  486. $searchList = $this->asCtrl->getSearchWords($searchString, $advSearch);
  487. foreach ($searchList as $searchTerm) {
  488. $rank++;
  489. $wordLength = $mbStrlen($searchTerm);
  490. $wordLength2 = $wordLength / 2;
  491. // $pattern = '/' . preg_quote($searchTerm, '/') . $lookAhead . '/' . $pcreModifier;
  492. $pattern = '/' . preg_quote($searchTerm, '/') . '/' . $pcreModifier;
  493. $matches = array();
  494. $nbr = preg_match_all($pattern, $text, $matches, PREG_OFFSET_CAPTURE);
  495. for($i=0;$i<$nbr && $i<$this->extractNb;$i++) {
  496. $wordLeft = $mbStrlen(substr($text,0,$matches[0][$i][1]));
  497. $wordRight = $wordLeft + $wordLength - 1;
  498. $left = intval($wordLeft - $extractLength2 + $wordLength2);
  499. $right = $left + $extractLength - 1;
  500. if ($left < 0) $left = 0;
  501. if ($right > $textLength) $right = $textLength;
  502. $extracts[] = array('word' => $searchTerm,
  503. 'wordLeft' => $wordLeft,
  504. 'wordRight' => $wordRight,
  505. 'rank' => $rank,
  506. 'left' => $left,
  507. 'right' => $right,
  508. 'etcLeft' => $this->asCfg->cfg['extractEllips'],
  509. 'etcRight' => $this->asCfg->cfg['extractEllips']
  510. );
  511. }
  512. }
  513. $nbExtr = count($extracts);
  514. if ($nbExtr > 1) {
  515. for ($i = 0;$i < $nbExtr;$i++) {
  516. $lft[$i] = $extracts[$i]['left'];
  517. $rght[$i] = $extracts[$i]['right'];
  518. }
  519. array_multisort($lft, SORT_ASC, $rght, SORT_ASC, $extracts);
  520. for ($i = 0;$i < $nbExtr;$i++) {
  521. $begin = $mbSubstr($text, 0, $extracts[$i]['left']);
  522. if ($begin != '') $extracts[$i]['left'] = (int)$mbStrrpos($begin, ' ');
  523. $end = $mbSubstr($text, $extracts[$i]['right'] + 1, $textLength - $extracts[$i]['right']);
  524. if ($end != '') $dr = (int)$mbStrpos($end, ' ');
  525. if (is_int($dr)) $extracts[$i]['right']+= $dr + 1;
  526. }
  527. if ($extracts[0]['left'] == 0) $extracts[0]['etcLeft'] = '';
  528. for ($i = 1;$i < $nbExtr;$i++) {
  529. if ($extracts[$i]['left'] < $extracts[$i - 1]['wordRight']) {
  530. $extracts[$i - 1]['right'] = $extracts[$i - 1]['wordRight'];
  531. $extracts[$i]['left'] = $extracts[$i - 1]['right'] + 1;
  532. $extracts[$i - 1]['etcRight'] = $extracts[$i]['etcLeft'] = '';
  533. } else if ($extracts[$i]['left'] < $extracts[$i - 1]['right']) {
  534. $extracts[$i - 1]['right'] = $extracts[$i]['left'];
  535. $extracts[$i - 1]['etcRight'] = $extracts[$i]['etcLeft'] = '';
  536. }
  537. }
  538. }
  539. for ($i = 0;$i < $nbExtr;$i++) {
  540. $separation = ($extracts[$i]['etcRight'] != '') ? $this->asCfg->cfg['extractSeparator'] : '';
  541. $extract = $mbSubstr($text, $extracts[$i]['left'], $extracts[$i]['right'] - $extracts[$i]['left'] + 1);
  542. if ($this->asCfg->cfg['highlightResult']) {
  543. $rank = $extracts[$i]['rank'];
  544. $searchTerm = $searchList[$rank - 1];
  545. $pattern = '/' . preg_quote($searchTerm, '/') . '/' . $pcreModifier;
  546. $subject = '<span class="' . $highlightClass . ' ' . $highlightClass . $rank . '">\0</span>';
  547. $extract = preg_replace($pattern, $subject, $extract);
  548. }
  549. $finalExtract.= $extracts[$i]['etcLeft'] . $extract . $extracts[$i]['etcRight'] . $separation;
  550. }
  551. $finalExtract = $mbSubstr($finalExtract, 0, $mbStrlen($finalExtract) - $mbStrlen($this->asCfg->cfg['extractSeparator']));
  552. }
  553. else if ((($text !== '') && ($searchString !== '') && ($this->extractNb > 0) && ($advSearch == NOWORDS)) ||
  554. (($text !== '') && ($searchString == '') && ($this->extractNb > 0))) {
  555. if (($this->asCfg->dbCharset == 'utf8') && ($this->asCfg->cfg['mbstring'])) {
  556. $mbSubstr = 'mb_substr';
  557. $mbStrrpos = 'mb_strrpos';
  558. mb_internal_encoding('UTF-8');
  559. } else {
  560. $mbSubstr = 'substr';
  561. $mbStrrpos = 'strrpos';
  562. }
  563. $introLength = $this->asCfg->cfg['extractLength'];
  564. $intro = $mbSubstr($text,0,$introLength);
  565. $right = (int) $mbStrrpos($intro, ' ');
  566. $intro = $mbSubstr($intro,0,$right);
  567. if ($intro) $intro .= ' ' . $this->asCfg->cfg['extractEllips'];
  568. $finalExtract = $intro;
  569. }
  570. return $finalExtract;
  571. }
  572. /*
  573. * Get the extract result from each row
  574. *
  575. * @access public
  576. * @param row $row mysql row
  577. * @return string extract
  578. */
  579. function getExtractRow($row) {
  580. $text = '';
  581. $nbExtr = 0;
  582. if ($this->extractNb) {
  583. foreach ($this->_extractFields as $f) if ($row[$f]) $text.= $row[$f] . ' ';
  584. $text = $this->cleanText($text, $this->asCfg->cfg['stripOutput']);
  585. $highlightClass = $this->asOutput->getHClass();
  586. $text = $this->_getExtract($text, $this->asCtrl->searchString, $this->asCtrl->advSearch, $highlightClass, $nbExtr);
  587. }
  588. return $text;
  589. }
  590. /*
  591. * Strip function to clean outputted results
  592. */
  593. function cleanText($text, $stripOutput) {
  594. global $modx;
  595. if (($stripOutput) && function_exists($stripOutput)) $text = $stripOutput($text);
  596. else $text = $this->defaultStripOutput($text);
  597. return $text;
  598. }
  599. /*
  600. * Return the sign and the list of Ids used for the search (parents & documents)
  601. */
  602. function _doBeforeSearchFilter() {
  603. global $modx;
  604. $beforeFilter = array();
  605. list($fsign,$listIds) = explode(':',$this->_pardoc);
  606. if (($fsign != 'in') && ($fsign != 'not in')) {
  607. $listIds = $fsign;
  608. $fsign = 'in';
  609. }
  610. $beforeFilter['oper'] = ($fsign == 'in') ? 'in' : 'not in';
  611. if ($listIds != '') $listIds = $this->_cleanIds($listIds);
  612. if (strlen($listIds)) {
  613. switch ($this->_idType) {
  614. case "parents":
  615. $arrayIds = explode(",", $listIds);
  616. $listIds = implode(',', $this->_getChildIds($arrayIds, $this->_depth));
  617. break;
  618. case "documents":
  619. break;
  620. }
  621. }
  622. $beforeFilter['listIds'] = $listIds;
  623. return $beforeFilter;
  624. }
  625. /*
  626. * Filter the search results
  627. */
  628. function _doFilter($results, $searchString, $advSearch) {
  629. $globalDelimiter = '|';
  630. $localDelimiter = ',';
  631. $results = $this->_doFilterTags($results, $searchString, $advSearch);
  632. $filter = $this->asCfg->cfg['filter'];
  633. if ($filter) {
  634. $searchString_array = array();
  635. if ($advSearch == EXACTPHRASE) $searchString_array[] = $searchString;
  636. else $searchString_array = explode(' ', $searchString);
  637. $nbs = count($searchString_array);
  638. $filter_array = explode('|', $filter);
  639. $nbf = count($filter_array);
  640. for ($i = 0;$i < $nbf;$i++) {
  641. if (preg_match('/#/', $filter_array[$i])) {
  642. $terms_array = explode(',', $filter_array[$i]);
  643. if ($searchString == EXACTPHRASE) $filter_array[$i] = preg_replace('/#/i', $searchString, $filter_array[$i]);
  644. else {
  645. $filter_array[$i] = preg_replace('/#/i', $searchString_array[0], $filter_array[$i]);
  646. for ($j = 1;$j < $nbs;$j++) {
  647. $filter_array[] = $terms_array[0] . ',' . $searchString_array[$j] . ',' . $terms_array[2];
  648. }
  649. }
  650. }
  651. }
  652. $filter = implode('|', $filter_array);
  653. $parsedFilters = array();
  654. $filters = explode($globalDelimiter, $filter);
  655. if ($filter && count($filters) > 0) {
  656. foreach ($filters AS $filter) {
  657. if (!empty($filter)) {
  658. $filterArray = explode($localDelimiter, $filter);
  659. $this->_array_key = $filterArray[0];
  660. if (substr($filterArray[1], 0, 5) != "@EVAL") {
  661. $this->_filterValue = $filterArray[1];
  662. } else {
  663. $this->_filterValue = eval(substr($filterArray[1], 5));
  664. }
  665. $this->_filtertype = (isset($filterArray[2])) ? $filterArray[2] : 1;
  666. $results = array_filter($results, array($this, "_basicFilter"));
  667. }
  668. }
  669. }
  670. $results = array_values($results);
  671. }
  672. return $results;
  673. }
  674. /*
  675. * Do basic comparison filtering
  676. */
  677. function _basicFilter($value) {
  678. $unset = 1;
  679. switch ($this->_filtertype) {
  680. case "!=":
  681. case 1:
  682. if (!isset($value[$this->_array_key]) || $value[$this->_array_key] != $this->_filterValue) $unset = 0;
  683. break;
  684. case "==":
  685. case 2:
  686. if ($value[$this->_array_key] == $this->_filterValue) $unset = 0;
  687. break;
  688. case "<":
  689. case 3:
  690. if ($value[$this->_array_key] < $this->_filterValue) $unset = 0;
  691. break;
  692. case ">":
  693. case 4:
  694. if ($value[$this->_array_key] > $this->_filterValue) $unset = 0;
  695. break;
  696. case "<=":
  697. case 5:
  698. if (!($value[$this->_array_key] < $this->_filterValue)) $unset = 0;
  699. break;
  700. case ">=":
  701. case 6:
  702. if (!($value[$this->_array_key] > $this->_filterValue)) $unset = 0;
  703. break;
  704. case "not like":
  705. case 7: // does not contain the text of the criterion (like)
  706. if (strpos($value[$this->_array_key], $this->_filterValue) === FALSE) $unset = 0;
  707. break;
  708. case "like":
  709. case 8: // does contain the text of the criterion (not like)
  710. if (strpos($value[$this->_array_key], $this->_filterValue) !== FALSE) $unset = 0;
  711. break;
  712. case 9: // case insenstive version of #7 - exclude records that do not contain the text of the criterion
  713. if (strpos(strtolower($value[$this->_array_key]), strtolower($this->_filterValue)) === FALSE) $unset = 0;
  714. break;
  715. case 10: // case insenstive version of #8 - exclude records that do contain the text of the criterion
  716. if (strpos(strtolower($value[$this->_array_key]), strtolower($this->_filterValue)) !== FALSE) $unset = 0;
  717. break;
  718. case "in":
  719. case 11: // in list
  720. $filter_list = explode(':',$this->_filterValue);
  721. if (in_array($value[$this->_array_key] , $filter_list)) $unset = 0;
  722. break;
  723. case "not in":
  724. case 12: // not in list
  725. $filter_list = explode(':',$this->_filterValue);
  726. if (!in_array($value[$this->_array_key] , $filter_list)) $unset = 0;
  727. break;
  728. case "custom":
  729. case 13: // custom
  730. $custom_list = explode(':',$this->_filterValue);
  731. $custom = array_shift($custom_list);
  732. if (function_exists($custom)) {
  733. if ($custom($value[$this->_array_key], $custom_list)) $unset = 0;
  734. }
  735. break;
  736. }
  737. return $unset;
  738. }
  739. /*
  740. * Get the Ids ready to be processed
  741. */
  742. function _getChildIds($Ids, $depth) {
  743. global $modx;
  744. $depth = intval($depth);
  745. $kids = array();
  746. $docIds = array();
  747. if ($depth == 0 && $Ids[0] == 0 && count($Ids) == 1) {
  748. foreach ($modx->documentMap as $null => $document) {
  749. foreach ($document as $parent => $id) {
  750. $kids[] = $id;
  751. }
  752. }
  753. return $kids;
  754. } else if ($depth == 0) {
  755. $depth = 10000;
  756. }
  757. foreach ($modx->documentMap as $null => $document) {
  758. foreach ($document as $parent => $id) {
  759. $kids[$parent][] = $id;
  760. }
  761. }
  762. foreach ($Ids AS $seed) {
  763. if (!empty($kids[intval($seed) ])) {
  764. $docIds = array_merge($docIds, $kids[intval($seed) ]);
  765. unset($kids[intval($seed) ]);
  766. }
  767. }
  768. $depth--;
  769. while ($depth != 0) {
  770. $valid = $docIds;
  771. foreach ($docIds as $child => $id) {
  772. if (!empty($kids[intval($id) ])) {
  773. $docIds = array_merge($docIds, $kids[intval($id) ]);
  774. unset($kids[intval($id) ]);
  775. }
  776. }
  777. $depth--;
  778. if ($valid == $docIds) $depth = 0;
  779. }
  780. return array_unique($docIds);
  781. }
  782. /*
  783. * Clean Ids list of unwanted characters
  784. */
  785. function _cleanIds($Ids) {
  786. $pattern = array('`(,)+`',
  787. '`^(,)`',
  788. '`(,)$`'
  789. );
  790. $replace = array(',', '', '');
  791. $Ids = preg_replace($pattern, $replace, $Ids);
  792. return $Ids;
  793. }
  794. /*
  795. * Filter the search results when the search terms are found inside HTML or MODx tags
  796. */
  797. function _doFilterTags($results, $searchString, $advSearch) {
  798. $filteredResults = array();
  799. $nbr = count($results);
  800. for($i=0;$i<$nbr;$i++) {
  801. if ($advSearch === NOWORDS) $found = true;
  802. else {
  803. $text = implode(' ',$results[$i]);
  804. $text = $this->defaultStripOutput($text);
  805. $found = true;
  806. if ($searchString !== '') {
  807. if (($this->asCfg->dbCharset == 'utf8') && ($this->asCfg->cfg['mbstring'])) {
  808. $text = $this->_html_entity_decode($text, ENT_QUOTES, 'UTF-8');
  809. $mbStrpos = 'mb_stripos';
  810. mb_internal_encoding('UTF-8');
  811. }
  812. else {
  813. $text = html_entity_decode($text, ENT_QUOTES);
  814. $mbStrpos = 'stripos';
  815. }
  816. $searchList = $this->asCtrl->getSearchWords($searchString, $advSearch);
  817. foreach ($searchList as $searchTerm) {
  818. $found = $mbStrpos($text, $searchTerm);
  819. if ($found !== false) break;
  820. }
  821. }
  822. }
  823. if ($found) $filteredResults[] = $results[$i];
  824. }
  825. return $filteredResults;
  826. }
  827. /*
  828. * Get the array of categories found
  829. */
  830. function getResultsCateg() {
  831. $resCategName = array();
  832. $resCategNb = array();
  833. for ($i = 0;$i < $this->nbGroups;$i++) {
  834. $resCategName[$i] = "'" . $this->groupResults[$i]['subsite'] . "'";
  835. $resCategNb[$i] = $this->groupResults[$i]['length'];
  836. }
  837. return array("name" => $resCategName, "nb" => $resCategNb);
  838. }
  839. /*
  840. * Get the array of tags found
  841. */
  842. function getResultsTag() {
  843. $tags = array();
  844. $resResTag = array();
  845. $resTagName = array();
  846. $resTagNb = array();
  847. $indTag = array();
  848. for ($i = 0;$i < $this->nbGroups;$i++) {
  849. $categ = $this->groupResults[$i]['subsite'];
  850. $nbr = $this->groupResults[$i]['length'];
  851. $results = $this->groupResults[$i]['results'];
  852. for ($j = 0;$j < $nbr; $j++) {
  853. $tags_array = explode(',',$results[$j]['tags']);
  854. foreach($tags_array as $tagv) {
  855. $tv = ($tagv) ? (string) (trim($tagv)) : UNTAGGED;
  856. $tags[$tv][]= $i . ',' . $j;
  857. $resResTag[$i][$j][] = $tv;
  858. }
  859. }
  860. }
  861. $itag = 0;
  862. foreach($tags as $key => $value) {
  863. $resTagName[] = "'" . $key . "'";
  864. $resTagNb[] = count($tags[$key]);
  865. $indTag[$key] = $itag;
  866. $itag++;
  867. }
  868. for ($i = 0;$i < $this->nbGroups;$i++) {
  869. $nbr = $this->groupResults[$i]['length'];
  870. for ($j = 0;$j < $nbr; $j++) {
  871. $nbt = count($resResTag[$i][$j]);
  872. for ($t = 0;$t < $nbt; $t++) {
  873. $resResTag[$i][$j][$t] = $indTag[$resResTag[$i][$j][$t]];
  874. }
  875. }
  876. }
  877. return array("name" => $resTagName, "nb" => $resTagNb, "restag" => $resResTag);
  878. }
  879. /*
  880. * Default ouput strip function
  881. */
  882. function defaultStripOutput($text) {
  883. if ($text !== '') {
  884. // $text = $modx->parseDocumentSource($text); // parse document
  885. $text = $this->stripLineBreaking($text);
  886. $text = $this->stripTags($text);
  887. $text = $this->stripJscripts($text);
  888. $text = $this->stripHTML($text);
  889. }
  890. return $text;
  891. }
  892. /*
  893. * stripLineBreaking : replace line breaking tags with whitespace
  894. */
  895. function stripLineBreaking($text) {
  896. $text = preg_replace("'<(br[^/>]*?/|hr[^/>]*?/|/(div|h[1-6]|li|p|td))>'si", ' ', $text);
  897. return $text;
  898. }
  899. /*
  900. * stripTags : Remove MODx sensitive tags
  901. */
  902. function stripTags($text) {
  903. $modRegExArray[] = '~\[\[(.*?)\]\]~';
  904. $modRegExArray[] = '~\[!(.*?)!\]~';
  905. $modRegExArray[] = '!\[\~(.*?)\~\]!is';
  906. $modRegExArray[] = '~\[\((.*?)\)\]~';
  907. $modRegExArray[] = '~{{(.*?)}}~';
  908. $modRegExArray[] = '~\[\*(.*?)\*\]~';
  909. $modRegExArray[] = '~\[\+(.*?)\+\]~';
  910. foreach ($modRegExArray as $mReg) $text = preg_replace($mReg, '', $text);
  911. return $text;
  912. }
  913. /*
  914. * stripJscript : Remove jscript
  915. */
  916. function stripJscripts($text) {
  917. $text = preg_replace("'<script[^>]*>.*?</script>'si", "", $text);
  918. $text = preg_replace('/{.+?}/', '', $text);
  919. return $text;
  920. }
  921. /*
  922. * stripHtml : Remove HTML sensitive tags
  923. */
  924. function stripHtml($text) {
  925. return strip_tags($text);
  926. }
  927. /*
  928. * stripHtmlExceptImage : Remove HTML sensitive tags except image tag
  929. */
  930. function stripHtmlExceptImage($text) {
  931. $text = strip_tags($text, '<img>');
  932. return $text;
  933. }
  934. function getSearchContext() {
  935. // return the search context
  936. $searchContext['main'] = $this->_asRequest->scMain;
  937. $searchContext['joined'] = $this->_asRequest->scJoined;
  938. $searchContext['tvs'] = $this->_asRequest->scTvs;
  939. $searchContext['category'] = $this->_asRequest->scCategory;
  940. $searchContext['tags'] = $this->_asRequest->scTags;
  941. return $searchContext;
  942. }
  943. function getWithContent() {
  944. // return the withContent boolean value
  945. return $this->_asRequest->withContent;
  946. }
  947. function _html_entity_decode($text, $quote_style = ENT_COMPAT, $charset) {
  948. if (version_compare(PHP_VERSION, '5.0.0', '>=')) $text = html_entity_decode($text, ENT_QUOTES, $charset);
  949. else $text = $this->_html_entity_decode_php4($text);
  950. return $text;
  951. }
  952. // Author : Nicola Asuni
  953. // License : GNU LGPL (http://www.gnu.org/copyleft/lesser.html)
  954. //
  955. // Description : This is a PHP4 function that redefine the
  956. // standard html_entity_decode function to support
  957. // UTF-8 encoding.
  958. /**
  959. * Reverse function for htmlentities.
  960. * Convert entities in UTF-8.
  961. */
  962. function _html_entity_decode_php4($text_to_convert) {
  963. $htmlentities_table = array (
  964. "&Aacute;" => "".chr(195).chr(129)."",
  965. "&aacute;" => "".chr(195).chr(161)."",
  966. "&Acirc;" => "".chr(195).chr(130)."",
  967. "&acirc;" => "".chr(195).chr(162)."",
  968. "&acute;" => "".chr(194).chr(180)."",
  969. "&AElig;" => "".chr(195).chr(134)."",
  970. "&aelig;" => "".chr(195).chr(166)."",
  971. "&Agrave;" => "".chr(195).chr(128)."",
  972. "&agrave;" => "".chr(195).chr(160)."",
  973. "&alefsym;" => "".chr(226).chr(132).chr(181)."",
  974. "&Alpha;" => "".chr(206).chr(145)."",
  975. "&alpha;" => "".chr(206).chr(177)."",
  976. "&amp;" => "".chr(38)."",
  977. "&and;" => "".chr(226).chr(136).chr(167)."",
  978. "&ang;" => "".chr(226).chr(136).chr(160)."",
  979. "&Aring;" => "".chr(195).chr(133)."",
  980. "&aring;" => "".chr(195).chr(165)."",
  981. "&asymp;" => "".chr(226).chr(137).chr(136)."",
  982. "&Atilde;" => "".chr(195).chr(131)."",
  983. "&atilde;" => "".chr(195).chr(163)."",
  984. "&Auml;" => "".chr(195).chr(132)."",
  985. "&auml;" => "".chr(195).chr(164)."",
  986. "&bdquo;" => "".chr(226).chr(128).chr(158)."",
  987. "&Beta;" => "".chr(206).chr(146)."",
  988. "&beta;" => "".chr(206).chr(178)."",
  989. "&brvbar;" => "".chr(194).chr(166)."",
  990. "&bull;" => "".chr(226).chr(128).chr(162)."",
  991. "&cap;" => "".chr(226).chr(136).chr(169)."",
  992. "&Ccedil;" => "".chr(195).chr(135)."",
  993. "&ccedil;" => "".chr(195).chr(167)."",
  994. "&cedil;" => "".chr(194).chr(184)."",
  995. "&cent;" => "".chr(194).chr(162)."",
  996. "&Chi;" => "".chr(206).chr(167)."",
  997. "&chi;" => "".chr(207).chr(135)."",
  998. "&circ;" => "".chr(203).chr(134)."",
  999. "&clubs;" => "".chr(226).chr(153).chr(163)."",
  1000. "&cong;" => "".chr(226).chr(137).chr(133)."",
  1001. "&copy;" => "".chr(194).chr(169)."",
  1002. "&crarr;" => "".chr(226).chr(134).chr(181)."",
  1003. "&cup;" => "".chr(226).chr(136).chr(170)."",
  1004. "&curren;" => "".chr(194).chr(164)."",
  1005. "&dagger;" => "".chr(226).chr(128).chr(160)."",
  1006. "&Dagger;" => "".chr(226).chr(128).chr(161)."",
  1007. "&darr;" => "".chr(226).chr(134).chr(147)."",
  1008. "&dArr;" => "".chr(226).chr(135).chr(147)."",
  1009. "&deg;" => "".chr(194).chr(176)."",
  1010. "&Delta;" => "".chr(206).chr(148)."",
  1011. "&delta;" => "".chr(206).chr(180)."",
  1012. "&diams;" => "".chr(226).chr(153).chr(166)."",
  1013. "&divide;" => "".chr(195).chr(183)."",
  1014. "&Eacute;" => "".chr(195).chr(137)."",
  1015. "&eacute;" => "".chr(195).chr(169)."",
  1016. "&Ecirc;" => "".chr(195).chr(138)."",
  1017. "&ecirc;" => "".chr(195).chr(170)."",
  1018. "&Egrave;" => "".chr(195).chr(136)."",
  1019. "&egrave;" => "".chr(195).chr(168)."",
  1020. "&empty;" => "".chr(226).chr(136).chr(133)."",
  1021. "&emsp;" => "".chr(226).chr(128).chr(131)."",
  1022. "&ensp;" => "".chr(226).chr(128).chr(130)."",
  1023. "&Epsilon;" => "".chr(206).chr(149)."",
  1024. "&epsilon;" => "".chr(206).chr(181)."",
  1025. "&equiv;" => "".chr(226).chr(137).chr(161)."",
  1026. "&Eta;" => "".chr(206).chr(151)."",
  1027. "&eta;" => "".chr(206).chr(183)."",
  1028. "&ETH;" => "".chr(195).chr(144)."",
  1029. "&eth;" => "".chr(195).chr(176)."",
  1030. "&Euml;" => "".chr(195).chr(139)."",
  1031. "&euml;" => "".chr(195).chr(171)."",
  1032. "&euro;" => "".chr(226).chr(130).chr(172)."",
  1033. "&exist;" => "".chr(226).chr(136).chr(131)."",
  1034. "&fnof;" => "".chr(198).chr(146)."",
  1035. "&forall;" => "".chr(226).chr(136).chr(128)."",
  1036. "&frac12;" => "".chr(194).chr(189)."",
  1037. "&frac14;" => "".chr(194).chr(188)."",
  1038. "&frac34;" => "".chr(194).chr(190)."",
  1039. "&frasl;" => "".chr(226).chr(129).chr(132)."",
  1040. "&Gamma;" => "".chr(206).chr(147)."",
  1041. "&gamma;" => "".chr(206).chr(179)."",
  1042. "&ge;" => "".chr(226).chr(137).chr(165)."",
  1043. "&harr;" => "".chr(226).chr(134).chr(148)."",
  1044. "&hArr;" => "".chr(226).chr(135).chr(148)."",
  1045. "&hearts;" => "".chr(226).chr(153).chr(165)."",
  1046. "&hellip;" => "".chr(226).chr(128).chr(166)."",
  1047. "&Iacute;" => "".chr(195).chr(141)."",
  1048. "&iacute;" => "".chr(195).chr(173)."",
  1049. "&Icirc;" => "".chr(195).chr(142)."",
  1050. "&icirc;" => "".chr(195).chr(174)."",
  1051. "&iexcl;" => "".chr(194).chr(161)."",
  1052. "&Igrave;" => "".chr(195).chr(140)."",
  1053. "&igrave;" => "".chr(195).chr(172)."",
  1054. "&image;" => "".chr(226).chr(132).chr(145)."",
  1055. "&infin;" => "".chr(226).chr(136).chr(158)."",
  1056. "&int;" => "".chr(226).chr(136).chr(171)."",
  1057. "&Iota;" => "".chr(206).chr(153)."",
  1058. "&iota;" => "".chr(206).chr(185)."",
  1059. "&iquest;" => "".chr(194).chr(191)."",
  1060. "&isin;" => "".chr(226).chr(136).chr(136)."",
  1061. "&Iuml;" => "".chr(195).chr(143)."",
  1062. "&iuml;" => "".chr(195).chr(175)."",
  1063. "&Kappa;" => "".chr(206).chr(154)."",
  1064. "&kappa;" => "".chr(206).chr(186)."",
  1065. "&Lambda;" => "".chr(206).chr(155)."",
  1066. "&lambda;" => "".chr(206).chr(187)."",
  1067. "&lang;" => "".chr(226).chr(140).chr(169)."",
  1068. "&laquo;" => "".chr(194).chr(171)."",
  1069. "&larr;" => "".chr(226).chr(134).chr(144)."",
  1070. "&lArr;" => "".chr(226).chr(135).chr(144)."",
  1071. "&lceil;" => "".chr(226).chr(140).chr(136)."",
  1072. "&ldquo;" => "".chr(226).chr(128).chr(156)."",
  1073. "&le;" => "".chr(226).chr(137).chr(164)."",
  1074. "&lfloor;" => "".chr(226).chr(140).chr(138)."",
  1075. "&lowast;" => "".chr(226).chr(136).chr(151)."",
  1076. "&loz;" => "".chr(226).chr(151).chr(138)."",
  1077. "&lrm;" => "".chr(226).chr(128).chr(142)."",

Large files files are truncated, but you can click here to view the full file