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 |
<?php |
||||||
include 'xapian/XapianIndexer.class.php'; |
require_once dirname(__FILE__) . '/../../global.inc.php'; |
||||||
|
include_once 'xapian/XapianIndexer.class.php'; |
||||||
|
|
||||||
/** |
/** |
||||||
* Class wrapper |
* Class wrapper |
||||||
*/ |
*/ |
||||||
class DokeosIndexer extends XapianIndexer { |
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); |
||||||
|
?> |