Search engine: - Index - use wrapper methods instead on calling xapian.php directly in most of callings - improve dokeos xapian-API - XapianIndexer - new methods: - replace_document() - remove_term_from_doc() - add_term_to_doc() - DokeosIndexer - new methods: - dokeos_preprocess_results() - Preprocess all results depending on the toolid - set_terms() - general interface for getting, comparing and set search engine terms - get_terms_on_db() - Get the terms stored at database (normal, no xapian) - more wrappers for XapianIndexer methods: dokeos_query_query(), dokeos_query_query(), dokeos_get_boolean_query(), dokeos_join_queries() - remove tags search engine feature to replace it with a new generic feature: specific fields - it let define xapian prefix terms dinamically to be included across dokeos index on each tool - move search engine document ID field from lp item table to a general man db table search_engine_ref - Search - use search_engine processor classes to change how process results depending of the dokeos tool - change interface for terms select at search: now use one multiple select foreach specific field instead of jquery thickbox plugin only for tags - search ajax suggestion feature to query terms on db - it need some work Learnpath: - refactor search engine pieces to use new tables search_engine_ref, specific_field and specific_field_values - add scorm and woogie index - remove search widget from lp list and use it from main/search - also, there are part on audios on lp items feature addedskala
@ -0,0 +1,93 @@ |
||||
<?php |
||||
/* |
||||
============================================================================== |
||||
Dokeos - elearning and course management software |
||||
|
||||
Copyright (c) 2008 Dokeos SPRL |
||||
|
||||
For a full list of contributors, see "credits.txt". |
||||
The full license can be read in "license.txt". |
||||
|
||||
This program is free software; you can redistribute it and/or |
||||
modify it under the terms of the GNU General Public License |
||||
as published by the Free Software Foundation; either version 2 |
||||
of the License, or (at your option) any later version. |
||||
|
||||
See the GNU General Public License for more details. |
||||
|
||||
Contact: Dokeos, rue du Corbeau, 108, B-1030 Brussels, Belgium, info@dokeos.com |
||||
============================================================================== |
||||
*/ |
||||
/** |
||||
* Manage specific fields |
||||
* |
||||
* @package dokeos.admin |
||||
*/ |
||||
$language_file[] = 'admin'; |
||||
// including some necessary dokeos files |
||||
require('../inc/global.inc.php'); |
||||
|
||||
// user permissions |
||||
api_protect_admin_script(); |
||||
|
||||
// breadcrumb |
||||
$interbreadcrumb[] = array ("url" => 'index.php', "name" => get_lang('PlatformAdmin')); |
||||
$interbreadcrumb[] = array ("url" => 'specific_fields.php', "name" => get_lang('SpecificSearchFields')); |
||||
|
||||
$libpath = api_get_path(LIBRARY_PATH); |
||||
include_once ($libpath.'specific_fields_manager.lib.php'); |
||||
require_once ($libpath.'formvalidator/FormValidator.class.php'); |
||||
|
||||
// Create an add-field box |
||||
$form = new FormValidator('add_field','post','','',null,false); |
||||
$renderer =& $form->defaultRenderer(); |
||||
$renderer->setElementTemplate('<span>{element}</span> '); |
||||
$form->addElement('static','search_advanced_link',null,'<a href="specific_fields_add.php">'.Display::return_icon('fieldadd.gif').get_lang('AddSpecificSearchField').'</a>'); |
||||
|
||||
|
||||
|
||||
// Create a sortable table with specific fields data |
||||
$column_show = array(1,1,1,1); |
||||
$column_order = array(3,2,1,4); |
||||
$extra_fields = get_specific_field_list(); |
||||
$number_of_extra_fields = count($extra_fields); |
||||
|
||||
$table = new SortableTableFromArrayConfig($extra_fields,2,50,'',$column_show,$column_order); |
||||
$table->set_header(0, '', false,null,'width="2%"', 'style="display:none"'); |
||||
$table->set_header(1, get_lang('Code'), TRUE, 'width="10%"'); |
||||
$table->set_header(2, get_lang('Name')); |
||||
$table->set_header(3, get_lang('Modify'),true,'width="10%"'); |
||||
$table->set_column_filter(3, 'edit_filter'); |
||||
|
||||
function edit_filter($id,$url_params,$row) |
||||
{ |
||||
global $charset; |
||||
$return = '<a href="specific_fields_add.php?action=edit&field_id='.$row[0].'">'.Display::return_icon('edit.gif',get_lang('Edit')).'</a>'; |
||||
$return .= ' <a href="'.api_get_self().'?action=delete&field_id='.$row[0].'" onclick="javascript:if(!confirm('."'".addslashes(htmlentities(get_lang("ConfirmYourChoice"),ENT_QUOTES,$charset))."'".')) return false;">'.Display::return_icon('delete.gif',get_lang('Delete')).'</a>'; |
||||
return $return; |
||||
} |
||||
|
||||
if ($_REQUEST['action'] == 'delete') { |
||||
delete_specific_field($_REQUEST['field_id']); |
||||
header('Location: specific_fields.php?message='.get_lang('FieldRemoved')); |
||||
} |
||||
|
||||
// Start output |
||||
|
||||
// Displaying the header |
||||
Display::display_header($nameTools); |
||||
echo '<div class="admin-tool-intro">'.get_lang('SpecificSearchFieldsIntro').'</div>'; |
||||
|
||||
if(!empty($_GET['message'])) |
||||
{ |
||||
Display::display_confirmation_message($_GET['message']); |
||||
} |
||||
|
||||
echo '<div class="actions">'; |
||||
$form->display(); |
||||
echo '</div>'; |
||||
|
||||
$table->display(); |
||||
|
||||
// Displaying the footer |
||||
Display::display_footer(); |
@ -0,0 +1,101 @@ |
||||
<?php |
||||
/* |
||||
============================================================================== |
||||
Dokeos - elearning and course management software |
||||
|
||||
Copyright (c) 2008 Dokeos SPRL |
||||
|
||||
For a full list of contributors, see "credits.txt". |
||||
The full license can be read in "license.txt". |
||||
|
||||
This program is free software; you can redistribute it and/or |
||||
modify it under the terms of the GNU General Public License |
||||
as published by the Free Software Foundation; either version 2 |
||||
of the License, or (at your option) any later version. |
||||
|
||||
See the GNU General Public License for more details. |
||||
|
||||
Contact: Dokeos, rue du Corbeau, 108, B-1030 Brussels, Belgium, info@dokeos.com |
||||
============================================================================== |
||||
*/ |
||||
/** |
||||
* Add form |
||||
* @package dokeos.admin |
||||
*/ |
||||
|
||||
$language_file[] = 'admin'; |
||||
// including necessary libraries |
||||
require ('../inc/global.inc.php'); |
||||
$libpath = api_get_path(LIBRARY_PATH); |
||||
include_once ($libpath.'specific_fields_manager.lib.php'); |
||||
require_once ($libpath.'formvalidator/FormValidator.class.php'); |
||||
|
||||
// section for the tabs |
||||
$this_section=SECTION_PLATFORM_ADMIN; |
||||
|
||||
// user permissions |
||||
api_protect_admin_script(); |
||||
|
||||
// Database table definitions |
||||
$table_admin = Database :: get_main_table(TABLE_MAIN_ADMIN); |
||||
$table_user = Database :: get_main_table(TABLE_MAIN_USER); |
||||
$table_uf = Database :: get_main_table(TABLE_MAIN_USER_FIELD); |
||||
$table_uf_opt = Database :: get_main_table(TABLE_MAIN_USER_FIELD_OPTIONS); |
||||
$table_uf_val = Database :: get_main_table(TABLE_MAIN_USER_FIELD_VALUES); |
||||
|
||||
$interbreadcrumb[] = array ('url' => 'index.php', 'name' => get_lang('PlatformAdmin')); |
||||
$interbreadcrumb[] = array ('url' => 'specific_fields.php', 'name' => get_lang('SpecificSearchFields')); |
||||
if ($_GET['action']<>'edit') |
||||
{ |
||||
$tool_name = get_lang('AddSpecificSearchField'); |
||||
} |
||||
else |
||||
{ |
||||
$tool_name = get_lang('EditSpecificSearchField'); |
||||
} |
||||
// Create the form |
||||
$form = new FormValidator('specific_fields_add'); |
||||
// Field variable name |
||||
$form->addElement('hidden','field_id',(int)$_REQUEST['field_id']); |
||||
$form->addElement('text','field_name',get_lang('FieldName')); |
||||
$form->applyFilter('field_name','html_filter'); |
||||
$form->applyFilter('field_name','trim'); |
||||
$form->addRule('field_name', get_lang('ThisFieldIsRequired'), 'required'); |
||||
$form->addRule('fieldname', get_lang('OnlyLettersAndNumbersAllowed'), 'username'); |
||||
$form->addRule('fieldname', '', 'maxlength',20); |
||||
|
||||
// Set default values (only not empty when editing) |
||||
$defaults = array(); |
||||
if (is_numeric($_REQUEST['field_id'])) |
||||
{ |
||||
$form_information = get_specific_field_list(array( 'id' => (int)$_GET['field_id'] )); |
||||
$defaults['field_name'] = $form_information[0]['name']; |
||||
} |
||||
$form->setDefaults($defaults); |
||||
// Submit button |
||||
$form->addElement('submit', 'submit', get_lang('Add')); |
||||
|
||||
// Validate form |
||||
if ($form->validate()) { |
||||
$field = $form->exportValues(); |
||||
$field_name = $field['field_name']; |
||||
if (is_numeric($field['field_id']) && $field['field_id']<>0 && !empty($field['field_id'])) |
||||
{ |
||||
edit_specific_field($field['field_id'],$field['field_name']); |
||||
$message = get_lang('FieldEdited'); |
||||
} |
||||
else |
||||
{ |
||||
$field_id = add_specific_field($field_name); |
||||
$message = get_lang('FieldAdded'); |
||||
} |
||||
header('Location: specific_fields.php?message='.$message); |
||||
//exit (); |
||||
} |
||||
|
||||
// Display form |
||||
Display::display_header($tool_name); |
||||
|
||||
$form->display(); |
||||
|
||||
Display::display_footer(); |
After Width: | Height: | Size: 707 B |
After Width: | Height: | Size: 712 B |
After Width: | Height: | Size: 612 B |
After Width: | Height: | Size: 667 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 585 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,48 @@ |
||||
.ac_results { |
||||
padding: 0px; |
||||
border: 1px solid black; |
||||
background-color: white; |
||||
overflow: hidden; |
||||
z-index: 99999; |
||||
} |
||||
|
||||
.ac_results ul { |
||||
width: 100%; |
||||
list-style-position: outside; |
||||
list-style: none; |
||||
padding: 0; |
||||
margin: 0; |
||||
} |
||||
|
||||
.ac_results li { |
||||
margin: 0px; |
||||
padding: 2px 5px; |
||||
cursor: default; |
||||
display: block; |
||||
/* |
||||
if width will be 100% horizontal scrollbar will apear |
||||
when scroll mode will be used |
||||
*/ |
||||
/*width: 100%;*/ |
||||
font: menu; |
||||
font-size: 12px; |
||||
/* |
||||
it is very important, if line-height not setted or setted |
||||
in relative units scroll will be broken in firefox |
||||
*/ |
||||
line-height: 16px; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.ac_loading { |
||||
background: white url('indicator.gif') right center no-repeat; |
||||
} |
||||
|
||||
.ac_odd { |
||||
background-color: #eee; |
||||
} |
||||
|
||||
.ac_over { |
||||
background-color: #0A246A; |
||||
color: white; |
||||
} |
@ -0,0 +1,759 @@ |
||||
/* |
||||
* Autocomplete - jQuery plugin 1.0.2 |
||||
* |
||||
* Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer |
||||
* |
||||
* Dual licensed under the MIT and GPL licenses: |
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
* |
||||
* Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $ |
||||
* |
||||
*/ |
||||
|
||||
;(function($) { |
||||
|
||||
$.fn.extend({ |
||||
autocomplete: function(urlOrData, options) { |
||||
var isUrl = typeof urlOrData == "string"; |
||||
options = $.extend({}, $.Autocompleter.defaults, { |
||||
url: isUrl ? urlOrData : null, |
||||
data: isUrl ? null : urlOrData, |
||||
delay: isUrl ? $.Autocompleter.defaults.delay : 10, |
||||
max: options && !options.scroll ? 10 : 150 |
||||
}, options); |
||||
|
||||
// if highlight is set to false, replace it with a do-nothing function
|
||||
options.highlight = options.highlight || function(value) { return value; }; |
||||
|
||||
// if the formatMatch option is not specified, then use formatItem for backwards compatibility
|
||||
options.formatMatch = options.formatMatch || options.formatItem; |
||||
|
||||
return this.each(function() { |
||||
new $.Autocompleter(this, options); |
||||
}); |
||||
}, |
||||
result: function(handler) { |
||||
return this.bind("result", handler); |
||||
}, |
||||
search: function(handler) { |
||||
return this.trigger("search", [handler]); |
||||
}, |
||||
flushCache: function() { |
||||
return this.trigger("flushCache"); |
||||
}, |
||||
setOptions: function(options){ |
||||
return this.trigger("setOptions", [options]); |
||||
}, |
||||
unautocomplete: function() { |
||||
return this.trigger("unautocomplete"); |
||||
} |
||||
}); |
||||
|
||||
$.Autocompleter = function(input, options) { |
||||
|
||||
var KEY = { |
||||
UP: 38, |
||||
DOWN: 40, |
||||
DEL: 46, |
||||
TAB: 9, |
||||
RETURN: 13, |
||||
ESC: 27, |
||||
COMMA: 188, |
||||
PAGEUP: 33, |
||||
PAGEDOWN: 34, |
||||
BACKSPACE: 8 |
||||
}; |
||||
|
||||
// Create $ object for input element
|
||||
var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); |
||||
|
||||
var timeout; |
||||
var previousValue = ""; |
||||
var cache = $.Autocompleter.Cache(options); |
||||
var hasFocus = 0; |
||||
var lastKeyPressCode; |
||||
var config = { |
||||
mouseDownOnSelect: false |
||||
}; |
||||
var select = $.Autocompleter.Select(options, input, selectCurrent, config); |
||||
|
||||
var blockSubmit; |
||||
|
||||
// prevent form submit in opera when selecting with return key
|
||||
$.browser.opera && $(input.form).bind("submit.autocomplete", function() { |
||||
if (blockSubmit) { |
||||
blockSubmit = false; |
||||
return false; |
||||
} |
||||
}); |
||||
|
||||
// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
|
||||
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { |
||||
// track last key pressed
|
||||
lastKeyPressCode = event.keyCode; |
||||
switch(event.keyCode) { |
||||
|
||||
case KEY.UP: |
||||
event.preventDefault(); |
||||
if ( select.visible() ) { |
||||
select.prev(); |
||||
} else { |
||||
onChange(0, true); |
||||
} |
||||
break; |
||||
|
||||
case KEY.DOWN: |
||||
event.preventDefault(); |
||||
if ( select.visible() ) { |
||||
select.next(); |
||||
} else { |
||||
onChange(0, true); |
||||
} |
||||
break; |
||||
|
||||
case KEY.PAGEUP: |
||||
event.preventDefault(); |
||||
if ( select.visible() ) { |
||||
select.pageUp(); |
||||
} else { |
||||
onChange(0, true); |
||||
} |
||||
break; |
||||
|
||||
case KEY.PAGEDOWN: |
||||
event.preventDefault(); |
||||
if ( select.visible() ) { |
||||
select.pageDown(); |
||||
} else { |
||||
onChange(0, true); |
||||
} |
||||
break; |
||||
|
||||
// matches also semicolon
|
||||
case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: |
||||
case KEY.TAB: |
||||
case KEY.RETURN: |
||||
if( selectCurrent() ) { |
||||
// stop default to prevent a form submit, Opera needs special handling
|
||||
event.preventDefault(); |
||||
blockSubmit = true; |
||||
return false; |
||||
} |
||||
break; |
||||
|
||||
case KEY.ESC: |
||||
select.hide(); |
||||
break; |
||||
|
||||
default: |
||||
clearTimeout(timeout); |
||||
timeout = setTimeout(onChange, options.delay); |
||||
break; |
||||
} |
||||
}).focus(function(){ |
||||
// track whether the field has focus, we shouldn't process any
|
||||
// results if the field no longer has focus
|
||||
hasFocus++; |
||||
}).blur(function() { |
||||
hasFocus = 0; |
||||
if (!config.mouseDownOnSelect) { |
||||
hideResults(); |
||||
} |
||||
}).click(function() { |
||||
// show select when clicking in a focused field
|
||||
if ( hasFocus++ > 1 && !select.visible() ) { |
||||
onChange(0, true); |
||||
} |
||||
}).bind("search", function() { |
||||
// TODO why not just specifying both arguments?
|
||||
var fn = (arguments.length > 1) ? arguments[1] : null; |
||||
function findValueCallback(q, data) { |
||||
var result; |
||||
if( data && data.length ) { |
||||
for (var i=0; i < data.length; i++) { |
||||
if( data[i].result.toLowerCase() == q.toLowerCase() ) { |
||||
result = data[i]; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
if( typeof fn == "function" ) fn(result); |
||||
else $input.trigger("result", result && [result.data, result.value]); |
||||
} |
||||
$.each(trimWords($input.val()), function(i, value) { |
||||
request(value, findValueCallback, findValueCallback); |
||||
}); |
||||
}).bind("flushCache", function() { |
||||
cache.flush(); |
||||
}).bind("setOptions", function() { |
||||
$.extend(options, arguments[1]); |
||||
// if we've updated the data, repopulate
|
||||
if ( "data" in arguments[1] ) |
||||
cache.populate(); |
||||
}).bind("unautocomplete", function() { |
||||
select.unbind(); |
||||
$input.unbind(); |
||||
$(input.form).unbind(".autocomplete"); |
||||
}); |
||||
|
||||
|
||||
function selectCurrent() { |
||||
var selected = select.selected(); |
||||
if( !selected ) |
||||
return false; |
||||
|
||||
var v = selected.result; |
||||
previousValue = v; |
||||
|
||||
if ( options.multiple ) { |
||||
var words = trimWords($input.val()); |
||||
if ( words.length > 1 ) { |
||||
v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v; |
||||
} |
||||
v += options.multipleSeparator; |
||||
} |
||||
|
||||
$input.val(v); |
||||
hideResultsNow(); |
||||
$input.trigger("result", [selected.data, selected.value]); |
||||
return true; |
||||
} |
||||
|
||||
function onChange(crap, skipPrevCheck) { |
||||
if( lastKeyPressCode == KEY.DEL ) { |
||||
select.hide(); |
||||
return; |
||||
} |
||||
|
||||
var currentValue = $input.val(); |
||||
|
||||
if ( !skipPrevCheck && currentValue == previousValue ) |
||||
return; |
||||
|
||||
previousValue = currentValue; |
||||
|
||||
currentValue = lastWord(currentValue); |
||||
if ( currentValue.length >= options.minChars) { |
||||
$input.addClass(options.loadingClass); |
||||
if (!options.matchCase) |
||||
currentValue = currentValue.toLowerCase(); |
||||
request(currentValue, receiveData, hideResultsNow); |
||||
} else { |
||||
stopLoading(); |
||||
select.hide(); |
||||
} |
||||
}; |
||||
|
||||
function trimWords(value) { |
||||
if ( !value ) { |
||||
return [""]; |
||||
} |
||||
var words = value.split( options.multipleSeparator ); |
||||
var result = []; |
||||
$.each(words, function(i, value) { |
||||
if ( $.trim(value) ) |
||||
result[i] = $.trim(value); |
||||
}); |
||||
return result; |
||||
} |
||||
|
||||
function lastWord(value) { |
||||
if ( !options.multiple ) |
||||
return value; |
||||
var words = trimWords(value); |
||||
return words[words.length - 1]; |
||||
} |
||||
|
||||
// fills in the input box w/the first match (assumed to be the best match)
|
||||
// q: the term entered
|
||||
// sValue: the first matching result
|
||||
function autoFill(q, sValue){ |
||||
// autofill in the complete box w/the first match as long as the user hasn't entered in more data
|
||||
// if the last user key pressed was backspace, don't autofill
|
||||
if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { |
||||
// fill in the value (keep the case the user has typed)
|
||||
$input.val($input.val() + sValue.substring(lastWord(previousValue).length)); |
||||
// select the portion of the value not typed by the user (so the next character will erase)
|
||||
$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length); |
||||
} |
||||
}; |
||||
|
||||
function hideResults() { |
||||
clearTimeout(timeout); |
||||
timeout = setTimeout(hideResultsNow, 200); |
||||
}; |
||||
|
||||
function hideResultsNow() { |
||||
var wasVisible = select.visible(); |
||||
select.hide(); |
||||
clearTimeout(timeout); |
||||
stopLoading(); |
||||
if (options.mustMatch) { |
||||
// call search and run callback
|
||||
$input.search( |
||||
function (result){ |
||||
// if no value found, clear the input box
|
||||
if( !result ) { |
||||
if (options.multiple) { |
||||
var words = trimWords($input.val()).slice(0, -1); |
||||
$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); |
||||
} |
||||
else |
||||
$input.val( "" ); |
||||
} |
||||
} |
||||
); |
||||
} |
||||
if (wasVisible) |
||||
// position cursor at end of input field
|
||||
$.Autocompleter.Selection(input, input.value.length, input.value.length); |
||||
}; |
||||
|
||||
function receiveData(q, data) { |
||||
if ( data && data.length && hasFocus ) { |
||||
stopLoading(); |
||||
select.display(data, q); |
||||
autoFill(q, data[0].value); |
||||
select.show(); |
||||
} else { |
||||
hideResultsNow(); |
||||
} |
||||
}; |
||||
|
||||
function request(term, success, failure) { |
||||
if (!options.matchCase) |
||||
term = term.toLowerCase(); |
||||
var data = cache.load(term); |
||||
// recieve the cached data
|
||||
if (data && data.length) { |
||||
success(term, data); |
||||
// if an AJAX url has been supplied, try loading the data now
|
||||
} else if( (typeof options.url == "string") && (options.url.length > 0) ){ |
||||
|
||||
var extraParams = { |
||||
timestamp: +new Date() |
||||
}; |
||||
$.each(options.extraParams, function(key, param) { |
||||
extraParams[key] = typeof param == "function" ? param() : param; |
||||
}); |
||||
|
||||
$.ajax({ |
||||
// try to leverage ajaxQueue plugin to abort previous requests
|
||||
mode: "abort", |
||||
// limit abortion to this input
|
||||
port: "autocomplete" + input.name, |
||||
dataType: options.dataType, |
||||
url: options.url, |
||||
data: $.extend({ |
||||
q: lastWord(term), |
||||
limit: options.max |
||||
}, extraParams), |
||||
success: function(data) { |
||||
var parsed = options.parse && options.parse(data) || parse(data); |
||||
cache.add(term, parsed); |
||||
success(term, parsed); |
||||
} |
||||
}); |
||||
} else { |
||||
// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
|
||||
select.emptyList(); |
||||
failure(term); |
||||
} |
||||
}; |
||||
|
||||
function parse(data) { |
||||
var parsed = []; |
||||
var rows = data.split("\n"); |
||||
for (var i=0; i < rows.length; i++) { |
||||
var row = $.trim(rows[i]); |
||||
if (row) { |
||||
row = row.split("|"); |
||||
parsed[parsed.length] = { |
||||
data: row, |
||||
value: row[0], |
||||
result: options.formatResult && options.formatResult(row, row[0]) || row[0] |
||||
}; |
||||
} |
||||
} |
||||
return parsed; |
||||
}; |
||||
|
||||
function stopLoading() { |
||||
$input.removeClass(options.loadingClass); |
||||
}; |
||||
|
||||
}; |
||||
|
||||
$.Autocompleter.defaults = { |
||||
inputClass: "ac_input", |
||||
resultsClass: "ac_results", |
||||
loadingClass: "ac_loading", |
||||
minChars: 1, |
||||
delay: 400, |
||||
matchCase: false, |
||||
matchSubset: true, |
||||
matchContains: false, |
||||
cacheLength: 10, |
||||
max: 100, |
||||
mustMatch: false, |
||||
extraParams: {}, |
||||
selectFirst: true, |
||||
formatItem: function(row) { return row[0]; }, |
||||
formatMatch: null, |
||||
autoFill: false, |
||||
width: 0, |
||||
multiple: false, |
||||
multipleSeparator: ", ", |
||||
highlight: function(value, term) { |
||||
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>"); |
||||
}, |
||||
scroll: true, |
||||
scrollHeight: 180 |
||||
}; |
||||
|
||||
$.Autocompleter.Cache = function(options) { |
||||
|
||||
var data = {}; |
||||
var length = 0; |
||||
|
||||
function matchSubset(s, sub) { |
||||
if (!options.matchCase)
|
||||
s = s.toLowerCase(); |
||||
var i = s.indexOf(sub); |
||||
if (i == -1) return false; |
||||
return i == 0 || options.matchContains; |
||||
}; |
||||
|
||||
function add(q, value) { |
||||
if (length > options.cacheLength){ |
||||
flush(); |
||||
} |
||||
if (!data[q]){
|
||||
length++; |
||||
} |
||||
data[q] = value; |
||||
} |
||||
|
||||
function populate(){ |
||||
if( !options.data ) return false; |
||||
// track the matches
|
||||
var stMatchSets = {}, |
||||
nullData = 0; |
||||
|
||||
// no url was specified, we need to adjust the cache length to make sure it fits the local data store
|
||||
if( !options.url ) options.cacheLength = 1; |
||||
|
||||
// track all options for minChars = 0
|
||||
stMatchSets[""] = []; |
||||
|
||||
// loop through the array and create a lookup structure
|
||||
for ( var i = 0, ol = options.data.length; i < ol; i++ ) { |
||||
var rawValue = options.data[i]; |
||||
// if rawValue is a string, make an array otherwise just reference the array
|
||||
rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; |
||||
|
||||
var value = options.formatMatch(rawValue, i+1, options.data.length); |
||||
if ( value === false ) |
||||
continue; |
||||
|
||||
var firstChar = value.charAt(0).toLowerCase(); |
||||
// if no lookup array for this character exists, look it up now
|
||||
if( !stMatchSets[firstChar] )
|
||||
stMatchSets[firstChar] = []; |
||||
|
||||
// if the match is a string
|
||||
var row = { |
||||
value: value, |
||||
data: rawValue, |
||||
result: options.formatResult && options.formatResult(rawValue) || value |
||||
}; |
||||
|
||||
// push the current match into the set list
|
||||
stMatchSets[firstChar].push(row); |
||||
|
||||
// keep track of minChars zero items
|
||||
if ( nullData++ < options.max ) { |
||||
stMatchSets[""].push(row); |
||||
} |
||||
}; |
||||
|
||||
// add the data items to the cache
|
||||
$.each(stMatchSets, function(i, value) { |
||||
// increase the cache size
|
||||
options.cacheLength++; |
||||
// add to the cache
|
||||
add(i, value); |
||||
}); |
||||
} |
||||
|
||||
// populate any existing data
|
||||
setTimeout(populate, 25); |
||||
|
||||
function flush(){ |
||||
data = {}; |
||||
length = 0; |
||||
} |
||||
|
||||
return { |
||||
flush: flush, |
||||
add: add, |
||||
populate: populate, |
||||
load: function(q) { |
||||
if (!options.cacheLength || !length) |
||||
return null; |
||||
/* |
||||
* if dealing w/local data and matchContains than we must make sure |
||||
* to loop through all the data collections looking for matches |
||||
*/ |
||||
if( !options.url && options.matchContains ){ |
||||
// track all matches
|
||||
var csub = []; |
||||
// loop through all the data grids for matches
|
||||
for( var k in data ){ |
||||
// don't search through the stMatchSets[""] (minChars: 0) cache
|
||||
// this prevents duplicates
|
||||
if( k.length > 0 ){ |
||||
var c = data[k]; |
||||
$.each(c, function(i, x) { |
||||
// if we've got a match, add it to the array
|
||||
if (matchSubset(x.value, q)) { |
||||
csub.push(x); |
||||
} |
||||
}); |
||||
} |
||||
}
|
||||
return csub; |
||||
} else
|
||||
// if the exact item exists, use it
|
||||
if (data[q]){ |
||||
return data[q]; |
||||
} else |
||||
if (options.matchSubset) { |
||||
for (var i = q.length - 1; i >= options.minChars; i--) { |
||||
var c = data[q.substr(0, i)]; |
||||
if (c) { |
||||
var csub = []; |
||||
$.each(c, function(i, x) { |
||||
if (matchSubset(x.value, q)) { |
||||
csub[csub.length] = x; |
||||
} |
||||
}); |
||||
return csub; |
||||
} |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
}; |
||||
}; |
||||
|
||||
$.Autocompleter.Select = function (options, input, select, config) { |
||||
var CLASSES = { |
||||
ACTIVE: "ac_over" |
||||
}; |
||||
|
||||
var listItems, |
||||
active = -1, |
||||
data, |
||||
term = "", |
||||
needsInit = true, |
||||
element, |
||||
list; |
||||
|
||||
// Create results
|
||||
function init() { |
||||
if (!needsInit) |
||||
return; |
||||
element = $("<div/>") |
||||
.hide() |
||||
.addClass(options.resultsClass) |
||||
.css("position", "absolute") |
||||
.appendTo(document.body); |
||||
|
||||
list = $("<ul/>").appendTo(element).mouseover( function(event) { |
||||
if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { |
||||
active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); |
||||
$(target(event)).addClass(CLASSES.ACTIVE);
|
||||
} |
||||
}).click(function(event) { |
||||
$(target(event)).addClass(CLASSES.ACTIVE); |
||||
select(); |
||||
// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
|
||||
input.focus(); |
||||
return false; |
||||
}).mousedown(function() { |
||||
config.mouseDownOnSelect = true; |
||||
}).mouseup(function() { |
||||
config.mouseDownOnSelect = false; |
||||
}); |
||||
|
||||
if( options.width > 0 ) |
||||
element.css("width", options.width); |
||||
|
||||
needsInit = false; |
||||
}
|
||||
|
||||
function target(event) { |
||||
var element = event.target; |
||||
while(element && element.tagName != "LI") |
||||
element = element.parentNode; |
||||
// more fun with IE, sometimes event.target is empty, just ignore it then
|
||||
if(!element) |
||||
return []; |
||||
return element; |
||||
} |
||||
|
||||
function moveSelect(step) { |
||||
listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE); |
||||
movePosition(step); |
||||
var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE); |
||||
if(options.scroll) { |
||||
var offset = 0; |
||||
listItems.slice(0, active).each(function() { |
||||
offset += this.offsetHeight; |
||||
}); |
||||
if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) { |
||||
list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight()); |
||||
} else if(offset < list.scrollTop()) { |
||||
list.scrollTop(offset); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
function movePosition(step) { |
||||
active += step; |
||||
if (active < 0) { |
||||
active = listItems.size() - 1; |
||||
} else if (active >= listItems.size()) { |
||||
active = 0; |
||||
} |
||||
} |
||||
|
||||
function limitNumberOfItems(available) { |
||||
return options.max && options.max < available |
||||
? options.max |
||||
: available; |
||||
} |
||||
|
||||
function fillList() { |
||||
list.empty(); |
||||
var max = limitNumberOfItems(data.length); |
||||
for (var i=0; i < max; i++) { |
||||
if (!data[i]) |
||||
continue; |
||||
var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term); |
||||
if ( formatted === false ) |
||||
continue; |
||||
var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0]; |
||||
$.data(li, "ac_data", data[i]); |
||||
} |
||||
listItems = list.find("li"); |
||||
if ( options.selectFirst ) { |
||||
listItems.slice(0, 1).addClass(CLASSES.ACTIVE); |
||||
active = 0; |
||||
} |
||||
// apply bgiframe if available
|
||||
if ( $.fn.bgiframe ) |
||||
list.bgiframe(); |
||||
} |
||||
|
||||
return { |
||||
display: function(d, q) { |
||||
init(); |
||||
data = d; |
||||
term = q; |
||||
fillList(); |
||||
}, |
||||
next: function() { |
||||
moveSelect(1); |
||||
}, |
||||
prev: function() { |
||||
moveSelect(-1); |
||||
}, |
||||
pageUp: function() { |
||||
if (active != 0 && active - 8 < 0) { |
||||
moveSelect( -active ); |
||||
} else { |
||||
moveSelect(-8); |
||||
} |
||||
}, |
||||
pageDown: function() { |
||||
if (active != listItems.size() - 1 && active + 8 > listItems.size()) { |
||||
moveSelect( listItems.size() - 1 - active ); |
||||
} else { |
||||
moveSelect(8); |
||||
} |
||||
}, |
||||
hide: function() { |
||||
element && element.hide(); |
||||
listItems && listItems.removeClass(CLASSES.ACTIVE); |
||||
active = -1; |
||||
}, |
||||
visible : function() { |
||||
return element && element.is(":visible"); |
||||
}, |
||||
current: function() { |
||||
return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]); |
||||
}, |
||||
show: function() { |
||||
var offset = $(input).offset(); |
||||
element.css({ |
||||
width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(), |
||||
top: offset.top + input.offsetHeight, |
||||
left: offset.left |
||||
}).show(); |
||||
if(options.scroll) { |
||||
list.scrollTop(0); |
||||
list.css({ |
||||
maxHeight: options.scrollHeight, |
||||
overflow: 'auto' |
||||
}); |
||||
|
||||
if($.browser.msie && typeof document.body.style.maxHeight === "undefined") { |
||||
var listHeight = 0; |
||||
listItems.each(function() { |
||||
listHeight += this.offsetHeight; |
||||
}); |
||||
var scrollbarsVisible = listHeight > options.scrollHeight; |
||||
list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight ); |
||||
if (!scrollbarsVisible) { |
||||
// IE doesn't recalculate width when scrollbar disappears
|
||||
listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) ); |
||||
} |
||||
} |
||||
|
||||
} |
||||
}, |
||||
selected: function() { |
||||
var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE); |
||||
return selected && selected.length && $.data(selected[0], "ac_data"); |
||||
}, |
||||
emptyList: function (){ |
||||
list && list.empty(); |
||||
}, |
||||
unbind: function() { |
||||
element && element.remove(); |
||||
} |
||||
}; |
||||
}; |
||||
|
||||
$.Autocompleter.Selection = function(field, start, end) { |
||||
if( field.createTextRange ){ |
||||
var selRange = field.createTextRange(); |
||||
selRange.collapse(true); |
||||
selRange.moveStart("character", start); |
||||
selRange.moveEnd("character", end); |
||||
selRange.select(); |
||||
} else if( field.setSelectionRange ){ |
||||
field.setSelectionRange(start, end); |
||||
} else { |
||||
if( field.selectionStart ){ |
||||
field.selectionStart = start; |
||||
field.selectionEnd = end; |
||||
} |
||||
} |
||||
field.focus(); |
||||
}; |
||||
|
||||
})(jQuery); |
@ -1,9 +1,87 @@ |
||||
<?php |
||||
include 'xapian/XapianIndexer.class.php'; |
||||
require_once dirname(__FILE__) . '/../../global.inc.php'; |
||||
include_once 'xapian/XapianIndexer.class.php'; |
||||
|
||||
/** |
||||
* Class wrapper |
||||
*/ |
||||
class DokeosIndexer 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 boolean False on error or nothing to do, true otherwise |
||||
*/ |
||||
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 = array(); |
||||
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 |
||||
*/ |
||||
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 = array(); |
||||
foreach($terms as $term) |
||||
{ |
||||
$prefix_terms[] = $term['value']; |
||||
} |
||||
return $prefix_terms; |
||||
} |
||||
|
||||
} |
||||
?> |
||||
|
||||
if (!function_exists('trim_value')) { |
||||
function trim_value(&$value) { |
||||
$value = trim($value); |
||||
} |
||||
} |
||||
|
||||
?> |
||||
|
@ -0,0 +1,117 @@ |
||||
<?php |
||||
include_once dirname(__FILE__) . '/../../../global.inc.php'; |
||||
require_once dirname(__FILE__) . '/search_processor.class.php'; |
||||
require_once dirname(__FILE__) . '/../IndexableChunk.class.php'; |
||||
|
||||
/** |
||||
* Process learning paths before pass it to search listing scripts |
||||
*/ |
||||
class learnpath_processor extends search_processor { |
||||
public $learnpaths = array(); |
||||
|
||||
function learnpath_processor($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 = array( |
||||
'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); |
||||
} |
||||
} |
||||
|
||||
public function process() { |
||||
$results = array(); |
||||
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_PATH) . 'main/newscorm/lp_controller.php?cidReq=%s&action=view&lp_id=%s'; |
||||
$url = sprintf($url, $courseid, $lp_id); |
||||
if ($search_show_unlinked_results) { |
||||
if (!$course_visible_for_user) { |
||||
$url = ''; |
||||
} |
||||
$results[] = array( |
||||
'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, |
||||
); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 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) { |
||||
$lpi_table = Database::get_course_table_from_code($course_id, TABLE_LP_ITEM); |
||||
$lp_table = Database::get_course_table_from_code($course_id, TABLE_LP_MAIN); |
||||
$doc_table = Database::get_course_table_from_code($course_id, TABLE_DOCUMENT); |
||||
|
||||
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 |
||||
WHERE $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.lp_id = $lp_id |
||||
AND $lpi_table.display_order = 1 |
||||
AND $lp_table.id = $lpi_table.lp_id |
||||
LIMIT 1"; |
||||
} |
||||
|
||||
$dk_result = api_sql_query ($sql); |
||||
|
||||
$path = ''; |
||||
$name = ''; |
||||
if ($row = Database::fetch_array ($dk_result)) { |
||||
// Get the image path |
||||
$img_location = api_get_path(WEB_COURSE_PATH).api_get_course_path($course_id)."/document/"; |
||||
$thumbnail_path = str_replace ('.png.html', '_thumb.png', $row['path']); |
||||
$big_img_path = str_replace ('.png.html', '.png', $row['path']); |
||||
$thumbnail = $img_location . $thumbnail_path; |
||||
$image = $img_location . $big_img_path; |
||||
$name = $row['name']; |
||||
} |
||||
|
||||
return array($thumbnail, $image, $name, $row['author']); |
||||
} |
||||
} |
||||
?> |
@ -0,0 +1,25 @@ |
||||
<?php |
||||
|
||||
/** |
||||
* 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. |
||||
*/ |
||||
abstract class search_processor { |
||||
/** |
||||
* Search engine api results |
||||
*/ |
||||
protected $rows = array(); |
||||
|
||||
/** |
||||
* Process the data sorted by the constructor |
||||
*/ |
||||
abstract protected function process(); |
||||
} |
||||
?> |
@ -1,7 +0,0 @@ |
||||
<?php |
||||
/* |
||||
* This file is included by the other xapian files, it only describes some |
||||
* constants and 'config'. |
||||
*/ |
||||
|
||||
?> |
@ -0,0 +1,226 @@ |
||||
<?php |
||||
/** |
||||
* Manage specific tools |
||||
*/ |
||||
|
||||
// Database table definitions |
||||
$table_sf = Database :: get_main_table(TABLE_MAIN_SPECIFIC_FIELD); |
||||
$table_sf_val = Database :: get_main_table(TABLE_MAIN_SPECIFIC_FIELD_VALUES); |
||||
|
||||
/** |
||||
* Add a specific field |
||||
* @param string $name specific field name |
||||
*/ |
||||
function add_specific_field($name) { |
||||
$table_sf = Database :: get_main_table(TABLE_MAIN_SPECIFIC_FIELD); |
||||
$name = trim($name); |
||||
if (empty($name)) { |
||||
return FALSE; |
||||
} |
||||
$sql = 'INSERT INTO %s(id, code, name) VALUES(NULL, \'%s\', \'%s\')'; |
||||
$_safe_name = Database::escape_string($name); |
||||
$_safe_code = substr($_safe_name,0,1); |
||||
$_safe_code = get_specific_field_code_from_name($_safe_code); |
||||
if ($_safe_code === false) { return false; } |
||||
$sql = sprintf($sql, $table_sf, $_safe_code, $_safe_name); |
||||
$result = api_sql_query($sql,__FILE__,__LINE__); |
||||
if ($result) { |
||||
return Database::get_last_insert_id(); |
||||
} |
||||
else { |
||||
return FALSE; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Delete a specific field |
||||
* @param int $id specific field id |
||||
*/ |
||||
function delete_specific_field($id) { |
||||
$table_sf = Database :: get_main_table(TABLE_MAIN_SPECIFIC_FIELD); |
||||
$id = (int)$id; |
||||
if (!is_numeric($id)) { |
||||
return FALSE; |
||||
} |
||||
$sql = 'DELETE FROM %s WHERE id=%s LIMIT 1'; |
||||
$sql = sprintf($sql, $table_sf, $id); |
||||
$result = api_sql_query($sql,__FILE__,__LINE__); |
||||
//TODO also delete the corresponding values |
||||
} |
||||
|
||||
/** |
||||
* Edit a specific field |
||||
* @param int $id specific field id |
||||
* @param string $name new field name |
||||
*/ |
||||
function edit_specific_field($id, $name) { |
||||
$table_sf = Database :: get_main_table(TABLE_MAIN_SPECIFIC_FIELD); |
||||
$id = (int)$id; |
||||
if (!is_numeric($id)) { |
||||
return FALSE; |
||||
} |
||||
$sql = 'UPDATE %s SET name = \'%s\' WHERE id = %s LIMIT 1'; |
||||
$sql = sprintf($sql, $table_sf, $name, $id); |
||||
$result = api_sql_query($sql,__FILE__,__LINE__); |
||||
} |
||||
|
||||
/** |
||||
* @param array $conditions a list of condition (exemple : status=>STUDENT) |
||||
* @param array $order_by a list of fields on which to sort |
||||
* @return array An array with all specific fields, at platform level |
||||
*/ |
||||
function get_specific_field_list($conditions = array(), $order_by = array()) { |
||||
$table_sf = Database :: get_main_table(TABLE_MAIN_SPECIFIC_FIELD); |
||||
$return_array = array(); |
||||
$sql = "SELECT * FROM $table_sf"; |
||||
if (count($conditions) > 0) { |
||||
$sql .= ' WHERE '; |
||||
$conditions_string_array = array(); |
||||
foreach ($conditions as $field => $value) { |
||||
$conditions_string_array[] = $field.' = '. $value; |
||||
} |
||||
$sql .= implode(' AND ', $conditions_string_array); |
||||
} |
||||
if (count($order_by) > 0) { |
||||
$sql .= ' ORDER BY '.implode(',',$order_by); |
||||
} |
||||
$sql_result = api_sql_query($sql,__FILE__,__LINE__); |
||||
while ($result = Database::fetch_array($sql_result)) { |
||||
$return_array[] = $result; |
||||
} |
||||
|
||||
return $return_array; |
||||
} |
||||
|
||||
/** |
||||
* @param array $conditions a list of condition (exemple : status=>STUDENT) |
||||
* @param array $order_by a list of fields on which sort |
||||
* @return array An array with all users of the platform. |
||||
*/ |
||||
function get_specific_field_values_list($conditions = array(), $order_by = array()) { |
||||
$table_sfv = Database :: get_main_table(TABLE_MAIN_SPECIFIC_FIELD_VALUES); |
||||
$return_array = array(); |
||||
$sql = "SELECT * FROM $table_sfv"; |
||||
if (count($conditions) > 0) { |
||||
$sql .= ' WHERE '; |
||||
$conditions_string_array = array(); |
||||
foreach ($conditions as $field => $value) { |
||||
$conditions_string_array[] = $field.' = '. $value; |
||||
} |
||||
$sql .= implode(' AND ', $conditions_string_array); |
||||
} |
||||
if (count($order_by) > 0) { |
||||
$sql .= ' ORDER BY '.implode(',',$order_by); |
||||
} |
||||
$sql_result = api_sql_query($sql,__FILE__,__LINE__); |
||||
while ($result = Database::fetch_array($sql_result)) { |
||||
$return_array[] = $result; |
||||
} |
||||
|
||||
return $return_array; |
||||
} |
||||
|
||||
/** |
||||
* @param char $prefix xapian prefix |
||||
* @param string $course_code |
||||
* @param string $tool_id Constant from mainapi.lib.php |
||||
* @param int $ref_id representative id inside one tool item |
||||
* @return array |
||||
*/ |
||||
function get_specific_field_values_list_by_prefix($prefix, $course_code, $tool_id, $ref_id) { |
||||
$table_sf = Database :: get_main_table(TABLE_MAIN_SPECIFIC_FIELD); |
||||
$table_sfv = Database :: get_main_table(TABLE_MAIN_SPECIFIC_FIELD_VALUES); |
||||
$sql = 'SELECT sfv.value FROM %s sf LEFT JOIN %s sfv ON sf.id = sfv.field_id' . |
||||
' WHERE sf.code = \'%s\' AND sfv.course_code = \'%s\' AND tool_id = \'%s\' AND sfv.ref_id = %s'; |
||||
$sql = sprintf($sql, $table_sf, $table_sfv, $prefix, $course_code, $tool_id, $ref_id); |
||||
$sql_result = api_sql_query($sql,__FILE__,__LINE__); |
||||
while ($result = Database::fetch_array($sql_result)) { |
||||
$return_array[] = $result; |
||||
} |
||||
return $return_array; |
||||
} |
||||
|
||||
/** |
||||
* Add a specific field value |
||||
* @param int $id_specific_field specific field id |
||||
* @param string $course_id course code |
||||
* @param string $tool_id tool id, from main.api.lib |
||||
* @param int $ref_id intern id inside specific tool table |
||||
* @param string $value specific field value |
||||
*/ |
||||
function add_specific_field_value($id_specific_field, $course_id, $tool_id, $ref_id, $value) { |
||||
$table_sf_values = Database :: get_main_table(TABLE_MAIN_SPECIFIC_FIELD_VALUES); |
||||
$value = trim($value); |
||||
if (empty($value)) { |
||||
return FALSE; |
||||
} |
||||
$sql = 'INSERT INTO %s(id, course_code, tool_id, ref_id, field_id, value) VALUES(NULL, \'%s\', \'%s\', %s, %s, \'%s\')'; |
||||
$sql = sprintf($sql, $table_sf_values, $course_id, $tool_id, $ref_id, $id_specific_field, Database::escape_string($value)); |
||||
$result = api_sql_query($sql,__FILE__,__LINE__); |
||||
if ($result) { |
||||
return Database::get_last_insert_id(); |
||||
} |
||||
else { |
||||
return FALSE; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Delete all values from a specific field id, course_id, ref_id and tool |
||||
* @param string $course_id course code |
||||
* @param int $id_specific_field specific field id |
||||
* @param string $tool_id tool id, from main.api.lib |
||||
* @param int $ref_id intern id inside specific tool table |
||||
*/ |
||||
function delete_all_specific_field_value($course_id, $id_specific_field, $tool_id, $ref_id) { |
||||
$table_sf_values = Database :: get_main_table(TABLE_MAIN_SPECIFIC_FIELD_VALUES); |
||||
$sql = 'DELETE FROM %s WHERE course_code = \'%s\' AND tool_id = \'%s\' AND ref_id = %s AND field_id = %s'; |
||||
$sql = sprintf($sql, $table_sf_values, $course_id, $tool_id, $ref_id, $id_specific_field); |
||||
$result = api_sql_query($sql,__FILE__,__LINE__); |
||||
} |
||||
|
||||
/** |
||||
* Delete all values from a specific item (course_id, tool_id and ref_id). |
||||
* To be used when deleting such item from Dokeos |
||||
* @param string Course code |
||||
* @param string Tool ID |
||||
* @param int Internal ID used in specific tool table |
||||
*/ |
||||
function delete_all_values_for_item($course_id, $tool_id, $ref_id) { |
||||
$table_sf_values = Database :: get_main_table(TABLE_MAIN_SPECIFIC_FIELD_VALUES); |
||||
$sql = 'DELETE FROM %s WHERE course_code = \'%s\' AND tool_id = \'%s\' AND ref_id = %s'; |
||||
$sql = sprintf($sql, $table_sf_values, $course_id, $tool_id, $ref_id); |
||||
$result = api_sql_query($sql,__FILE__,__LINE__); |
||||
} |
||||
|
||||
/** |
||||
* Generates a code (one-letter string) for a given field name |
||||
* Defaults to the first letter of the name, otherwise iterate through available |
||||
* letters |
||||
* @param string Name |
||||
* @return string One-letter code, upper-case |
||||
*/ |
||||
function get_specific_field_code_from_name($name) { |
||||
$list = array('A','B','D','E','F','G','H','I','J','K','L','M','N','P','Q','R','S','U','V','W','X','Y'); //Z is used internally by Xapian and we already use C,T & O for tool_id, course_id and normal tags |
||||
$table_sf = Database :: get_main_table(TABLE_MAIN_SPECIFIC_FIELD); |
||||
$sql = "SELECT code FROM $table_sf ORDER BY code"; |
||||
$res = api_sql_query($sql,__FILE__,__LINE__); |
||||
$code = strtoupper(substr($name,0,1)); |
||||
//if no code exists in DB, return current one |
||||
if (Database::num_rows($res)<1) { return $code;} |
||||
|
||||
$existing_list = array(); |
||||
while ($row = Database::fetch_array($res)) { |
||||
$existing_list[] = $row['code']; |
||||
} |
||||
//if the current code doesn't exist in DB, return current one |
||||
if (!in_array($code,$existing_list)) { return $code;} |
||||
|
||||
$idx = array_search($code,$list); |
||||
$c = count($list); |
||||
for ($i = $idx+1, $j=0 ; $j<$c ; $i++, $j++) { |
||||
if (!in_array($list[$i],$existing_list)) { return $idx[$i]; } |
||||
} |
||||
// all 26 codes are used |
||||
return false; |
||||
} |
@ -0,0 +1,102 @@ |
||||
<?php |
||||
/* |
||||
* Suggest words to search |
||||
*/ |
||||
require_once dirname(__FILE__) . '/../inc/global.inc.php'; |
||||
|
||||
function get_suggestions_from_search_engine($q) { |
||||
if (strlen($q)<2) { return null;} |
||||
global $charset; |
||||
$table_sfv = Database :: get_main_table(TABLE_MAIN_SPECIFIC_FIELD_VALUES); |
||||
$q = Database::escape_string($q); |
||||
$cid = api_get_course_id(); |
||||
$sql_add = ''; |
||||
if ($cid != -1) { |
||||
$sql_add = " AND course_code = '".$cid."' "; |
||||
} |
||||
$sql = "SELECT * FROM $table_sfv where value LIKE '%$q%'".$sql_add." ORDER BY course_code, tool_id, ref_id, field_id"; |
||||
$sql_result = api_sql_query($sql,__FILE__,__LINE__); |
||||
$data = array(); |
||||
$i = 0; |
||||
while ($row = Database::fetch_array($sql_result)) { |
||||
echo mb_convert_encoding($row['value'],'UTF-8',$charset)."| value\n"; |
||||
if ($i<20) { |
||||
$data[ $row['course_code'] ] [ $row['tool_id'] ] [ $row['ref_id'] ] = 1; |
||||
} |
||||
$i++; |
||||
} |
||||
// now that we have all the values corresponding to this search, we want to |
||||
// make sure we get all the associated values that could match this one |
||||
// initial value... |
||||
$more_sugg = array(); |
||||
foreach ($data as $cc => $course_id) { |
||||
foreach ($course_id as $ti => $item_tool_id) { |
||||
foreach ($item_tool_id as $ri => $item_ref_id) { |
||||
//natsort($item_ref_id); |
||||
$output = array(); |
||||
$field_val = array(); |
||||
$sql2 = "SELECT * FROM $table_sfv where course_code = '$cc' AND tool_id = '$ti' AND ref_id = '$ri' ORDER BY field_id"; |
||||
$res2 = api_sql_query($sql2,__FILE__,__LINE__); |
||||
// TODO this code doesn't manage multiple terms in one same field just yet (should duplicate results in this case) |
||||
$field_id = 0; |
||||
while ($row2 = Database::fetch_array($res2)) { |
||||
//TODO : this code is not perfect yet. It overrides the |
||||
// first match set, so having 1:Yannick,Julio;2:Rectum;3:LASER |
||||
// will actually never return: Yannick - Rectum - LASER |
||||
// because it is overwriteen by Julio - Rectum - LASER |
||||
// We should have recursivity here to avoid this problem! |
||||
//Store the new set of results (only one per combination |
||||
// of all fields) |
||||
$field_val[$row2['field_id']] = $row2['value']; |
||||
$current_field_val = ''; |
||||
foreach ($field_val as $id => $val) { |
||||
$current_field_val .= $val.' - '; |
||||
} |
||||
//Check whether we have a field repetition or not. Results |
||||
// have been ordered by field_id, so we should catch them |
||||
// all here |
||||
if ($field_id == $row2['field_id']) { |
||||
//We found the same field id twice, split the output |
||||
// array to allow for two sets of results (copy all |
||||
// existing array elements into copies and update the |
||||
// copies) eg. Yannick - Car - Driving in $output[1] |
||||
// will create a copy as Yannick - Car - Speed |
||||
// in $output[3] |
||||
$c = count($output); |
||||
for ($i=0;$i<$c; $i++) { |
||||
$output[($c+$i)] = $current_field_val; |
||||
} |
||||
} else { |
||||
//no identical field id, continue as usual |
||||
$c = count($output); |
||||
if ($c == 0) { |
||||
$output[] = $row2['value'].' - '; |
||||
} else { |
||||
foreach ($output as $i=>$out) { |
||||
//use the latest combination of fields |
||||
$output[$i] .= $row2['value'].' - '; |
||||
} |
||||
} |
||||
$field_id = $row2['field_id']; |
||||
} |
||||
} |
||||
foreach ($output as $i=>$out) { |
||||
if (stristr($out,$q) === false) {continue;} |
||||
$s = mb_convert_encoding(substr($out,0,-3),'UTF-8',$charset) . "| value\n"; |
||||
if (!in_array($s,$more_sugg)) { |
||||
$more_sugg[] = $s; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
foreach ($more_sugg as $sugg) { |
||||
echo $sugg; |
||||
} |
||||
} |
||||
|
||||
$q = strtolower($_GET["q"]); |
||||
if (!$q) return; |
||||
//echo $q . "| value\n"; |
||||
get_suggestions_from_search_engine($q); |
||||
?> |