Actualización

This commit is contained in:
Xes
2025-04-10 12:49:05 +02:00
parent 4aff98e77b
commit 1cdd00920f
9151 changed files with 1800913 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
<?php
/* For licensing terms, see /license.txt */
/**
* @package chamilo.include.search
*/
require_once __DIR__.'/../../global.inc.php';
/**
* Class wrapper.
*
* @package chamilo.include.search
*/
class ChamiloIndexer extends XapianIndexer
{
/**
* Set terms on search_did given.
*
* @param string $terms_string Comma-separated list of terms from input form
* @param string $prefix Search engine prefix
* @param string $course_code Course code
* @param string $tool_id Tool id from mainapi.lib.php
* @param int $ref_id_high_level Main id of the entity to index (Ex. lp_id)
* @param int $ref_id_second_level Secondary id of the entity to index (Ex. lp_item)
* @param int $search_did Search engine document id from search_engine_ref table
*
* @return bool False on error or nothing to do, true otherwise
*/
public function set_terms(
$terms_string,
$prefix,
$course_code,
$tool_id,
$ref_id_high_level,
$ref_id_second_level,
$search_did
) {
$terms_string = trim($terms_string);
$terms = explode(',', $terms_string);
array_walk($terms, 'trim_value');
$stored_terms = $this->get_terms_on_db($prefix, $course_code, $tool_id, $ref_id_high_level);
// don't do anything if no change, verify only at DB, not the search engine
if ((count(array_diff($terms, $stored_terms)) == 0) &&
(count(array_diff($stored_terms, $terms)) == 0)
) {
return false;
}
require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
// compare terms
$doc = $this->get_document($search_did);
$xapian_terms = xapian_get_doc_terms($doc, $prefix);
$xterms = [];
foreach ($xapian_terms as $xapian_term) {
$xterms[] = substr($xapian_term['name'], 1);
}
$dterms = $terms;
$missing_terms = array_diff($dterms, $xterms);
$deprecated_terms = array_diff($xterms, $dterms);
// save it to search engine
foreach ($missing_terms as $term) {
$this->add_term_to_doc($prefix.$term, $doc);
}
foreach ($deprecated_terms as $term) {
$this->remove_term_from_doc($prefix.$term, $doc);
}
// don't do anything if no change
if ((count($missing_terms) > 0) || (count($deprecated_terms) > 0)) {
$this->replace_document($doc, (int) $search_did);
}
return true;
}
/**
* Get the terms stored at database.
*
* @return array Array of terms
*/
public function get_terms_on_db($prefix, $course_code, $tool_id, $ref_id)
{
require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
$terms = get_specific_field_values_list_by_prefix(
$prefix,
$course_code,
$tool_id,
$ref_id
);
$prefix_terms = [];
foreach ($terms as $term) {
$prefix_terms[] = $term['value'];
}
return $prefix_terms;
}
}

View File

@@ -0,0 +1,82 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Script defining generic functions against a search engine api. Just only if one day the search engine changes.
*
* @package chamilo.include.search
*/
require 'xapian/XapianQuery.php';
/**
* Wrapper for queries.
*
* @param string $query_string The search string
* @param int $offset Offset to the first item to retrieve. Optional
* @param int length Number of items to retrieve. Optional
* @param array extra Extra queries to join with. Optional
*
* @return array
*/
function chamilo_query_query($query_string, $offset = 0, $length = 10, $extra = null)
{
list($count, $results) = xapian_query($query_string, null, $offset, $length, $extra);
return chamilo_preprocess_results($results);
}
function chamilo_query_simple_query($query_string, $offset = 0, $length = 10, $extra = null)
{
return xapian_query($query_string, null, $offset, $length, $extra);
}
/**
* Wrapper for getting boolean queries.
*
* @param string $term The term string
*/
function chamilo_get_boolean_query($term)
{
return xapian_get_boolean_query($term);
}
/**
* Preprocess all results depending on the toolid.
*/
function chamilo_preprocess_results($results)
{
// group by toolid
$results_by_tool = [];
if (count($results) > 0) {
foreach ($results as $key => $row) {
$results_by_tool[$row['toolid']][] = $row;
}
$processed_results = [];
foreach ($results_by_tool as $toolid => $rows) {
$tool_processor_class = $toolid.'_processor';
$tool_processor_path = api_get_path(LIBRARY_PATH).'search/tool_processors/'.$tool_processor_class.'.class.php';
if (file_exists($tool_processor_path)) {
require_once $tool_processor_path;
$tool_processor = new $tool_processor_class($rows);
$processed_results = array_merge($tool_processor->process(), $processed_results);
}
}
return [count($processed_results), $processed_results];
}
}
/**
* Wrapper for join xapian queries.
*
* @param XapianQuery|array $query1
* @param XapianQuery|array $query2
* @param string $op
*
* @return XapianQuery query joined
*/
function chamilo_join_queries($query1, $query2 = null, $op = 'or')
{
return xapian_join_queries($query1, $query2, $op);
}

View File

@@ -0,0 +1,122 @@
<?php
/* For licensing terms, see /license.txt */
/**
* @package chamilo.include.search
*/
// some constants to avoid serialize string keys on serialized data array
define('SE_COURSE_ID', 0);
define('SE_TOOL_ID', 1);
define('SE_DATA', 2);
define('SE_USER', 3);
// in some cases we need top differenciate xapian documents of the same tool
define('SE_DOCTYPE_EXERCISE_EXERCISE', 0);
define('SE_DOCTYPE_EXERCISE_QUESTION', 1);
// xapian prefixes
define('XAPIAN_PREFIX_COURSEID', 'C');
define('XAPIAN_PREFIX_TOOLID', 'O');
/**
* Class.
*
* @package chamilo.include.search
*/
abstract class _IndexableChunk
{
/* struct (array)
* {
* string title; <- nombre de archivo/elemento
* string content; <- texto a indexar
* string ids; <- los flags a guardar "cidReq:lp_id:path"
* }
*/
public $data;
/**
* array (
* 'SE_COURSE_ID' => string <- course id from course table on main db
* 'SE_TOOL_ID' => string <- tool id from mainapi lib constants
* 'SE_DATA' => mixed <- extra information, depends on SE_TOOL_ID
* 'SE_USER' => id <- user id from user table in main db
* ).
*/
public $xapian_data;
/**
* array(
* name => string
* flag => char
* ).
*/
public $terms;
/**
* Class constructor. Just generates an empty 'data' array attribute.
*/
public function __construct()
{
$this->data = [];
}
/**
* Class desctructor. Unsets attributes.
*/
public function __destruct()
{
unset($this->data);
unset($this->terms);
}
/**
* Add a value to the indexed item.
*
* @param string Key
* @param string Value
*/
public function addValue($key, $value)
{
$this->data[$key] = $value;
}
/**
* Add a term (like xapian definition).
*
* @param string Term
* @param string Flag (one character)
*/
public function addTerm($term, $flag)
{
global $charset;
if (strlen($flag) == 1) {
$this->terms[] = ['name' => api_convert_encoding(stripslashes($term), 'UTF-8', $charset), 'flag' => $flag];
}
}
}
/**
* Extension of the _IndexableChunk class to make IndexableChunk extensible.
*
* @package chamilo.include.search
*/
class IndexableChunk extends _IndexableChunk
{
/**
* Let add course id term.
*/
public function addCourseId($course_id)
{
$this->addTerm($course_id, XAPIAN_PREFIX_COURSEID);
}
/**
* Let add tool id term.
*/
public function addToolId($tool_id)
{
$this->addTerm($tool_id, XAPIAN_PREFIX_TOOLID);
}
}

