Actualización
This commit is contained in:
103
main/inc/lib/search/ChamiloIndexer.class.php
Normal file
103
main/inc/lib/search/ChamiloIndexer.class.php
Normal 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;
|
||||
}
|
||||
}
|
||||
82
main/inc/lib/search/ChamiloQuery.php
Normal file
82
main/inc/lib/search/ChamiloQuery.php
Normal 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);
|
||||
}
|
||||
122
main/inc/lib/search/IndexableChunk.class.php
Normal file
122
main/inc/lib/search/IndexableChunk.class.php
Normal 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);
|
||||
}
|
||||
}
|
||||
99
main/inc/lib/search/get_terms.php
Normal file
99
main/inc/lib/search/get_terms.php
Normal 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);
|
||||
6
main/inc/lib/search/index.html
Normal file
6
main/inc/lib/search/index.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
86
main/inc/lib/search/search_widget.css
Normal file
86
main/inc/lib/search/search_widget.css
Normal 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;
|
||||
}
|
||||
42
main/inc/lib/search/search_widget.js
Normal file
42
main/inc/lib/search/search_widget.js
Normal 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);
|
||||
});
|
||||
|
||||
});
|
||||
404
main/inc/lib/search/search_widget.php
Normal file
404
main/inc/lib/search/search_widget.php
Normal 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.' </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.' </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);
|
||||
}
|
||||
105
main/inc/lib/search/tool_processors/document_processor.class.php
Normal file
105
main/inc/lib/search/tool_processors/document_processor.class.php
Normal 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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
6
main/inc/lib/search/tool_processors/index.html
Normal file
6
main/inc/lib/search/tool_processors/index.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
122
main/inc/lib/search/tool_processors/link_processor.class.php
Normal file
122
main/inc/lib/search/tool_processors/link_processor.class.php
Normal 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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
141
main/inc/lib/search/tool_processors/quiz_processor.class.php
Normal file
141
main/inc/lib/search/tool_processors/quiz_processor.class.php
Normal 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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
333
main/inc/lib/search/xapian/XapianIndexer.class.php
Normal file
333
main/inc/lib/search/xapian/XapianIndexer.class.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
274
main/inc/lib/search/xapian/XapianQuery.php
Normal file
274
main/inc/lib/search/xapian/XapianQuery.php
Normal 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');
|
||||
}
|
||||
6
main/inc/lib/search/xapian/index.html
Normal file
6
main/inc/lib/search/xapian/index.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user