XapianIndexer.class.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * @package chamilo.include.search
  5. */
  6. require_once 'xapian.php';
  7. /**
  8. * Abstract helper class.
  9. *
  10. * @package chamilo.include.search
  11. */
  12. abstract class XapianIndexer
  13. {
  14. /* XapianTermGenerator */
  15. public $indexer;
  16. /* XapianStem */
  17. public $stemmer;
  18. /* XapianWritableDatabase */
  19. protected $db;
  20. /* IndexableChunk[] */
  21. protected $chunks;
  22. /**
  23. * Class contructor.
  24. */
  25. public function __construct()
  26. {
  27. $this->db = null;
  28. $this->stemmer = null;
  29. }
  30. /**
  31. * Class destructor.
  32. */
  33. public function __destruct()
  34. {
  35. unset($this->db);
  36. unset($this->stemmer);
  37. }
  38. /**
  39. * Generates a list of languages Xapian manages.
  40. *
  41. * This method enables the definition of more matches between
  42. * Chamilo languages and Xapian languages (through hardcoding)
  43. *
  44. * @return array Array of languages codes -> Xapian languages
  45. */
  46. final public function xapian_languages()
  47. {
  48. /* http://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html */
  49. return [
  50. 'none' => 'none', //don't stem terms
  51. 'da' => 'danish',
  52. 'nl' => 'dutch',
  53. /* Martin Porter's 2002 revision of his stemmer */
  54. 'en' => 'english',
  55. /* Lovin's stemmer */
  56. 'lovins' => 'english_lovins',
  57. /* Porter's stemmer as described in his 1980 paper */
  58. 'porter' => 'english_porter',
  59. 'fi' => 'finnish',
  60. 'fr' => 'french',
  61. 'de' => 'german',
  62. 'it' => 'italian',
  63. 'no' => 'norwegian',
  64. 'pt' => 'portuguese',
  65. 'ru' => 'russian',
  66. 'es' => 'spanish',
  67. 'sv' => 'swedish',
  68. ];
  69. }
  70. /**
  71. * Connect to the database, and create it if it doesn't exist.
  72. */
  73. public function connectDb($path = null, $dbMode = null, $lang = 'english')
  74. {
  75. if ($this->db != null) {
  76. return $this->db;
  77. }
  78. if ($dbMode == null) {
  79. $dbMode = Xapian::DB_CREATE_OR_OPEN;
  80. }
  81. if ($path == null) {
  82. $path = api_get_path(SYS_UPLOAD_PATH).'plugins/xapian/searchdb/';
  83. }
  84. try {
  85. $this->db = new XapianWritableDatabase($path, $dbMode);
  86. $this->indexer = new XapianTermGenerator();
  87. if (!in_array($lang, $this->xapian_languages())) {
  88. $lang = 'english';
  89. }
  90. $this->stemmer = new XapianStem($lang);
  91. $this->indexer->set_stemmer($this->stemmer);
  92. return $this->db;
  93. } catch (Exception $e) {
  94. echo Display::return_message($e->getMessage(), 'error');
  95. return 1;
  96. }
  97. }
  98. /**
  99. * Simple getter for the db attribute.
  100. *
  101. * @return object The db attribute
  102. */
  103. public function getDb()
  104. {
  105. return $this->db;
  106. }
  107. /**
  108. * Add this chunk to the chunk array attribute.
  109. *
  110. * @param string Chunk of text
  111. */
  112. public function addChunk($chunk)
  113. {
  114. $this->chunks[] = $chunk;
  115. }
  116. /**
  117. * Actually index the current data.
  118. *
  119. * @return int New Xapian document ID or null upon failure
  120. */
  121. public function index()
  122. {
  123. try {
  124. if (!empty($this->chunks)) {
  125. foreach ($this->chunks as $chunk) {
  126. $doc = new XapianDocument();
  127. $this->indexer->set_document($doc);
  128. if (!empty($chunk->terms)) {
  129. foreach ($chunk->terms as $term) {
  130. /* FIXME: think of getting weight */
  131. $doc->add_term($term['flag'].$term['name'], 1);
  132. }
  133. }
  134. // free-form index all data array (title, content, etc)
  135. if (!empty($chunk->data)) {
  136. foreach ($chunk->data as $key => $value) {
  137. $this->indexer->index_text($value, 1);
  138. }
  139. }
  140. $doc->set_data($chunk->xapian_data, 1);
  141. $did = $this->db->add_document($doc);
  142. //write to disk
  143. $this->db->flush();
  144. return $did;
  145. }
  146. }
  147. } catch (Exception $e) {
  148. echo Display::return_message($e->getMessage(), 'error');
  149. exit(1);
  150. }
  151. }
  152. /**
  153. * Get a specific document from xapian db.
  154. *
  155. * @param int did Xapian::docid
  156. *
  157. * @return mixed XapianDocument, or false on error
  158. */
  159. public function get_document($did)
  160. {
  161. if ($this->db == null) {
  162. $this->connectDb();
  163. }
  164. try {
  165. $docid = $this->db->get_document($did);
  166. } catch (Exception $e) {
  167. //echo Display::return_message($e->getMessage(), 'error');
  168. return false;
  169. }
  170. return $docid;
  171. }
  172. /**
  173. * Get document data on a xapian document.
  174. *
  175. * @param XapianDocument $doc xapian document to push into the db
  176. *
  177. * @return mixed xapian document data or FALSE if error
  178. */
  179. public function get_document_data($doc)
  180. {
  181. if ($this->db == null) {
  182. $this->connectDb();
  183. }
  184. try {
  185. if (!is_a($doc, 'XapianDocument')) {
  186. return false;
  187. }
  188. $doc_data = $doc->get_data();
  189. return $doc_data;
  190. } catch (Exception $e) {
  191. //echo Display::return_message($e->getMessage(), 'error');
  192. return false;
  193. }
  194. }
  195. /**
  196. * Replace all terms of a document in xapian db.
  197. *
  198. * @param int $did Xapian::docid
  199. * @param array $terms New terms of the document
  200. * @param string $prefix Prefix used to categorize the doc
  201. * (usually 'T' for title, 'A' for author)
  202. *
  203. * @return bool false on error
  204. */
  205. public function update_terms($did, $terms, $prefix)
  206. {
  207. $doc = $this->get_document($did);
  208. if ($doc === false) {
  209. return false;
  210. }
  211. $doc->clear_terms();
  212. foreach ($terms as $term) {
  213. //add directly
  214. $doc->add_term($prefix.$term, 1);
  215. }
  216. $this->db->replace_document($did, $doc);
  217. $this->db->flush();
  218. return true;
  219. }
  220. /**
  221. * Remove a document from xapian db.
  222. *
  223. * @param int did Xapian::docid
  224. */
  225. public function remove_document($did)
  226. {
  227. if ($this->db == null) {
  228. $this->connectDb();
  229. }
  230. $did = (int) $did;
  231. if ($did > 0) {
  232. $doc = $this->get_document($did);
  233. if ($doc !== false) {
  234. $this->db->delete_document($did);
  235. $this->db->flush();
  236. }
  237. }
  238. }
  239. /**
  240. * Adds a term to the document specified.
  241. *
  242. * @param string $term The term to add
  243. * @param XapianDocument $doc The xapian document where to add the term
  244. *
  245. * @return mixed XapianDocument, or false on error
  246. */
  247. public function add_term_to_doc($term, $doc)
  248. {
  249. if (!is_a($doc, 'XapianDocument')) {
  250. return false;
  251. }
  252. try {
  253. $doc->add_term($term);
  254. } catch (Exception $e) {
  255. echo Display::return_message($e->getMessage(), 'error');
  256. return 1;
  257. }
  258. }
  259. /**
  260. * Remove a term from the document specified.
  261. *
  262. * @param string $term The term to add
  263. * @param XapianDocument $doc The xapian document where to add the term
  264. *
  265. * @return mixed XapianDocument, or false on error
  266. */
  267. public function remove_term_from_doc($term, $doc)
  268. {
  269. if (!is_a($doc, 'XapianDocument')) {
  270. return false;
  271. }
  272. try {
  273. $doc->remove_term($term);
  274. } catch (Exception $e) {
  275. echo Display::return_message($e->getMessage(), 'error');
  276. return 1;
  277. }
  278. }
  279. /**
  280. * Replace a document in the actual db.
  281. *
  282. * @param XapianDocument $doc xapian document to push into the db
  283. * @param int $did xapian document id of the document to replace
  284. *
  285. * @return mixed
  286. */
  287. public function replace_document($doc, $did)
  288. {
  289. if (!is_a($doc, 'XapianDocument')) {
  290. return false;
  291. }
  292. if ($this->db == null) {
  293. $this->connectDb();
  294. }
  295. try {
  296. $this->getDb()->replace_document((int) $did, $doc);
  297. $this->getDb()->flush();
  298. } catch (Exception $e) {
  299. echo Display::return_message($e->getMessage(), 'error');
  300. return 1;
  301. }
  302. }
  303. }