View File

@@ -0,0 +1,99 @@
<?php
/* For licensing terms, see /license.txt */
/**
* This script retrieves a list of terms that have xapian documents
* related with the term passed.
*
* @package chamilo.include.search
*/
$terms_list = [];
// verify parameter and return a right value to avoid problems parsing it
if (empty($_GET['term']) || empty($_GET['prefix']) || !in_array($_GET['operator'], ['or', 'and'])) {
echo json_encode($terms_list);
return;
}
require_once __DIR__.'../../../global.inc.php';
require_once api_get_path(LIBRARY_PATH).'search/ChamiloQuery.php';
/**
* Search with filter and build base array avoiding repeated terms.
*
* @param array $filter XapianQuery array
* @param array $specific_fields
*
* @return array $sf_terms
*/
function get_usual_sf_terms($filter, $specific_fields)
{
$sf_terms = [];
$dkterms = chamilo_query_simple_query('', 0, 1000, $filter);
if (is_array($dkterms) && is_array($dkterms[1])) {
foreach ($specific_fields as $specific_field) {
foreach ($dkterms[1] as $obj) {
foreach ($obj['sf-'.$specific_field['code']] as $raw_term) {
if (count($raw_term['name']) > 1) {
$normal_term = substr($raw_term['name'], 1);
$sf_terms[$specific_field['code']][$normal_term] = $normal_term;
}
}
}
}
}
return $sf_terms;
}
$term = $_GET['term'];
$prefix = $_GET['prefix'];
$operator = $_GET['operator'];
$specific_fields = get_specific_field_list();
$sf_terms = [];
if (($cid = api_get_course_id()) != -1) { // with cid
// course filter
$filter[] = chamilo_get_boolean_query(XAPIAN_PREFIX_COURSEID.$cid);
// term filter
if ($term != '__all__') {
$filter[] = chamilo_get_boolean_query($prefix.$term);
// always and between term and courseid
$filter = chamilo_join_queries($filter, null, 'and');
}
$sf_terms = get_usual_sf_terms($filter, $specific_fields);
} else { // without cid
if ($term != '__all__') {
$filter[] = chamilo_get_boolean_query($prefix.$term);
$sf_terms = get_usual_sf_terms($filter, $specific_fields);
} else { // no cid and all/any terms
foreach ($specific_fields as $specific_field) {
foreach (xapian_get_all_terms(1000, $specific_field['code']) as $raw_term) {
if (count($raw_term['name']) > 1) {
$normal_term = substr($raw_term['name'], 1);
$sf_terms[$specific_field['code']][$normal_term] = $normal_term;
}
}
}
}
}
// build array to return
foreach ($sf_terms as $sf_prefix => $term_group) {
//if (count($tem_group) > 0) {
$first_term = ['__all__' => ($operator == 'or' ? '-- Any --' : '-- All -- ')];
//}
if ($sf_prefix != $prefix) {
$terms_list[] = [
'prefix' => $sf_prefix,
'terms' => array_merge($first_term, $term_group),
];
}
}
echo json_encode($terms_list);

View File

@@ -0,0 +1,6 @@
<html>
<head>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,86 @@
.tags {
display: block;
margin-top: 20px;
width: 90%;
}
.sf-select-multiple {
width: 14em;
margin: 0 1em 0 1em;
}
.sf-select-multiple-title {
font-weight: bold;
margin-left: 1em;
font-size: 130%;
}
#submit {
background-image: url('/main/img/search-lense.gif');
background-repeat: no-repeat;
background-position: 0px 3px;
padding-left:28px;
}
.lower-submit {
float:right;
margin: 0 0.9em 0 0.5em;
}
#tags-clean {
float: right;
}
.sf-select-splitter {
margin-top: 4em;
}
.search-links-box {
background-color: #ddd;
border: 1px solid #888;
padding: 1em;
-moz-border-radius: 0.8em;
}
#thesaurus-icon {
margin-bottom: -6px;
}
#operator-select {
padding-right: 0.9em;
}
.doc_table {
width: 30%;
text-align: left;
}
.doc_img {
border: 1px solid black;
padding: 1px solid white;
background: white;
}
.doc_img,
.doc_img img {
width: 120px;
}
.doc_text,
.doc_title {
padding-left: 10px;
vertical-align: top;
}
.doc_title {
font-size: large;
font-weight: bold;
height: 2em;
}
.data_table {
text-align:center;
}
.cls {
clear:both
}
#search-results-container {
width: 100%;
border: 1px solid #979797;
position: relative;
background-repeat: repeat-x
}
#mode-selector {
width: 100px;
padding: 4px;
top: 0px;
z-index: 9;
float:left;
height:15px;
}

View File

@@ -0,0 +1,42 @@
$(document).ready(function() {
/* toggle advanced view */
$('a#tags-toggle').click(function() {
$('#tags').toggle(150);
return false;
});
/* reset terms form */
$('#tags-clean').click(function() {
// clear multiple select
$('select option:selected').each(function () {
$(this).prop('selected', false);
});
return false;
});
/* ajax suggestions */
$('#query').autocomplete({
source: 'search_suggestions.php',
multiple: false,
selectFirst: false,
mustMatch: false,
autoFill: false
});
/* prefilter form */
$('#prefilter').change(function () {
var str = "";
$("#prefilter option:selected").each(function () {
str += $(this).text() + " ";
});
process_terms = function(data) {
$(".sf-select-multiple").html("");
$.each(data, function(i,item) {
$.each(item.terms, function(index, term) {
$('<option />').val(index).text(term).appendTo("#sf-" + item.prefix);
});
});
};
url = "/main/inc/lib/search/get_terms.php";
params = "?term=" + $(this).val() + "&prefix=" + $(this).attr("title") + "&operator=" + $("input[@name=operator]:checked").val();
$.getJSON(url + params, process_terms);
});
});

View File

@@ -0,0 +1,404 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Search widget. Shows the search screen contents.
*
* @package chamilo.include.search
*/
require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
/**
* Add some required CSS and JS to html's head.
*
* Note that $htmlHeadXtra should be passed by reference and not value,
* otherwise this function will have no effect and your form will be broken.
*
* @param array $htmlHeadXtra A reference to the doc $htmlHeadXtra
*/
function search_widget_prepare(&$htmlHeadXtra)
{
$htmlHeadXtra[] = '
<!-- script type="text/javascript" src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.autocomplete.js"></script -->
<script type="text/javascript" src="'.api_get_path(WEB_LIBRARY_PATH).'search/search_widget.js"></script>
<link rel="stylesheet" type="text/css" href="'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.autocomplete.css" />
<link rel="stylesheet" type="text/css" href="'.api_get_path(WEB_LIBRARY_PATH).'search/search_widget.css" />';
}
/**
* Get one term html select.
*/
function format_one_specific_field_select($prefix, $sf_term_array, $op, $extra_select_attr = 'size="7" class="sf-select-multiple"')
{
global $charset;
$multiple_select = '<select '.$extra_select_attr.' title="'.$prefix.'" id="sf-'.$prefix.'" name="sf_'.$prefix.'[]">';
$all_selected = '';
if (!empty($_REQUEST['sf_'.$prefix])) {
if (in_array('__all__', $_REQUEST['sf_'.$prefix])) {
$all_selected = 'selected="selected"';
}
}
if ($op == 'and') {
$all_selected_name = get_lang('All');
} elseif ($op == 'or') {
$all_selected_name = get_lang('Any');
}
$multiple_select .= '<option value="__all__" '.$all_selected.' >-- '.$all_selected_name.' --</option>';
foreach ($sf_term_array as $raw_term) {
$term = substr($raw_term, 1);
if (empty($term)) {
continue;
}
$html_term = htmlspecialchars($term, ENT_QUOTES, $charset);
$selected = '';
if (!empty($_REQUEST['sf_'.$prefix]) && is_array($_REQUEST['sf_'.$prefix]) && in_array($term, $_REQUEST['sf_'.$prefix])) {
$selected = 'selected="selected"';
}
$multiple_select .= '<option value="'.$html_term.'" '.$selected.'>'.$html_term.'</option>';
}
$multiple_select .= '</select>';
return $multiple_select;
}
/**
* Get terms html selects.
*/
function format_specific_fields_selects($sf_terms, $op, $prefilter_prefix = '')
{
// Process each prefix type term
$i = 0;
$max = count($sf_terms);
$multiple_selects = '';
foreach ($sf_terms as $prefix => $sf_term_array) {
if ($prefix == $prefilter_prefix) {
continue;
}
$multiple_select = '';
if ($i > 0) {
//print "+" image
$multiple_select .= '<td><img class="sf-select-splitter" src="../img/search-big-plus.gif" alt="plus-sign-decoration"/></td>';
}
//sorting the array of terms
$temp = [];
if (!empty($sf_term_array)) {
foreach ($sf_term_array as $key => $value) {
$temp[trim(stripslashes($value['name']))] = $key;
}
}
$temp = array_flip($temp);
unset($sf_term_array);
natcasesort($temp);
$sf_term_array = $temp;
$sf_copy = $sf_term_array;
// get specific field name
$sf_value = get_specific_field_list(['code' => "'$prefix'"]);
$sf_value = array_shift($sf_value);
$multiple_select .= '<td><label class="sf-select-multiple-title" for="sf_'.$prefix.'[]">'.$sf_value['name'].'</label><br />';
$multiple_select .= format_one_specific_field_select($prefix, $sf_term_array, $op, 'multiple="multiple" size="7" class="sf-select-multiple"');
$multiple_select .= '</td>';
$multiple_selects .= $multiple_select;
$i++;
}
return $multiple_selects;
}
/**
* Build the normal form.
*
* First, natural way.
*/
function search_widget_normal_form($action, $show_thesaurus, $sf_terms, $op)
{
$thesaurus_icon = Display::return_icon('thesaurus.gif', get_lang('SearchAdvancedOptions'), ['id' => 'thesaurus-icon']);
$advanced_options = '<a id="tags-toggle" href="#">'.get_lang('SearchAdvancedOptions').'</a>';
$display_thesaurus = ($show_thesaurus == true ? 'block' : 'none');
$help = '<h3>'.get_lang('SearchKeywordsHelpTitle').'</h3>'.get_lang('SearchKeywordsHelpComment');
$mode = (!empty($_REQUEST['mode']) ? htmlentities($_REQUEST['mode']) : 'gallery');
$type = (!empty($_REQUEST['type']) ? htmlentities($_REQUEST['type']) : 'normal');
/**
* POST avoid long urls, but we are using GET because
* SortableTableFromArray pagination is done with simple links, so now we
* could not send a form in pagination.
*/
if (isset($_GET['action']) && strcmp(trim($_GET['action']), 'search') === 0) {
$action = 'index.php';
}
$navigator_info = api_get_navigator();
if ($navigator_info['name'] == 'Internet Explorer' && $navigator_info['version'] == '6') {
$submit_button1 = '<input type="submit" id="submit" value="'.get_lang('Search').'" />';
$submit_button2 = '<input class="lower-submit" type="submit" value="'.get_lang('Search').'" />';
$reset_button = '<input type="submit" id="tags-clean" value="'.get_lang('SearchResetKeywords').'" />';
} else {
$submit_button1 = '<button class="search" type="submit" id="submit" value="'.get_lang("Search").'" /> '.get_lang('Search').'</button>';
$submit_button2 = '<button class="search" type="submit" value="'.get_lang('Search').'" />'.get_lang('Search').'</button>';
$reset_button = '<button class="save" type="submit" id="tags-clean" value="'.get_lang('SearchResetKeywords').'" />'.get_lang('SearchResetKeywords').'</button> ';
}
$query = isset($_REQUEST['query']) ? Security::remove_XSS($_REQUEST['query']) : null;
$form = '<form id="chamilo_search" action="'.$action.'" method="GET">
<input type="text" id="query" name="query" size="40" value="'.$query.'" />
<input type="hidden" name="mode" value="'.$mode.'"/>
<input type="hidden" name="type" value="'.$type.'"/>
<input type="hidden" name="tablename_page_nr" value="1" />
'.$submit_button1.'
<br /><br />';
$list = get_specific_field_list();
if (!empty($list)) {
$form .= '<span class="search-links-box">'.$advanced_options.'&nbsp;</span>
<div id="tags" class="tags" style="display:'.$display_thesaurus.';">
<div class="search-help-box">'.$help.'</div>
<table>
<tr>';
$form .= format_specific_fields_selects($sf_terms, $op);
$or_checked = '';
$and_checked = '';
if ($op == 'or') {
$or_checked = 'checked="checked"';
} elseif ($op == 'and') {
$and_checked = 'checked="checked"';
}
$form .= '</tr>
<tr>
<td id="operator-select">
'.get_lang('SearchCombineSearchWith').':<br />
<input type="radio" class="search-operator" name="operator" value="or" '.$or_checked.'>'.api_strtoupper(get_lang('Or')).'</input>
<input type="radio" class="search-operator" name="operator" value="and" '.$and_checked.'>'.api_strtoupper(get_lang('And')).'</input>
</td>
<td></td>
<td>
<br />
'.$reset_button.'
'.$submit_button2.'
</td>
</tr>
</table>
</div>';
}
$form .= '</form>
<br style="clear: both;"/>';
return $form;
}
/**
* Build the prefilter form.
*
* This type allow filter all other multiple select terms by one term in a dinamic way
*/
function search_widget_prefilter_form(
$action,
$show_thesaurus,
$sf_terms,
$op,
$prefilter_prefix = null
) {
$thesaurus_icon = Display::return_icon('thesaurus.gif', get_lang('SearchAdvancedOptions'), ['id' => 'thesaurus-icon']);
$advanced_options = '<a id="tags-toggle" href="#">'.get_lang('SearchAdvancedOptions').'</a>';
$display_thesaurus = ($show_thesaurus == true ? 'block' : 'none');
$help = '<h3>'.get_lang('SearchKeywordsHelpTitle').'</h3>'.get_lang('SearchKeywordsHelpComment');
$mode = (!empty($_REQUEST['mode']) ? htmlentities($_REQUEST['mode']) : 'gallery');
$type = (!empty($_REQUEST['type']) ? htmlentities($_REQUEST['type']) : 'normal');
/**
* POST avoid long urls, but we are using GET because
* SortableTableFromArray pagination is done with simple links, so now we
* could not send a form in pagination.
*/
if (isset($_GET['action']) && strcmp(trim($_GET['action']), 'search') === 0) {
$action = 'index.php';
}
$form = '
<form id="chamilo_search" action="'.$action.'" method="GET">
<input type="text" id="query" name="query" size="40" />
<input type="hidden" name="mode" value="'.$mode.'"/>
<input type="hidden" name="type" value="'.$type.'"/>
<input type="hidden" name="tablename_page_nr" value="1" />
<input type="submit" id="submit" value="'.get_lang("Search").'" />
<br /><br />';
$list = get_specific_field_list();
if (!empty($list)) {
$form .= ' <span class="search-links-box">'.$thesaurus_icon.$advanced_options.'&nbsp;</span>
<div id="tags" class="tags" style="display:'.$display_thesaurus.';">
<div class="search-help-box">'.$help.'</div>
<table>
<tr>';
if (!is_null($prefilter_prefix)) {
//sorting the array of terms
$temp = [];
foreach ($sf_terms[$prefilter_prefix] as $key => $value) {
$temp[trim(stripslashes($value['name']))] = $key;
}
$temp = array_flip($temp);
unset($sf_term_array);
natcasesort($temp);
$sf_term_array = $temp;
// get specific field name
$sf_value = get_specific_field_list(['code' => "'$prefilter_prefix'"]);
$sf_value = array_shift($sf_value);
$form .= '<label class="sf-select-multiple-title" for="sf_'.$prefix.'[]">'.$icons_for_search_terms[$prefix].' '.$sf_value['name'].'</label><br />';
$form .= format_one_specific_field_select($prefilter_prefix, $sf_term_array, $op, 'id="prefilter"');
$form .= format_specific_fields_selects($sf_terms, $op, $prefilter_prefix);
} else {
$form .= format_specific_fields_selects($sf_terms, $op);
}
$or_checked = '';
$and_checked = '';
if ($op == 'or') {
$or_checked = 'checked="checked"';
} elseif ($op == 'and') {
$and_checked = 'checked="checked"';
}
$form .= '
</tr>
<tr>
<td id="operator-select">
'.get_lang('SearchCombineSearchWith').':<br />
<input type="radio" class="search-operator" name="operator" value="or" '.$or_checked.'>'.api_strtoupper(get_lang('Or')).'</input>
<input type="radio" class="search-operator" name="operator" value="and" '.$and_checked.'>'.api_strtoupper(get_lang('And')).'</input>
</td>
<td></td>
<td>
<br />
<input class="lower-submit" type="submit" value="'.get_lang('Search').'" />
<input type="submit" id="tags-clean" value="'.get_lang('SearchResetKeywords').'" />
</td>
</tr>
</table>
</div>';
}
$form .= '
</form>
<br style="clear: both;"/>';
return $form;
}
/**
* Show search form.
*/
function display_search_form($action, $show_thesaurus, $sf_terms, $op)
{
$type = (!empty($_REQUEST['type']) ? htmlentities($_REQUEST['type']) : 'normal');
switch ($type) {
case 'prefilter':
$prefilter_prefix = api_get_setting('search_prefilter_prefix');
$form = search_widget_prefilter_form(
$action,
$show_thesaurus,
$sf_terms,
$op,
$prefilter_prefix
);
break;
case 'normal':
default:
$form = search_widget_normal_form(
$action,
$show_thesaurus,
$sf_terms,
$op
);
break;
}
// show built form
echo $form;
}
/**
* Show the search widget.
*
* The form will post to index.php by default, you can pass a value to
* $action to use a custom action.
* IMPORTANT: you have to call search_widget_prepare() before calling this
* function or otherwise the form will not behave correctly.
*
* @param string $action Just in case your action is not
* index.php
*/
function search_widget_show($action = 'index.php')
{
require_once api_get_path(LIBRARY_PATH).'search/ChamiloQuery.php';
// TODO: load images dinamically when they're avalaible from specific field ui to add
$groupId = api_get_group_id();
$sf_terms = [];
$specific_fields = get_specific_field_list();
$url_params = [];
if (($cid = api_get_course_id()) != -1) { // with cid
// get search engine terms
$course_filter = chamilo_get_boolean_query(XAPIAN_PREFIX_COURSEID.$cid);
$dkterms = chamilo_query_simple_query('', 0, 1000, [$course_filter]);
//prepare specific fields names (and also get possible URL param names)
foreach ($specific_fields as $specific_field) {
$temp = [];
if (is_array($dkterms) && count($dkterms) > 0) {
foreach ($dkterms[1] as $obj) {
$temp = array_merge($obj['sf-'.$specific_field['code']], $temp);
}
}
$sf_terms[$specific_field['code']] = $temp;
$url_params[] = 'sf_'.$specific_field['code'];
unset($temp);
}
} else { // without cid
// prepare specific fields names (and also get possible URL param names)
foreach ($specific_fields as $specific_field) {
//get Xapian terms for a specific term prefix, in ISO, apparently
$sf_terms[$specific_field['code']] = xapian_get_all_terms(1000, $specific_field['code']);
$url_params[] = 'sf_'.$specific_field['code'];
}
}
echo '<h2>'.get_lang('Search').'</h2>';
// Tool introduction
// TODO: Settings for the online editor to be checked (insert an image for example). Probably this is a special case here.
if (api_get_course_id() !== -1) {
if (!empty($groupId)) {
Display::display_introduction_section(TOOL_SEARCH.$groupId);
} else {
Display::display_introduction_section(TOOL_SEARCH);
}
}
$op = 'or';
if (!empty($_REQUEST['operator']) && in_array($op, ['or', 'and'])) {
$op = $_REQUEST['operator'];
}
//check if URL params are defined (to see if we show the thesaurus or not)
$show_thesaurus = false;
foreach ($url_params as $param) {
if (isset($_REQUEST[$param]) && is_array($_REQUEST[$param])) {
$thesaurus_decided = false;
foreach ($_REQUEST[$param] as $term) {
if (!empty($term)) {
$show_thesaurus = true;
$thesaurus_decided = true;
break;
}
}
if ($thesaurus_decided) {
break;
}
}
}
// create the form
// TODO: use FormValidator
display_search_form($action, $show_thesaurus, $sf_terms, $op);
}

View File

@@ -0,0 +1,105 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Process documents before pass it to search listing scripts.
*
* @package chamilo.include.search
*/
class document_processor extends search_processor
{
public function __construct($rows)
{
$this->rows = $rows;
}
public function process()
{
$results = [];
foreach ($this->rows as $row_val) {
$search_show_unlinked_results = (api_get_setting('search_show_unlinked_results') == 'true');
$course_visible_for_user = api_is_course_visible_for_user(null, $row_val['courseid']);
// can view course?
if ($course_visible_for_user || $search_show_unlinked_results) {
// is visible?
$visibility = api_get_item_visibility(api_get_course_info($row_val['courseid']), TOOL_DOCUMENT, $row_val['xapian_data'][SE_DATA]['doc_id']);
if ($visibility) {
list($thumbnail, $image, $name, $author, $url) = $this->get_information($row_val['courseid'], $row_val['xapian_data'][SE_DATA]['doc_id']);
$result = [
'toolid' => TOOL_DOCUMENT,
'score' => $row_val['score'],
'url' => $url,
'thumbnail' => $thumbnail,
'image' => $image,
'title' => $name,
'author' => $author,
];
if ($course_visible_for_user) {
$results[] = $result;
} else { // course not visible for user
if ($search_show_unlinked_results) {
$result['url'] = '';
$results[] = $result;
}
}
}
}
}
// get information to sort
foreach ($results as $key => $row) {
$score[$key] = $row['score'];
}
// Sort results with score descending
array_multisort($score, SORT_DESC, $results);
return $results;
}
/**
* Get document information.
*/
private function get_information($course_id, $doc_id)
{
$course_information = api_get_course_info($course_id);
$course_id = $course_information['real_id'];
$course_path = $course_information['path'];
if (!empty($course_information)) {
$item_property_table = Database::get_course_table(TABLE_ITEM_PROPERTY);
$doc_table = Database::get_course_table(TABLE_DOCUMENT);
$doc_id = intval($doc_id);
$sql = "SELECT * FROM $doc_table
WHERE $doc_table.id = $doc_id AND c_id = $course_id
LIMIT 1";
$dk_result = Database::query($sql);
$sql = "SELECT insert_user_id FROM $item_property_table
WHERE ref = $doc_id AND tool = '".TOOL_DOCUMENT."' AND c_id = $course_id
LIMIT 1";
$name = '';
if ($row = Database::fetch_array($dk_result)) {
$name = $row['title'];
$url = api_get_path(WEB_COURSE_PATH).'%s/document%s';
$url = sprintf($url, $course_path, $row['path']);
// Get the image path
$icon = choose_image(basename($row['path']));
$thumbnail = Display::returnIconPath($icon);
$image = $thumbnail;
//FIXME: use big images
// get author
$author = '';
$item_result = Database::query($sql);
if ($row = Database::fetch_array($item_result)) {
$user_data = api_get_user_info($row['insert_user_id']);
$author = api_get_person_name($user_data['firstName'], $user_data['lastName']);
}
}
return [$thumbnail, $image, $name, $author, $url]; // FIXME: is it posible to get an author here?
} else {
return [];
}
}
}

View File

@@ -0,0 +1,6 @@
<html>
<head>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,151 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Process learning paths before pass it to search listing scripts.
*
* @package chamilo.include.search
*/
class learnpath_processor extends search_processor
{
public $learnpaths = [];
public function __construct($rows)
{
$this->rows = $rows;
// group by learning path
foreach ($rows as $row_id => $row_val) {
$lp_id = $row_val['xapian_data'][SE_DATA]['lp_id'];
$lp_item = $row_val['xapian_data'][SE_DATA]['lp_item'];
$document_id = $row_val['xapian_data'][SE_DATA]['document_id'];
$courseid = $row_val['courseid'];
$item = [
'courseid' => $courseid,
'lp_item' => $lp_item,
'score' => $row_val['score'],
'row_id' => $row_id,
];
$this->learnpaths[$courseid][$lp_id][] = $item;
$this->learnpaths[$courseid][$lp_id]['total_score'] += $row_val['score'];
$this->learnpaths[$courseid][$lp_id]['has_document_id'] = is_numeric($document_id);
}
}
/**
* @return array
*/
public function process()
{
$results = [];
foreach ($this->learnpaths as $courseid => $learnpaths) {
$search_show_unlinked_results = api_get_setting('search_show_unlinked_results') == 'true';
$course_visible_for_user = api_is_course_visible_for_user(null, $courseid);
// can view course?
if ($course_visible_for_user || $search_show_unlinked_results) {
foreach ($learnpaths as $lp_id => $lp) {
// is visible?
$visibility = api_get_item_visibility(
api_get_course_info($courseid),
TOOL_LEARNPATH,
$lp_id
);
if ($visibility) {
list($thumbnail, $image, $name, $author) = $this->get_information($courseid, $lp_id, $lp['has_document_id']);
$url = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?cidReq=%s&action=view&lp_id=%s';
$url = sprintf($url, $courseid, $lp_id);
$result = [
'toolid' => TOOL_LEARNPATH,
'score' => $lp['total_score'] / (count($lp) - 1), // not count total_score array item
'url' => $url,
'thumbnail' => $thumbnail,
'image' => $image,
'title' => $name,
'author' => $author,
];
if ($course_visible_for_user) {
$results[] = $result;
} else { // course not visible for user
if ($search_show_unlinked_results) {
$result['url'] = '';
$results[] = $result;
}
}
}
}
}
}
// get information to sort
foreach ($results as $key => $row) {
$score[$key] = $row['score'];
}
// Sort results with score descending
array_multisort($score, SORT_DESC, $results);
return $results;
}
/**
* Get learning path information.
*/
private function get_information($course_id, $lp_id, $has_document_id = true)
{
$course_information = api_get_course_info($course_id);
$course_id = $course_information['real_id'];
$course_path = $course_information['path'];
if (!empty($course_information)) {
$lpi_table = Database::get_course_table(TABLE_LP_ITEM);
$lp_table = Database::get_course_table(TABLE_LP_MAIN);
$doc_table = Database::get_course_table(TABLE_DOCUMENT);
$lp_id = intval($lp_id);
if ($has_document_id) {
$sql = "SELECT $lpi_table.id, $lp_table.name, $lp_table.author, $doc_table.path
FROM $lp_table, $lpi_table
INNER JOIN $doc_table ON $lpi_table.path = $doc_table.id AND $lpi_table.c_id = $course_id
WHERE $lpi_table.c_id = $course_id AND
$doc_table.c_id = $course_id AND
$lpi_table.lp_id = $lp_id AND
$lpi_table.display_order = 1 AND
$lp_table.id = $lpi_table.lp_id
LIMIT 1";
} else {
$sql = "SELECT $lpi_table.id, $lp_table.name, $lp_table.author
FROM $lp_table, $lpi_table
WHERE
$lpi_table.c_id = $course_id AND
$lp_table.c_id = $course_id AND
$lpi_table.lp_id = $lp_id AND
$lpi_table.display_order = 1 AND
$lp_table.id = $lpi_table.lp_id
LIMIT 1";
}
$dk_result = Database::query($sql);
$path = '';
$name = '';
if ($row = Database::fetch_array($dk_result)) {
// Get the image path
$img_location = api_get_path(WEB_COURSE_PATH).$course_path."/document/";
$thumbnail_path = str_replace('.png.html', '_thumb.png', $row['path']);
$big_img_path = str_replace('.png.html', '.png', $row['path']);
$thumbnail = '';
if (!empty($thumbnail_path)) {
$thumbnail = $img_location.$thumbnail_path;
}
$image = '';
if (!empty($big_img_path)) {
$image = $img_location.$big_img_path;
}
$name = $row['name'];
}
return [$thumbnail, $image, $name, $row['author']];
} else {
return [];
}
}
}

View File

@@ -0,0 +1,122 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Process links before pass it to search listing scripts.
*
* @package chamilo.include.search
*/
class link_processor extends search_processor
{
public $links = [];
public function __construct($rows)
{
$this->rows = $rows;
// group all links together
foreach ($rows as $row_id => $row_val) {
$link_id = $row_val['xapian_data'][SE_DATA]['link_id'];
$courseid = $row_val['courseid'];
$item = [
'courseid' => $courseid,
'score' => $row_val['score'],
'link_id' => $link_id,
'row_id' => $row_id,
];
$this->links[$courseid]['links'][] = $item;
$this->links[$courseid]['total_score'] += $row_val['score'];
}
}
public function process()
{
$results = [];
foreach ($this->links as $courseCode => $one_course_links) {
$course_info = api_get_course_info($courseCode);
$search_show_unlinked_results = (api_get_setting('search_show_unlinked_results') == 'true');
$course_visible_for_user = api_is_course_visible_for_user(null, $courseCode);
// can view course?
if ($course_visible_for_user || $search_show_unlinked_results) {
$result = null;
foreach ($one_course_links['links'] as $one_link) {
// is visible?
$visibility = api_get_item_visibility($course_info, TOOL_LINK, $one_link['link_id']);
if ($visibility) {
// if one is visible let show the result for a course
// also asume all data of this item like the data of the whole group of links(Ex. author)
list($thumbnail, $image, $name, $author, $url) = $this->get_information($courseCode, $one_link['link_id']);
$result_tmp = [
'toolid' => TOOL_LINK,
'score' => $one_course_links['total_score'] / (count($one_course_links) - 1), // not count total_score array item
'url' => $url,
'thumbnail' => $thumbnail,
'image' => $image,
'title' => $name,
'author' => $author,
];
if ($course_visible_for_user) {
$result = $result_tmp;
} else { // course not visible for user
if ($search_show_unlinked_results) {
$result_tmp['url'] = '';
$result = $result_tmp;
}
}
break;
}
}
if (!is_null($result)) {
// if there is at least one link item found show link to course Links tool page
$results[] = $result;
}
}
}
// get information to sort
foreach ($results as $key => $row) {
$score[$key] = $row['score'];
}
// Sort results with score descending
array_multisort($score, SORT_DESC, $results);
return $results;
}
/**
* Get document information.
*/
private function get_information($course_id, $link_id)
{
$course_information = api_get_course_info($course_id);
$course_id = $course_information['real_id'];
$course_id_alpha = $course_information['id'];
if (!empty($course_information)) {
$item_property_table = Database::get_course_table(TABLE_ITEM_PROPERTY);
$link_id = intval($link_id);
$sql = "SELECT insert_user_id FROM $item_property_table
WHERE ref = $link_id AND tool = '".TOOL_LINK."' AND c_id = $course_id
LIMIT 1";
$name = get_lang('Links');
$url = api_get_path(WEB_PATH).'main/link/link.php?cidReq=%s';
$url = sprintf($url, $course_id_alpha);
// Get the image path
$thumbnail = Display::returnIconPath('link.png');
$image = $thumbnail; //FIXME: use big images
// get author
$author = '';
$item_result = Database::query($sql);
if ($row = Database::fetch_array($item_result)) {
$user_data = api_get_user_info($row['insert_user_id']);
$author = api_get_person_name($user_data['firstName'], $user_data['lastName']);
}
return [$thumbnail, $image, $name, $author, $url];
} else {
return [];
}
}
}

View File

@@ -0,0 +1,141 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CourseBundle\Entity\CQuiz;
/**
* Process exercises before pass it to search listing scripts.
*
* @package chamilo.include.search
*/
class quiz_processor extends search_processor
{
public $exercices = [];
public function __construct($rows)
{
$this->rows = $rows;
// group by exercise
foreach ($rows as $row_id => $row_val) {
$courseid = $row_val['courseid'];
$se_data = $row_val['xapian_data'][SE_DATA];
switch ($row_val['xapian_data'][SE_DATA]['type']) {
case SE_DOCTYPE_EXERCISE_EXERCISE:
$exercise_id = $se_data['exercise_id'];
$question = null;
$item = [
'courseid' => $courseid,
'question' => $question,
'total_score' => $row_val['score'],
'row_id' => $row_id,
];
$this->exercises[$courseid][$exercise_id] = $item;
$this->exercises[$courseid][$exercise_id]['total_score'] += $row_val['score'];
break;
case SE_DOCTYPE_EXERCISE_QUESTION:
if (is_array($se_data['exercise_ids'])) {
foreach ($se_data['exercise_ids'] as $exercise_id) {
$question = $se_data['question_id'];
$item = [
'courseid' => $courseid,
'question' => $question,
'total_score' => $row_val['score'],
'row_id' => $row_id,
];
$this->exercises[$courseid][$exercise_id] = $item;
$this->exercises[$courseid][$exercise_id]['total_score'] += $row_val['score'];
}
}
break;
}
}
}
public function process()
{
$results = [];
foreach ($this->exercises as $courseid => $exercises) {
$search_show_unlinked_results = (api_get_setting('search_show_unlinked_results') == 'true');
$course_visible_for_user = api_is_course_visible_for_user(null, $courseid);
// can view course?
if ($course_visible_for_user || $search_show_unlinked_results) {
foreach ($exercises as $exercise_id => $exercise) {
// is visible?
$visibility = api_get_item_visibility(api_get_course_info($courseid), TOOL_QUIZ, $exercise_id);
if ($visibility) {
list($thumbnail, $image, $name, $author) = $this->get_information($courseid, $exercise_id);
$url = api_get_path(WEB_CODE_PATH).'exercise/exercise_submit.php?cidReq=%s&exerciseId=%s';
$url = sprintf($url, $courseid, $exercise_id);
$result = [
'toolid' => TOOL_QUIZ,
'total_score' => $exercise['total_score'] / (count($exercise) - 1), // not count total_score array item
'url' => $url,
'thumbnail' => $thumbnail,
'image' => $image,
'title' => $name,
'author' => $author,
];
if ($course_visible_for_user) {
$results[] = $result;
} else { // course not visible for user
if ($search_show_unlinked_results) {
$result['url'] = '';
$results[] = $result;
}
}
}
}
}
}
// get information to sort
foreach ($results as $key => $row) {
$score[$key] = $row['total_score'];
}
// Sort results with score descending
array_multisort($score, SORT_DESC, $results);
return $results;
}
/**
* Get learning path information.
*/
private function get_information($courseCode, $exercise_id)
{
$course_information = api_get_course_info($courseCode);
$course_id = $course_information['real_id'];
$em = Database::getManager();
if (!empty($course_information)) {
$exercise_id = intval($exercise_id);
$dk_result = $em->find(CQuiz::class, $exercise_id);
$name = '';
if ($dk_result) {
// Get the image path
$thumbnail = Display::returnIconPath('quiz.png');
$image = $thumbnail; //FIXME: use big images
$name = $dk_result->getTitle();
// get author
$author = '';
$item_result = $em
->getRepository('ChamiloCourseBundle:CItemProperty')
->findOneBy([
'ref' => $exercise_id,
'tool' => TOOL_QUIZ,
'course' => $course_id,
]);
if ($item_result) {
$author = UserManager::formatUserFullName($item_result->getInsertUser());
}
}
return [$thumbnail, $image, $name, $author];
} else {
return [];
}
}
}

View File

@@ -0,0 +1,32 @@
<?php
/* For licensing terms, see /license.txt */
/**
* @package chamilo.include.search
*/
/**
* Base class to make tool processors.
*
* This processor have to prepare the raw data from the search engine api to
* make it usable by search. See some implementations of these classes if you
* want to make one.
*
* Classes that extends this one should be named like: TOOL_<toolname> on
* TOOL_<toolname>.class.php
* See lp_list_search for an example of calling the process.
*
* @package chamilo.include.search
*/
abstract class search_processor
{
/**
* Search engine api results.
*/
protected $rows = [];
/**
* Process the data sorted by the constructor.
*/
abstract protected function process();
}

View File

@@ -0,0 +1,333 @@
<?php
/* For licensing terms, see /license.txt */
/**
* @package chamilo.include.search
*/
require_once 'xapian.php';
/**
* Abstract helper class.
*
* @package chamilo.include.search
*/
abstract class XapianIndexer
{
/* XapianTermGenerator */
public $indexer;
/* XapianStem */
public $stemmer;
/* XapianWritableDatabase */
protected $db;
/* IndexableChunk[] */
protected $chunks;
/**
* Class contructor.
*/
public function __construct()
{
$this->db = null;
$this->stemmer = null;
}
/**
* Class destructor.
*/
public function __destruct()
{
unset($this->db);
unset($this->stemmer);
}
/**
* Generates a list of languages Xapian manages.
*
* This method enables the definition of more matches between
* Chamilo languages and Xapian languages (through hardcoding)
*
* @return array Array of languages codes -> Xapian languages
*/
final public function xapian_languages()
{
/* http://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html */
return [
'none' => 'none', //don't stem terms
'da' => 'danish',
'nl' => 'dutch',
/* Martin Porter's 2002 revision of his stemmer */
'en' => 'english',
/* Lovin's stemmer */
'lovins' => 'english_lovins',
/* Porter's stemmer as described in his 1980 paper */
'porter' => 'english_porter',
'fi' => 'finnish',
'fr' => 'french',
'de' => 'german',
'it' => 'italian',
'no' => 'norwegian',
'pt' => 'portuguese',
'ru' => 'russian',
'es' => 'spanish',
'sv' => 'swedish',
];
}
/**
* Connect to the database, and create it if it doesn't exist.
*/
public function connectDb($path = null, $dbMode = null, $lang = 'english')
{
if ($this->db != null) {
return $this->db;
}
if ($dbMode == null) {
$dbMode = Xapian::DB_CREATE_OR_OPEN;
}
if ($path == null) {
$path = api_get_path(SYS_UPLOAD_PATH).'plugins/xapian/searchdb/';
}
try {
$this->db = new XapianWritableDatabase($path, $dbMode);
$this->indexer = new XapianTermGenerator();
if (!in_array($lang, $this->xapian_languages())) {
$lang = 'english';
}
$this->stemmer = new XapianStem($lang);
$this->indexer->set_stemmer($this->stemmer);
return $this->db;
} catch (Exception $e) {
echo Display::return_message($e->getMessage(), 'error');
return 1;
}
}
/**
* Simple getter for the db attribute.
*
* @return object The db attribute
*/
public function getDb()
{
return $this->db;
}
/**
* Add this chunk to the chunk array attribute.
*
* @param string Chunk of text
*/
public function addChunk($chunk)
{
$this->chunks[] = $chunk;
}
/**
* Actually index the current data.
*
* @return int New Xapian document ID or null upon failure
*/
public function index()
{
try {
if (!empty($this->chunks)) {
foreach ($this->chunks as $chunk) {
$doc = new XapianDocument();
$this->indexer->set_document($doc);
if (!empty($chunk->terms)) {
foreach ($chunk->terms as $term) {
/* FIXME: think of getting weight */
$doc->add_term($term['flag'].$term['name'], 1);
}
}
// free-form index all data array (title, content, etc)
if (!empty($chunk->data)) {
foreach ($chunk->data as $key => $value) {
$this->indexer->index_text($value, 1);
}
}
$doc->set_data($chunk->xapian_data, 1);
$did = $this->db->add_document($doc);
//write to disk
$this->db->flush();
return $did;
}
}
} catch (Exception $e) {
echo Display::return_message($e->getMessage(), 'error');
exit(1);
}
}
/**
* Get a specific document from xapian db.
*
* @param int did Xapian::docid
*
* @return mixed XapianDocument, or false on error
*/
public function get_document($did)
{
if ($this->db == null) {
$this->connectDb();
}
try {
$docid = $this->db->get_document($did);
} catch (Exception $e) {
//echo Display::return_message($e->getMessage(), 'error');
return false;
}
return $docid;
}
/**
* Get document data on a xapian document.
*
* @param XapianDocument $doc xapian document to push into the db
*
* @return mixed xapian document data or FALSE if error
*/
public function get_document_data($doc)
{
if ($this->db == null) {
$this->connectDb();
}
try {
if (!is_a($doc, 'XapianDocument')) {
return false;
}
$doc_data = $doc->get_data();
return $doc_data;
} catch (Exception $e) {
//echo Display::return_message($e->getMessage(), 'error');
return false;
}
}
/**
* Replace all terms of a document in xapian db.
*
* @param int $did Xapian::docid
* @param array $terms New terms of the document
* @param string $prefix Prefix used to categorize the doc
* (usually 'T' for title, 'A' for author)
*
* @return bool false on error
*/
public function update_terms($did, $terms, $prefix)
{
$doc = $this->get_document($did);
if ($doc === false) {
return false;
}
$doc->clear_terms();
foreach ($terms as $term) {
//add directly
$doc->add_term($prefix.$term, 1);
}
$this->db->replace_document($did, $doc);
$this->db->flush();
return true;
}
/**
* Remove a document from xapian db.
*
* @param int did Xapian::docid
*/
public function remove_document($did)
{
if ($this->db == null) {
$this->connectDb();
}
$did = (int) $did;
if ($did > 0) {
$doc = $this->get_document($did);
if ($doc !== false) {
$this->db->delete_document($did);
$this->db->flush();
}
}
}
/**
* Adds a term to the document specified.
*
* @param string $term The term to add
* @param XapianDocument $doc The xapian document where to add the term
*
* @return mixed XapianDocument, or false on error
*/
public function add_term_to_doc($term, $doc)
{
if (!is_a($doc, 'XapianDocument')) {
return false;
}
try {
$doc->add_term($term);
} catch (Exception $e) {
echo Display::return_message($e->getMessage(), 'error');
return 1;
}
}
/**
* Remove a term from the document specified.
*
* @param string $term The term to add
* @param XapianDocument $doc The xapian document where to add the term
*
* @return mixed XapianDocument, or false on error
*/
public function remove_term_from_doc($term, $doc)
{
if (!is_a($doc, 'XapianDocument')) {
return false;
}
try {
$doc->remove_term($term);
} catch (Exception $e) {
echo Display::return_message($e->getMessage(), 'error');
return 1;
}
}
/**
* Replace a document in the actual db.
*
* @param XapianDocument $doc xapian document to push into the db
* @param int $did xapian document id of the document to replace
*
* @return mixed
*/
public function replace_document($doc, $did)
{
if (!is_a($doc, 'XapianDocument')) {
return false;
}
if ($this->db == null) {
$this->connectDb();
}
try {
$this->getDb()->replace_document((int) $did, $doc);
$this->getDb()->flush();
} catch (Exception $e) {
echo Display::return_message($e->getMessage(), 'error');
return 1;
}
}
}

View File

@@ -0,0 +1,274 @@
<?php
/* For licensing terms, see /license.txt */
/**
* @package chamilo.include.search
*/
require_once 'xapian.php';
//TODO: think another way without including specific fields here
require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
define('XAPIAN_DB', api_get_path(SYS_UPLOAD_PATH).'plugins/xapian/searchdb/');
/**
* Queries the database.
* The xapian_query function queries the database using both a query string
* and application-defined terms. Based on drupal-xapian.
*
* @param string $query_string The search string. This string will
* be parsed and stemmed automatically.
* @param XapianDatabase $db Xapian database to connect
* @param int $start An integer defining the first
* document to return
* @param int $length the number of results to return
* @param array $extra an array containing arrays of
* extra terms to search for
* @param int $count_type Number of items to retrieve
*
* @return array an array of nids corresponding to the results
*/
function xapian_query($query_string, $db = null, $start = 0, $length = 10, $extra = [], $count_type = 0)
{
try {
if (!is_object($db)) {
$db = new XapianDatabase(XAPIAN_DB);
}
// Build subqueries from $extra array. Now only used by tags search filter on search widget
$subqueries = [];
foreach ($extra as $subquery) {
if (!empty($subquery)) {
$subqueries[] = new XapianQuery($subquery);
}
}
$query = null;
$enquire = new XapianEnquire($db);
if (!empty($query_string)) {
$query_parser = new XapianQueryParser();
//TODO: choose stemmer
$stemmer = new XapianStem("english");
$query_parser->set_stemmer($stemmer);
$query_parser->set_database($db);
$query_parser->set_stemming_strategy(XapianQueryParser::STEM_SOME);
$query_parser->add_boolean_prefix('courseid', XAPIAN_PREFIX_COURSEID);
$query_parser->add_boolean_prefix('toolid', XAPIAN_PREFIX_TOOLID);
$query = $query_parser->parse_query($query_string);
$final_array = array_merge($subqueries, [$query]);
$query = new XapianQuery(XapianQuery::OP_AND, $final_array);
} else {
$query = new XapianQuery(XapianQuery::OP_OR, $subqueries);
}
$enquire->set_query($query);
$matches = $enquire->get_mset((int) $start, (int) $length);
$specific_fields = get_specific_field_list();
$results = [];
$i = $matches->begin();
// Display the results.
//echo $matches->get_matches_estimated().'results found';
$count = 0;
while (!$i->equals($matches->end())) {
$count++;
$document = $i->get_document();
if (is_object($document)) {
// process one item terms
$courseid_terms = xapian_get_doc_terms($document, XAPIAN_PREFIX_COURSEID);
$results[$count]['courseid'] = substr($courseid_terms[0]['name'], 1);
$toolid_terms = xapian_get_doc_terms($document, XAPIAN_PREFIX_TOOLID);
$results[$count]['toolid'] = substr($toolid_terms[0]['name'], 1);
// process each specific field prefix
foreach ($specific_fields as $specific_field) {
$results[$count]['sf-'.$specific_field['code']] = xapian_get_doc_terms($document, $specific_field['code']);
}
// rest of data
$results[$count]['xapian_data'] = unserialize($document->get_data());
$results[$count]['score'] = ($i->get_percent());
}
$i->next();
}
switch ($count_type) {
case 1: // Lower bound
$count = $matches->get_matches_lower_bound();
break;
case 2: // Upper bound
$count = $matches->get_matches_upper_bound();
break;
case 0: // Best estimate
default:
$count = $matches->get_matches_estimated();
break;
}
return [$count, $results];
} catch (Exception $e) {
display_xapian_error($e->getMessage());
return null;
}
}
/**
* build a boolean query.
*/
function xapian_get_boolean_query($term)
{
return new XapianQuery($term);
}
/**
* Retrieve a list db terms.
*
* @param int $count Number of terms to retrieve
* @param char $prefix The prefix of the term to retrieve
* @param XapianDatabase $db Xapian database to connect
*
* @return array
*/
function xapian_get_all_terms($count = 0, $prefix, $db = null)
{
try {
if (!is_object($db)) {
$db = new XapianDatabase(XAPIAN_DB);
}
if (!empty($prefix)) {
$termi = $db->allterms_begin($prefix);
} else {
$termi = $db->allterms_begin();
}
$terms = [];
$i = 0;
for (; !$termi->equals($db->allterms_end()) && (++$i <= $count || $count == 0); $termi->next()) {
$terms[] = [
'frequency' => $termi->get_termfreq(),
'name' => $termi->get_term(),
];
}
return $terms;
} catch (Exception $e) {
display_xapian_error($e->getMessage());
return null;
}
}
/**
* Retrieve all terms of a document.
*
* @param XapianDocument document searched
*
* @return array
*/
function xapian_get_doc_terms($doc = null, $prefix)
{
try {
if (!is_a($doc, 'XapianDocument')) {
return;
}
//TODO: make the filter by prefix on xapian if possible
//ojwb marvil07: use Document::termlist_begin() and then skip_to(prefix) on the TermIterator
//ojwb you'll need to check the end condition by hand though
$terms = [];
for ($termi = $doc->termlist_begin(); !$termi->equals($doc->termlist_end()); $termi->next()) {
$term = [
'frequency' => $termi->get_termfreq(),
'name' => $termi->get_term(),
];
if ($term['name'][0] === $prefix) {
$terms[] = $term;
}
}
return $terms;
} catch (Exception $e) {
display_xapian_error($e->getMessage());
return null;
}
}
/**
* Join xapian queries.
*
* @param XapianQuery|array $query1
* @param XapianQuery|array $query2
* @param string $op
*
* @return XapianQuery query joined
*/
function xapian_join_queries($query1, $query2 = null, $op = 'or')
{
// let decide how to join, avoiding include xapian.php outside
switch ($op) {
case 'or':
$op = XapianQuery::OP_OR;
break;
case 'and':
$op = XapianQuery::OP_AND;
break;
default:
$op = XapianQuery::OP_OR;
break;
}
// review parameters to decide how to join
if (!is_array($query1)) {
$query1 = [$query1];
}
if (is_null($query2)) {
// join an array of queries with $op
return new XapianQuery($op, $query1);
}
if (!is_array($query2)) {
$query2 = [$query2];
}
return new XapianQuery($op, array_merge($query1, $query2));
}
/**
* @author Isaac flores paz <florespaz@bidsoftperu.com>
*
* @param string The xapian error message
*
* @return string The chamilo error message
*/
function display_xapian_error($xapian_error_message)
{
$message = explode(':', $xapian_error_message);
$type_error_message = $message[0];
if ($type_error_message == 'DatabaseOpeningError') {
$message_error = get_lang('SearchDatabaseOpeningError');
} elseif ($type_error_message == 'DatabaseVersionError') {
$message_error = get_lang('SearchDatabaseVersionError');
} elseif ($type_error_message == 'DatabaseModifiedError') {
$message_error = get_lang('SearchDatabaseModifiedError');
} elseif ($type_error_message == 'DatabaseLockError') {
$message_error = get_lang('SearchDatabaseLockError');
} elseif ($type_error_message == 'DatabaseCreateError') {
$message_error = get_lang('SearchDatabaseCreateError');
} elseif ($type_error_message == 'DatabaseCorruptError') {
$message_error = get_lang('SearchDatabaseCorruptError');
} elseif ($type_error_message == 'NetworkTimeoutError') {
$message_error = get_lang('SearchNetworkTimeoutError');
} else {
$message_error = get_lang('SearchOtherXapianError');
}
$display_message = get_lang('Error').' : '.$message_error;
echo Display::return_message($display_message, 'error');
}

View File

@@ -0,0 +1,6 @@
<html>
<head>
</head>
<body>
</body>
</html>