[svn r17550] search: refactor search engine index and search interface and let learnpath follow it

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 added
skala
Marco Villegas 17 years ago
parent 692bd15d68
commit 15ef79fbb7
  1. 5
      main/admin/index.php
  2. 93
      main/admin/specific_fields.php
  3. 101
      main/admin/specific_fields_add.php
  4. BIN
      main/img/ButtonGallOff.png
  5. BIN
      main/img/ButtonGallOn.png
  6. BIN
      main/img/ButtonListOff.png
  7. BIN
      main/img/ButtonListOn.png
  8. BIN
      main/img/search-big-plus.gif
  9. BIN
      main/img/search-lense.gif
  10. BIN
      main/img/thesaurus.gif
  11. 4
      main/inc/lib/add_course.lib.inc.php
  12. 5
      main/inc/lib/database.lib.php
  13. BIN
      main/inc/lib/javascript/indicator.gif
  14. 48
      main/inc/lib/javascript/jquery.autocomplete.css
  15. 759
      main/inc/lib/javascript/jquery.autocomplete.js
  16. 1
      main/inc/lib/main_api.lib.php
  17. 80
      main/inc/lib/search/DokeosIndexer.class.php
  18. 56
      main/inc/lib/search/DokeosQuery.php
  19. 117
      main/inc/lib/search/IndexableChunk.class.php
  20. 400
      main/inc/lib/search/search_widget.php
  21. 117
      main/inc/lib/search/tool_processors/learnpath_processor.class.php
  22. 25
      main/inc/lib/search/tool_processors/search_processor.class.php
  23. 7
      main/inc/lib/search/xapian/XapianConsts.php
  24. 188
      main/inc/lib/search/xapian/XapianIndexer.class.php
  25. 109
      main/inc/lib/search/xapian/XapianQuery.php
  26. 226
      main/inc/lib/specific_fields_manager.lib.php
  27. 5
      main/lang/english/admin.inc.php
  28. 13
      main/lang/english/learnpath.inc.php
  29. 14
      main/lang/english/trad4all.inc.php
  30. 193
      main/newscorm/learnpath.class.php
  31. 80
      main/newscorm/learnpathItem.class.php
  32. 40
      main/newscorm/lp_controller.php
  33. 43
      main/newscorm/lp_edit.php
  34. 4
      main/newscorm/lp_list.php
  35. 221
      main/newscorm/lp_list_search.php
  36. 70
      main/newscorm/openoffice_presentation.class.php
  37. 53
      main/newscorm/openoffice_text_document.class.php
  38. 52
      main/newscorm/scorm.class.php
  39. 7
      main/search/index.php
  40. 102
      main/search/search_suggestions.php
  41. 12
      main/upload/form.scorm.php
  42. 17
      main/upload/upload.scorm.php
  43. 39
      main/upload/upload_ppt.php
  44. 26
      main/upload/upload_word.php

@ -1,4 +1,4 @@
<?php // $Id: index.php 17478 2008-12-29 20:13:08Z cvargas1 $ <?php // $Id: index.php 17550 2009-01-06 18:56:28Z marvil07 $
/* /*
============================================================================== ==============================================================================
Dokeos - elearning and course management software Dokeos - elearning and course management software
@ -116,6 +116,9 @@ $keyword_url = Security::remove_XSS((empty($_GET['keyword'])?'':$_GET['keyword']
<li><a href="course_category.php"><?php echo get_lang("AdminCategories"); ?></a></li> <li><a href="course_category.php"><?php echo get_lang("AdminCategories"); ?></a></li>
<li><a href="subscribe_user2course.php"><?php echo get_lang('AddUsersToACourse'); ?></a></li> <li><a href="subscribe_user2course.php"><?php echo get_lang('AddUsersToACourse'); ?></a></li>
<li><a href="course_user_import.php"><?php echo get_lang('AddUsersToACourse').' CSV'; ?></a></li> <li><a href="course_user_import.php"><?php echo get_lang('AddUsersToACourse').' CSV'; ?></a></li>
<?php if (api_get_setting('search_enabled')=='true') { ?>
<li><a href="specific_fields.php"><?php echo get_lang('SpecificSearchFields'); ?></a></li>
<?php } ?>
</ul> </ul>
</div> </div>

@ -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();

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -1980,6 +1980,10 @@ function fill_Db_course($courseDbName, $courseRepository, $language,$default_doc
} }
} }
if (api_get_setting('search_enabled')=='true') {
api_sql_query("INSERT INTO `" . $tbl_course_homepage . "` VALUES (NULL, '" . TOOL_SEARCH. "','search/','info.gif','".string2binary(api_get_setting('course_create_active_tools', 'enable_search')) . "','0','search.gif','NO','_self','authoring')", __FILE__, __LINE__);
}
// Smartblogs (Kevin Van Den Haute :: kevin@develop-it.be) // Smartblogs (Kevin Van Den Haute :: kevin@develop-it.be)
$sql = "INSERT INTO `" . $tbl_course_homepage . "` VALUES (NULL,'" . TOOL_BLOGS . "','blog/blog_admin.php','blog_admin.gif','" . string2binary(api_get_setting('course_create_active_tools', 'blogs')) . "','1','squaregrey.gif','NO','_self','admin')"; $sql = "INSERT INTO `" . $tbl_course_homepage . "` VALUES (NULL,'" . TOOL_BLOGS . "','blog/blog_admin.php','blog_admin.gif','" . string2binary(api_get_setting('course_create_active_tools', 'blogs')) . "','1','squaregrey.gif','NO','_self','admin')";
api_sql_query($sql, __FILE__, __LINE__); api_sql_query($sql, __FILE__, __LINE__);

@ -1,4 +1,4 @@
<?php // $Id: database.lib.php 17539 2009-01-05 19:50:33Z marvil07 $ <?php // $Id: database.lib.php 17550 2009-01-06 18:56:28Z marvil07 $
/* See license terms in /dokeos_license.txt */ /* See license terms in /dokeos_license.txt */
/** /**
============================================================================== ==============================================================================
@ -53,6 +53,9 @@ define('TABLE_MAIN_GRADEBOOK_SCORE_DISPLAY','gradebook_score_display');
define('TABLE_MAIN_USER_FIELD', 'user_field'); define('TABLE_MAIN_USER_FIELD', 'user_field');
define('TABLE_MAIN_USER_FIELD_OPTIONS', 'user_field_options'); define('TABLE_MAIN_USER_FIELD_OPTIONS', 'user_field_options');
define('TABLE_MAIN_USER_FIELD_VALUES', 'user_field_values'); define('TABLE_MAIN_USER_FIELD_VALUES', 'user_field_values');
define('TABLE_MAIN_SPECIFIC_FIELD', 'specific_field');
define('TABLE_MAIN_SPECIFIC_FIELD_VALUES', 'specific_field_values');
define('TABLE_MAIN_SEARCH_ENGINE_REF', 'search_engine_ref');
define('TABLE_MAIN_ACCESS_URL', 'access_url'); define('TABLE_MAIN_ACCESS_URL', 'access_url');
define('TABLE_MAIN_SYSTEM_CALENDAR', 'sys_calendar'); define('TABLE_MAIN_SYSTEM_CALENDAR', 'sys_calendar');

Binary file not shown.

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);

@ -109,6 +109,7 @@ define('TOOL_HOTPOTATOES', 'hotpotatoes');
define('TOOL_CALENDAR_EVENT', 'calendar_event'); define('TOOL_CALENDAR_EVENT', 'calendar_event');
define('TOOL_LINK', 'link'); define('TOOL_LINK', 'link');
define('TOOL_COURSE_DESCRIPTION', 'course_description'); define('TOOL_COURSE_DESCRIPTION', 'course_description');
define('TOOL_SEARCH', 'search');
define('TOOL_LEARNPATH', 'learnpath'); define('TOOL_LEARNPATH', 'learnpath');
define('TOOL_ANNOUNCEMENT', 'announcement'); define('TOOL_ANNOUNCEMENT', 'announcement');
define('TOOL_FORUM', 'forum'); define('TOOL_FORUM', 'forum');

@ -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);
}
}
?> ?>

@ -11,10 +11,25 @@ require 'xapian/XapianQuery.php';
* @param string $query_string The search string * @param string $query_string The search string
* @param int $offset Offset to the first item to retrieve. Optional * @param int $offset Offset to the first item to retrieve. Optional
* @param int lenght Number of items to retrieve. Optional * @param int lenght Number of items to retrieve. Optional
* @param array extra Extra queries to join with. Optional
* @return array * @return array
*/ */
function dokeos_query_query($query_string, $offset=0, $length=10) { function dokeos_query_query($query_string, $offset=0, $length=10, $extra=NULL) {
return xapian_query($query_string, NULL, $offset, $length); list($count, $results) = xapian_query($query_string, NULL, $offset, $length, $extra);
return dokeos_preprocess_results($results);
}
function dokeos_query_simple_query($query_string, $offset=0, $length=10, $extra=NULL) {
return xapian_query($query_string, NULL, $offset, $length, $extra);
}
/**
* Wrapper for getting boolean queries
*
* @param string $query_string The term string
*/
function dokeos_get_boolean_query($term) {
return xapian_get_boolean_query($term);
} }
/** /**
@ -36,4 +51,41 @@ function dokeos_query_get_tags($count=100) {
function dokeos_query_tags_for_doc($doc) { function dokeos_query_tags_for_doc($doc) {
return xapian_get_doc_terms($doc); return xapian_get_doc_terms($doc);
} }
/**
* Preprocess all results depending on the toolid
*/
function dokeos_preprocess_results($results) {
// group by toolid
$results_by_tool = array();
foreach ($results as $key => $row) {
$results_by_tool[$row['toolid']][] = $row;
}
$processed_results = array();
foreach ($results_by_tool as $toolid => $rows) {
$tool_processor_class = $toolid .'_processor';
$tool_processor_path = api_get_path(LIBRARY_PATH) .'search/tool_processors/'. $tool_processor_class .'.class.php';
if (file_exists($tool_processor_path)) {
require_once($tool_processor_path);
$tool_processor = new $tool_processor_class($rows);
$processed_results = $tool_processor->process();
}
}
return array(count($processed_results), $processed_results);
}
/**
* Wrapper for join xapian queries
*
* @param XapianQuery|array $query1
* @param XapianQuery|array $query2
* @param string $op
* @return XapianQuery query joined
*/
function dokeos_join_queries($query1, $query2=NULL, $op='or') {
return xapian_join_queries($query1, $query2, $op);
}
?> ?>

@ -1,15 +1,21 @@
<?php <?php
abstract class _IndexableChunk // some constants to avoid serialize string keys on serialized data array
{ define('SE_COURSE_ID', 0);
define('SE_TOOL_ID', 1);
define('SE_DATA', 2);
define('SE_USER', 3);
/* int */ // in some cases we need top differenciate xapian documents of the same tool
protected $id; define('SE_DOCTYPE_EXERCISE_EXERCISE', 0);
define('SE_DOCTYPE_EXERCISE_QUESTION', 1);
/* boolean */ // xapian prefixes
public $parent; define('XAPIAN_PREFIX_COURSEID','C');
define('XAPIAN_PREFIX_TAG','T');
define('XAPIAN_PREFIX_TOOLID','O');
/* int */ abstract class _IndexableChunk
public $parentId; {
/* struct (array) /* struct (array)
* { * {
@ -20,6 +26,16 @@ abstract class _IndexableChunk
*/ */
public $data; public $data;
/**
* array (
* 'SE_COURSE_ID' => string <- course id from course table on main db
* 'SE_TOOL_ID' => string <- tool id from mainapi lib constants
* 'SE_DATA' => mixed <- extra information, depends on SE_TOOL_ID
* 'SE_USER' => id <- user id from user table in main db
* )
*/
public $xapian_data;
/** /**
* array( * array(
* name => string * name => string
@ -28,12 +44,12 @@ abstract class _IndexableChunk
*/ */
public $terms; public $terms;
/** /**
* Add a value to the indexed item * Add a value to the indexed item
* @param string Key * @param string Key
* @param string Value * @param string Value
* @return void * @return void
*/ */
function addValue($key, $value) { function addValue($key, $value) {
$this->data[$key] = $value; $this->data[$key] = $value;
} }
@ -43,60 +59,26 @@ abstract class _IndexableChunk
* @param string Term * @param string Term
* @param string Flag (one character) * @param string Flag (one character)
*/ */
function addTerm($term, $flag) { public function addTerm($term, $flag) {
global $charset;
if (strlen($flag) == 1) { if (strlen($flag) == 1) {
$this->terms[] = array('name' => $term, 'flag' => $flag); $this->terms[] = array('name' => mb_convert_encoding(stripslashes($term),'UTF-8',$charset), 'flag' => $flag);
} }
} }
/** /**
* Get the ID from an indexed item. In case data are in an array, get the second item of the 'ids' element of the array * Class constructor. Just generates an empty 'data' array attribute
* @return integer ID */
*/
function getId() {
$id = -1;
if (is_array($this->data)) {
$ids = explode(':', $this->data['ids']);
/* we need at least course_id and document_id, else it's broken */
if (count($ids)) {
$id = $ids[1];
}
}
return $id;
}
/**
* Sets the parent of the current indexed item
* @param mixed A parent object
* @return void
*/
function setParent($parent) {
if (is_a($parent, 'IndexableChunk')) {
$this->parentId = $parent->getId();
$this->parent = False;
} else {
$this->parentId = -1;
$this->parent = True;
}
}
/**
* Class constructor. Just generates an empty 'data' array attribute
*/
function __construct() { function __construct() {
$this->data = array(); $this->data = array();
} }
/** /**
* Class desctructor. Unsets attributes. * Class desctructor. Unsets attributes.
*/ */
function __destruct() { function __destruct() {
unset($this->data); unset($this->data);
unset($this->terms); unset($this->terms);
unset($this->parent);
} }
} }
@ -105,6 +87,27 @@ abstract class _IndexableChunk
*/ */
class IndexableChunk extends _IndexableChunk class IndexableChunk extends _IndexableChunk
{ {
/**
* Let add tags terms
*/
public function addTagTerm($term) {
$this->addTerm($term, XAPIAN_PREFIX_TAG);
}
/**
* Let add course id term
*/
public function addCourseId($course_id) {
$this->addTerm($course_id, XAPIAN_PREFIX_COURSEID);
}
/**
* Let add tool id term
*/
public function addToolId($tool_id) {
$this->addTerm($tool_id, XAPIAN_PREFIX_TOOLID);
}
} }
?> ?>

@ -1,4 +1,17 @@
<?php <?php
$icons_for_search_terms['Author']=Display::return_icon('search_author.gif');
$icons_for_search_terms['Technology']=Display::return_icon('search_technology.gif');
$icons_for_search_terms['Body part']=Display::return_icon('search_bodyparts2.gif');
/**
* Search widget. Shows the search screen contents.
* @package dokeos.search
*/
require_once dirname(__FILE__) . '/IndexableChunk.class.php';
require_once api_get_path(LIBRARY_PATH).'/specific_fields_manager.lib.php';
/** /**
* Add some required CSS and JS to html's head. * Add some required CSS and JS to html's head.
* *
@ -13,7 +26,7 @@ function search_widget_prepare(&$htmlHeadXtra) {
.tags { .tags {
display: block; display: block;
margin-top: 20px; margin-top: 20px;
width: 70%; width: 90%;
} }
.tag { .tag {
float: left; float: left;
@ -25,36 +38,121 @@ function search_widget_prepare(&$htmlHeadXtra) {
border: 1px solid #ddd; border: 1px solid #ddd;
} }
.tag:hover { .tag:hover {
background: #ddd; background: gray;
cursor: pointer; color:white;
cursor:pointer;
/* font-weight:bold;*/
} }
.lighttagcolor { .lighttagcolor {
background: #ddd; background: gray;
color:white;
/* font-weight:bold;*/
}
.sf-select-multiple {
width: 14em;
margin: 0 1em 0 1em;
}
.sf-select-multiple-title {
font-weight: bold;
margin-left: 1em;
font-size: 130%;
}
#submit {
background-image: url(\'../img/search-lense.gif\');
background-repeat: no-repeat;
background-position: 0px -1px;
padding-left:18px;
} }
.lighttagcolor:hover { .lower-submit {
background: #fff; float:right;
margin: 0 0.9em 0 0.5em;
}
#tags-clean {
float: right;
}
.sf-select-splitter {
margin-top: 4em;
}
.search-links-box {
background-color: #ddd;
border: 1px solid #888;
padding: 1em;
-moz-border-radius: 0.8em;
}
.search-help-box {
border: 1px solid #888;
padding: 0 1em 1em 1em;
-moz-border-radius: 0.8em;
float: right;
color: #888;
margin-right: 5%;
width: 300px;
} }
</style>'; </style>';
$htmlHeadXtra[] = ' $htmlHeadXtra[] = '
<script src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.js" type="text/javascript"></script>'; <script src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.js" type="text/javascript"></script>';
$htmlHeadXtra[] ='
<script type="text/javascript" src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.autocomplete.js"></script>
<link rel="stylesheet" type="text/css" href="'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.autocomplete.css" />';
$htmlHeadXtra[] = " $htmlHeadXtra[] = "
<script type=\"text/javascript\"> <script type=\"text/javascript\">
var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
$(document).ready(function() { $(document).ready(function() {
$('#dokeos_search').submit(function (e) { $('#dokeos_search').submit(function (e) {
var tags = String(); var tags = String();
$('.lighttagcolor').each(function (b, a) { $('.lighttagcolor').each(function (b, a) {
tags = tags.concat(a.id+','); // tags = tags.concat(a.id +',');
}); tags += $(this).attr('title') + jQuery.trim($(this).html().replace(".'"\n"'.",'')) + ',';
$('#tag_holder').val(tags); });
return true; $('#tag_holder').val(tags);
return true;
});
/*$('#tags').hide();*/
$('a#tags-toggle').click(function() {
$('#tags').toggle(150);
return false;
});
$('#tags-clean').click(function() {
$('span#key_wrapper').html('');
// clear multiple select
$('select option:selected').each(function () {
$(this).attr('selected', '');
}); });
$('.lighttagcolor').removeClass('lighttagcolor');
return false;
});
$('#query').autocomplete('search_suggestions.php', {
multiple: false,
selectFirst: false,
mustMatch: false,
autoFill: false
});
}); });
</script>"; </script>";
} }
/**
* Trim tags off first XAPIAN_PREFIX_TAG only
*/
function trim_tag($tag)
{
if(substr($tag,0,1)==XAPIAN_PREFIX_TAG)
{
return substr($tag,1);
}
return $tag;
}
/** /**
* Show the search widget * Show the search widget
* TODO: reorganize an clean this to reuse code
* *
* The form will post to lp_controller.php by default, you can pass a value to * The form will post to lp_controller.php by default, you can pass a value to
* $action to use a custom action. * $action to use a custom action.
@ -65,53 +163,269 @@ function search_widget_prepare(&$htmlHeadXtra) {
* lp_controller.php * lp_controller.php
*/ */
function search_widget_show($action="lp_controller.php") { function search_widget_show($action="lp_controller.php") {
global $charset, $icons_for_search_terms;
require_once api_get_path(LIBRARY_PATH).'/search/DokeosQuery.php'; require_once api_get_path(LIBRARY_PATH).'/search/DokeosQuery.php';
$dktags = dokeos_query_get_tags(); $sf_terms = array();
$specific_fields = get_specific_field_list();
if ( ($cid=api_get_course_id()) != -1 ) {
// with cid
$course_filter = dokeos_get_boolean_query(XAPIAN_PREFIX_COURSEID . $cid);
$dktags = dokeos_query_simple_query('',0,1000,array($course_filter));
$dktags_org = $dktags;
$temp = array();
foreach($dktags[1] as $obj){
$temp = array_merge($obj['tags'], $temp);
}
$dktags = $temp;
unset($temp);
//prepare specific fields names (and also get possible URL param names)
$url_params = array();
foreach ($specific_fields as $specific_field) {
$temp = array();
foreach($dktags_org[1] as $obj) {
$temp = array_merge($obj['sf-'.$specific_field['code']], $temp);
}
$sf_terms[] = $temp;
$url_params[] = 'sf_'.$specific_field['code'];
unset($temp);
}
//var_dump($sf_terms);
}
else {
//without cid
$dktags = xapian_get_all_terms(1000, XAPIAN_PREFIX_TAG);
//prepare specific fields names (and also get possible URL param names)
$url_params = array();
foreach ($specific_fields as $specific_field) {
$temp = array();
//get Xapian terms for a specific term prefix, in ISO, apparently
$sf_terms[] = xapian_get_all_terms(1000, $specific_field['code']);
$url_params[] = 'sf_'.$specific_field['code'];
unset($temp);
}
//var_dump($sf_terms);
}
//check if URL params are defined (to see if we show the thesaurus or not)
$show_thesaurus = false;
foreach ($url_params as $param) {
if (is_array($_REQUEST[$param])) {
foreach ($_REQUEST[$param] as $term) {
if (!empty($term)) { $show_thesaurus = true; }
}
}
}
$post_tags = array(); $post_tags = array();
if (isset($_REQUEST['tags'])) { if (isset($_REQUEST['tags'])) {
$filter = TRUE; $filter = TRUE;
$post_tags = explode(',', $_REQUEST['tags']); $post_tags = explode(',', stripslashes($_REQUEST['tags']));
}
//sorting the array of tags and keywords
foreach ($dktags as $key => $value) {
$temp[trim(trim_tag(stripslashes(mb_convert_encoding($value['name'],$charset,'UTF-8'))))] = $key;
}
$temp = array_flip($temp);
unset($dktags);
natcasesort($temp);
$dktags = $temp;
$i = 0;
foreach ($temp as $key) {
if (in_array($key, $post_tags)==true && !empty($key)) {
$tags_list .= '<span id="t_'.md5($key).'">'.(($i==0)?'':', ').$key.'</span>';
$i++;
}
}
//echo '<div class="search-help-box"><h3>'.get_lang('ThesaurusHelpTitle').'</h3>'.get_lang('ThesaurusHelpComment').'</div>';
echo '<h2>'.get_lang('LectureLibrary').'</h2>';
//echo '<span class="tool-intro">'.get_lang('LectureLibraryIntro').'</span><br /><br />';
//echo '<span class="tool-intro">'.get_lang('ThesaurusHelpComment').'</span><br /><br />';
if (!empty($_SESSION['_gid'])) {
Display::display_introduction_section(TOOL_SEARCH.$_SESSION['_gid'],'left');
} else {
Display::display_introduction_section(TOOL_SEARCH,'left');
}
$op = 'or';
if (!empty($_REQUEST['operator']) && in_array($op,array('or','and'))) {
$op = Security::remove_XSS($_REQUEST['operator']);
} }
?> ?>
<form id="dokeos_search" action="<?php echo $action ?>" <form id="dokeos_search" action="<?php echo $action.'?mode='.htmlentities($_GET['mode']) ?>"
method="get"> method="get">
<input type="hidden" name="action" value="search"/> <input type="hidden" name="action" value="search"/>
<input type="text" name="query" size="40" /> <input type="text" id="query" name="query" size="40" />
<input type="submit" id="submit" value="<?php echo get_lang("Search") ?>" /> <input type="submit" id="submit" value="<?php echo get_lang("Search") ?>" />
<br/> <!--span id="keywords" style="font-size:12px;font-weight:bold"><?php echo get_lang("Keywords") ?>:</span-->
<h2><?php echo get_lang("Tags") ?></h2> <span id="key_wrapper"><?php echo $tags_list ?></span>
<input type="hidden" name="tags" id="tag_holder" /> <input type="hidden" name="tags" id="tag_holder" />
<div class="tags"> <br /><br />
<?php
echo '<span class="search-links-box">';
echo Display::return_icon('thesaurus.gif',get_lang('ShowTags'),array('style'=>'margin-bottom:-6px;')) .' <a id="tags-toggle" href="#">'. get_lang('ShowTags') .'</a>';
echo '&nbsp;';
//$specific_fields = get_specific_field_list();
//$icon = Display::return_icon('thesaurus.gif',get_lang('ShowTags'),array('style'=>'margin-bottom:-6px;'));
// foreach ($specific_fields as $specific_field) {
// echo '<a id="tags-toggle-sf-'. $specific_field['code'] .'" href="#">'. $icon .' '. $specific_field['name'] .'</a>&nbsp;';
// }
//echo Display::return_icon('eraser.gif',get_lang('CleanTags'),array('style'=>'margin-bottom:-6px;')).' <a id="tags-clean" href="#">'. get_lang('CleanTags') .'</a>';
echo '</span>'."\n";
echo '<div id="tags" class="tags" style="display:'.($show_thesaurus==true?'block':'none').';">'."\n";
/*
foreach ($dktags as $tagged)
{
//$tag = trim(trim($tagged, 'T '));
$tag = trim($tagged); //strimming of XAPIAN_PREFIX_TAG has already been done
$color = "";
if (empty($tag)) continue;
if ($filter) {
if (array_search($tag, $post_tags) !== FALSE)
$color = "lighttagcolor";
}
$word =htmlspecialchars($tag,ENT_QUOTES,$charset);
$tag = md5($tag);
?>
<span title="<?php echo XAPIAN_PREFIX_TAG ?>" class="tag <?php echo $color?>" id="<?php echo $tag ?>">
<?php echo $word ?></span>
<script type="text/javascript">
$('#<?php echo $tag ?>').click(function waaa (e) {
total_list = $('span#key_wrapper').children(":first");
count_total_list = total_list.length;
if ( $('.lighttagcolor').size() < 5) {
if ($('#t_<?php echo $tag ?>').length>0){
$('#t_<?php echo $tag ?>').remove();
} else {
if (count_total_list == 0) {
$('#key_wrapper').append('<span id="t_<?php echo $tag ?>"><?php echo XAPIAN_PREFIX_TAG . $word ?></span>');
} else {
$('#key_wrapper').append('<span id="t_<?php echo $tag ?>">, <?php echo XAPIAN_PREFIX_TAG . $word ?></span>');
}
}
$('#<?php echo $tag ?>').toggleClass('lighttagcolor');
} else {
$('#<?php echo $tag ?>').removeClass('lighttagcolor');
$('#t_<?php echo $tag ?>').remove();
}
});
</script>
<?php <?php
foreach ($dktags as $tag) } //*/
{
$tag = trim($tag['name'], 'T '); // Process each prefix type term
$tag = str_replace(' ', '_', $tag); echo '<table><tr>';
$color = ""; $i = 0;
if ($filter) { $max = count($sf_terms);
if (array_search($tag, $post_tags) !== FALSE) foreach ($sf_terms as $sf_term_array) {
$color = "lighttagcolor"; //$multiple_select = '<div style="float:left;">';
} $multiple_select = '';
?> //sorting the array of tags and keywords
<span class="tag <?php echo $color?>" id="<?php echo $tag ?>"> if ($i>0) {
<?php echo $tag ?></span> //print "+" image
<script type="text/javascript">
$('#<?php echo $tag ?>').click(function waaa (e) { $multiple_select .= '<td><img class="sf-select-splitter" src="../img/search-big-plus.gif" alt="plus-sign-decoration"/></td>';
if ( $('.lighttagcolor').size() < 3) { }
$('#<?php echo $tag ?>').toggleClass('lighttagcolor'); $temp = array();
foreach ($sf_term_array as $key => $value) {
//$temp[trim(stripslashes(mb_convert_encoding($value['name'],$charset,'UTF-8')))] = $key;
$temp[trim(stripslashes($value['name']))] = $key;
}
$temp = array_flip($temp);
unset($sf_term_array);
natcasesort($temp);
$sf_term_array = $temp;
// $temp = array_merge($obj['sf-'.$specific_field['code']], $temp);
//var_dump($sf_term_array);
$sf_copy = $sf_term_array;
$one_element = array_shift($sf_copy);
//we took the Xapian results in the same order as the specific fields
//came, so using the specific fields index, we are able to recover
//each field's name
$multiple_select .= '<td><label class="sf-select-multiple-title" for="sf_'. substr($one_element, 0, 1) .'[]">'.$icons_for_search_terms[$specific_fields[$i]['name']].' '.$specific_fields[$i]['name'].'</label><br />';
$multiple_select .= '<select multiple="multiple" size="7" class="sf-select-multiple" name="sf_'. substr($one_element, 0, 1) .'[]">';
foreach ($sf_term_array as $tagged)
{
$tag = substr($tagged, 1);
$prefix = substr($tagged, 0, 1);
$color = "";
if (empty($tag)) continue;
if ($filter) {
if (array_search($tag, $post_tags) !== FALSE)
$color = "lighttagcolor";
}
$word =htmlspecialchars($tag,ENT_QUOTES,$charset);
$tag = md5($tag);
/*
?>
<span title="<?php echo $prefix ?>" class="tag <?php echo $color?>" id="<?php echo $tag ?>">
<?php echo $word ?></span>
<script type="text/javascript">
$('#<?php echo $tag ?>').click(function waaa (e) {
total_list = $('span#key_wrapper').children(":first");
count_total_list_<?php echo $prefix ?> = total_list.length;
if ( $('.lighttagcolor').size() < 5) {
if ($('#<?php echo $prefix .'_'. $tag ?>').length>0){
$('#<?php echo $prefix .'_'. $tag ?>').remove();
} else {
if (count_total_list_<?php echo $prefix ?> == 0) {
$('#key_wrapper').append('<span title="<?php echo $prefix ?>" id="<?php echo $prefix .'_'. $tag ?>"><?php echo $prefix . $word ?></span>');
} else { } else {
$('#<?php echo $tag ?>').removeClass('lighttagcolor'); $('#key_wrapper').append('<span title="<?php echo $prefix ?>" id="<?php echo $prefix .'_'. $tag ?>">, <?php echo $prefix . $word ?></span>');
} }
}); }
</script> $('#<?php echo $tag ?>').toggleClass('lighttagcolor');
<?php } else {
$('#<?php echo $tag ?>').removeClass('lighttagcolor');
$('#<?php echo $prefix .'_'. $tag ?>').remove();
}
});
</script>
<?php
//*/
$selected = '';
$tag_mark = substr($tagged,0,1);
$tagged_clean = substr($tagged,1);
if (isset($_REQUEST['sf_'.$tag_mark]) && in_array($tagged_clean,$_REQUEST['sf_'.$tag_mark])) {
$selected = 'selected="selected"';
}
$multiple_select .= '<option value="'. $word .'" '.$selected.'>'. $word .'</option>';
} }
?> $multiple_select .= '</select>';
//if (($i+1) == $max) {
// $multiple_select .= '<br /><input type="submit" id="tags-clean" href="#" value="'. get_lang('CleanTags') .'" /><input class="lower-submit" type="submit" value="'.get_lang('Validate').'" />';
//}
//$multiple_select .= '</div>';
$multiple_select .= '</td>';
print $multiple_select;
$i++;
}
echo '</tr><tr>';
echo '<td colspan="'.((($i-1)*2)-1).'" align="right" style="padding-right:0.9em;">';
echo get_lang('CombineSearchWith').':';
echo '<input type="radio" class="search-operator" name="operator" value="or" '.($op=='or'?'checked="checked"':'').'>'.strtoupper(get_lang('Or')).'</input>';
echo '<input type="radio" class="search-operator" name="operator" value="and" '.($op=='and'?'checked="checked"':'').'>'.strtoupper(get_lang('And')).'</input>';
echo '</td><td></td><td><br /><input class="lower-submit" type="submit" value="'.get_lang('Validate').'" /><input type="submit" id="tags-clean" value="'. get_lang('CleanTags') .'" /></td>';
?>
</tr></table>
<div style="clear:both;"></div>
</div> </div>
</form> </form>
<br style="clear: both;"/> <br style="clear: both;"/>
<?php <?php
} }
?>

@ -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'.
*/
?>

@ -1,5 +1,6 @@
<?php <?php
require 'xapian.php'; require_once 'xapian.php';
require_once dirname(__FILE__) . '/../IndexableChunk.class.php';
/** /**
* Abstract helper class * Abstract helper class
@ -7,7 +8,6 @@ require 'xapian.php';
abstract class XapianIndexer { abstract class XapianIndexer {
/* XapianWritableDatabase */ /* XapianWritableDatabase */
protected $db; protected $db;
protected $parents;
/* IndexableChunk[] */ /* IndexableChunk[] */
protected $chunks; protected $chunks;
/* XapianTermGenerator */ /* XapianTermGenerator */
@ -15,13 +15,13 @@ abstract class XapianIndexer {
/* XapianStem */ /* XapianStem */
public $stemmer; public $stemmer;
/** /**
* Generates a list of languages Xapian manages * Generates a list of languages Xapian manages
* *
* This method enables the definition of more matches between * This method enables the definition of more matches between
* Dokeos languages and Xapian languages (through hardcoding) * Dokeos languages and Xapian languages (through hardcoding)
* @return array Array of languages codes -> Xapian languages * @return array Array of languages codes -> Xapian languages
*/ */
public final function xapian_languages() { public final function xapian_languages() {
/* http://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html */ /* http://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html */
return array( return array(
@ -46,10 +46,12 @@ abstract class XapianIndexer {
); );
} }
/** /**
* Connect to the database, and create it if it doesn't exist * Connect to the database, and create it if it doesn't exist
*/ */
function connectDb($path=NULL, $dbMode=NULL, $lang='english') { function connectDb($path=NULL, $dbMode=NULL, $lang='english') {
if ($this->db != NULL)
return $this->db;
if ($dbMode == NULL) if ($dbMode == NULL)
$dbMode = Xapian::DB_CREATE_OR_OPEN; $dbMode = Xapian::DB_CREATE_OR_OPEN;
@ -77,32 +79,25 @@ abstract class XapianIndexer {
/** /**
* Simple getter for the db attribute * Simple getter for the db attribute
* @return object The db attribute * @return object The db attribute
*/ */
function getDb() { function getDb() {
return $this->db; return $this->db;
} }
/** /**
* Add this chunk to the chunk array attribute * Add this chunk to the chunk array attribute
* @param string Chunk of text * @param string Chunk of text
* @return void * @return void
*/ */
function addChunk($chunk) { function addChunk($chunk) {
/*
if ($chunk->parent) {
$this->parents[] = $chunk;
} else {
$this->chunks[] = $chunk;
}
*/
$this->chunks[] = $chunk; $this->chunks[] = $chunk;
} }
/** /**
* Actually index the current data * Actually index the current data
* *
* @return integer New Xapian document ID or NULL upon failure * @return integer New Xapian document ID or NULL upon failure
*/ */
function index() { function index() {
try { try {
@ -115,25 +110,14 @@ abstract class XapianIndexer {
$doc->add_term($term['flag'] . $term['name'], 1); $doc->add_term($term['flag'] . $term['name'], 1);
} }
/* free-form ignoring ids, indexes title and content */ // free-form index all data array (title, content, etc)
foreach ($chunk->data as $key => $value) { foreach ($chunk->data as $key => $value) {
//if text is empty, we don't index (because it triggers a Xapian error) $this->indexer->index_text($value, 1);
if ($key != 'ids' && !empty($value))
$this->indexer->index_text($value, 1);
} }
/* Hard-coded approach */ $doc->set_data($chunk->xapian_data, 1);
/*
if (array_key_exists ('title', $chunk->data))
$this->indexer->index_text($chunk->data['title'], 1);
*/
$doc->set_data($chunk->data['ids'], 1);
$id = $chunk->getId();
if ($id < 0)
return NULL;
$did = $this->db->replace_document($id, $doc); $did = $this->db->add_document($doc);
//write to disk //write to disk
$this->db->flush(); $this->db->flush();
@ -152,13 +136,43 @@ abstract class XapianIndexer {
* Get a specific document from xapian db * Get a specific document from xapian db
* *
* @param int did Xapian::docid * @param int did Xapian::docid
* @return XapianDocument * @return mixed XapianDocument, or false on error
*/ */
function get_document($did) { function get_document($did) {
if ($path == NULL) { if ($this->db == NULL) {
$this->connectDb(); $this->connectDb();
} }
return $this->db->get_document($did); try {
$docid = $this->db->get_document($did);
}
catch (Exception $e) {
//Display::display_error_message($e->getMessage());
return false;
}
return $docid;
}
/**
* Get document data on a xapian document
*
* @param XapianDocument $doc xapian document to push into the db
* @return mixed xapian document data or FALSE if error
*/
function get_document_data($doc) {
if ($this->db == NULL) {
$this->connectDb();
}
try {
if (!is_a($doc, 'XapianDocument')) {
return FALSE;
}
$doc_data = $doc->get_data();
return $doc_data;
}
catch (Exception $e) {
//Display::display_error_message($e->getMessage());
return false;
}
} }
/** /**
@ -166,9 +180,11 @@ abstract class XapianIndexer {
* *
* @param int did Xapian::docid * @param int did Xapian::docid
* @param array terms New terms of the document * @param array terms New terms of the document
* @return boolean false on error
*/ */
function update_terms($did, $terms, $prefix='T') { function update_terms($did, $terms, $prefix=XAPIAN_PREFIX_TAG) {
$doc = $this->get_document($did); $doc = $this->get_document($did);
if($doc===false){return false;}
$doc->clear_terms(); $doc->clear_terms();
foreach ($terms as $term) { foreach ($terms as $term) {
//add directly //add directly
@ -176,6 +192,7 @@ abstract class XapianIndexer {
} }
$this->db->replace_document($did, $doc); $this->db->replace_document($did, $doc);
$this->db->flush(); $this->db->flush();
return true;
} }
/** /**
@ -184,16 +201,85 @@ abstract class XapianIndexer {
* @param int did Xapian::docid * @param int did Xapian::docid
*/ */
function remove_document($did) { function remove_document($did) {
if ($path == NULL) { if ($this->db == NULL) {
$this->connectDb(); $this->connectDb();
} }
$this->db->delete_document($did); if (is_numeric($did) && $did>0) {
$this->db->flush(); $doc = $this->get_document($did);
if ($doc !== FALSE) {
$this->db->delete_document($did);
$this->db->flush();
}
}
} }
/** /**
* Class contructor * Adds a term to the document specified
*/ *
* @param string $term The term to add
* @param XapianDocument $doc The xapian document where to add the term
* @return mixed XapianDocument, or false on error
*/
function add_term_to_doc($term, $doc) {
if (!is_a($doc,'XapianDocument')) {
return FALSE;
}
try {
$doc->add_term($term);
}
catch (Exception $e) {
Display::display_error_message($e->getMessage());
return 1;
}
}
/**
* Remove a term from the document specified
*
* @param string $term The term to add
* @param XapianDocument $doc The xapian document where to add the term
* @return mixed XapianDocument, or false on error
*/
function remove_term_from_doc($term, $doc) {
if (!is_a($doc,'XapianDocument')) {
return FALSE;
}
try {
$doc->remove_term($term);
}
catch (Exception $e) {
Display::display_error_message($e->getMessage());
return 1;
}
}
/**
* Replace a document in the actual db
*
* @param XapianDocument $doc xapian document to push into the db
* @param Xapian::docid $did xapian document id of the document to replace
*/
function replace_document($doc, $did) {
if (!is_a($doc,'XapianDocument')) {
return FALSE;
}
if ($this->db == NULL) {
$this->connectDb();
}
try {
$this->getDb()->replace_document((int)$did, $doc);
$this->getDb()->flush();
}
catch (Exception $e) {
Display::display_error_message($e->getMessage());
return 1;
}
}
/**
* Class contructor
*/
function __construct() { function __construct() {
$this->db = NULL; $this->db = NULL;
$this->stemmer = NULL; $this->stemmer = NULL;

@ -1,5 +1,8 @@
<?php <?php
require_once 'xapian.php'; require_once 'xapian.php';
require_once dirname(__FILE__) . '/../IndexableChunk.class.php';
//TODO: think another way without including specific fields here
require_once api_get_path(LIBRARY_PATH).'/specific_fields_manager.lib.php';
define('XAPIAN_DB', api_get_path(SYS_PATH).'searchdb/'); define('XAPIAN_DB', api_get_path(SYS_PATH).'searchdb/');
@ -27,32 +30,39 @@ function xapian_query($query_string, $db = NULL, $start = 0, $length = 10,
$db = new XapianDatabase(XAPIAN_DB); $db = new XapianDatabase(XAPIAN_DB);
} }
$enquire = new XapianEnquire($db); // Build subqueries from $extra array. Now only used by tags search filter on search widget
$query_parser = new XapianQueryParser(); $subqueries = array();
$stemmer = new XapianStem("english"); foreach ($extra as $subquery) {
$query_parser->set_stemmer($stemmer); if (!empty($subquery)) {
$query_parser->set_database($db); $subqueries[] = new XapianQuery($subquery);
$query_parser->set_stemming_strategy(XapianQueryParser::STEM_SOME);
$query_parser->add_boolean_prefix('filetype', 'F');
$query_parser->add_boolean_prefix('tag', 'T');
$query_parser->add_boolean_prefix('courseid', 'C');
$query = $query_parser->parse_query($query_string);
// Build subqueries from $extra array.
foreach ($extra as $subq) {
if (!empty($subq)) {
/* TODO: review if we want to use this constructor
* deprecated in C: http://xapian.org/docs/apidoc/html/classXapian_1_1Query.html#f85d155b99f1f2007fe75ffc7a8bd51e
* maybe use: Query (Query::op op_, const Query &left, const Query &right) ?
*/
$subquery = new XapianQuery(XapianQuery::OP_OR, $subq);
$query = new XapianQuery(XapianQuery::OP_AND, array($subquery, $query));
} }
} }
$query = NULL;
$enquire = new XapianEnquire($db);
if (!empty($query_string)) {
$query_parser = new XapianQueryParser();
//TODO: choose stemmer
$stemmer = new XapianStem("english");
$query_parser->set_stemmer($stemmer);
$query_parser->set_database($db);
$query_parser->set_stemming_strategy(XapianQueryParser::STEM_SOME);
$query_parser->add_boolean_prefix('tag', XAPIAN_PREFIX_TAG);
$query_parser->add_boolean_prefix('courseid', XAPIAN_PREFIX_COURSEID);
$query_parser->add_boolean_prefix('toolid', XAPIAN_PREFIX_TOOLID);
$query = $query_parser->parse_query($query_string);
$query = new XapianQuery(XapianQuery::OP_AND, array_merge($subqueries, array($query)));
}
else {
$query = new XapianQuery(XapianQuery::OP_OR, $subqueries);
}
$enquire->set_query($query); $enquire->set_query($query);
$matches = $enquire->get_mset((int)$start, (int)$length); $matches = $enquire->get_mset((int)$start, (int)$length);
$specific_fields = get_specific_field_list();
$results = array(); $results = array();
$i = $matches->begin(); $i = $matches->begin();
$count = 0; $count = 0;
@ -60,9 +70,21 @@ function xapian_query($query_string, $db = NULL, $start = 0, $length = 10,
$count++; $count++;
$document = $i->get_document(); $document = $i->get_document();
if (is_object($document)) { if (is_object($document)) {
$results[$count]->ids = ($document->get_data()); // process one item terms
$results[$count]->score = ($i->get_percent()); $courseid_terms = xapian_get_doc_terms($document, XAPIAN_PREFIX_COURSEID);
$results[$count]->terms = xapian_get_doc_terms($document); $results[$count]['courseid'] = substr($courseid_terms[0]['name'], 1);
$toolid_terms = xapian_get_doc_terms($document, XAPIAN_PREFIX_TOOLID);
$results[$count]['toolid'] = substr($toolid_terms[0]['name'], 1);
// process each specific field prefix
foreach ($specific_fields as $specific_field) {
$results[$count]['sf-'.$specific_field['code']] = xapian_get_doc_terms($document, $specific_field['code']);
}
// rest of data
$results[$count]['xapian_data'] = unserialize($document->get_data());
$results[$count]['score'] = ($i->get_percent());
$results[$count]['tags'] = xapian_get_doc_terms($document);
} }
$i->next(); $i->next();
} }
@ -90,6 +112,12 @@ function xapian_query($query_string, $db = NULL, $start = 0, $length = 10,
} }
} }
/**
* build a boolean query
*/
function xapian_get_boolean_query($term) {
return new XapianQuery($term);
}
/** /**
* Retrieve a list db terms * Retrieve a list db terms
* *
@ -98,7 +126,7 @@ function xapian_query($query_string, $db = NULL, $start = 0, $length = 10,
* @param XapianDatabase $db Xapian database to connect * @param XapianDatabase $db Xapian database to connect
* @return array * @return array
*/ */
function xapian_get_all_terms($count=0, $prefix='T', $db=NULL) { function xapian_get_all_terms($count=0, $prefix=XAPIAN_PREFIX_TAG, $db=NULL) {
try { try {
if (!is_object($db)) { if (!is_object($db)) {
$db = new XapianDatabase(XAPIAN_DB); $db = new XapianDatabase(XAPIAN_DB);
@ -134,7 +162,7 @@ function xapian_get_all_terms($count=0, $prefix='T', $db=NULL) {
* @param XapianDocument document searched * @param XapianDocument document searched
* @return array * @return array
*/ */
function xapian_get_doc_terms($doc=NULL, $prefix='T') { function xapian_get_doc_terms($doc=NULL, $prefix=XAPIAN_PREFIX_TAG) {
try { try {
if (!is_a($doc, 'XapianDocument')) { if (!is_a($doc, 'XapianDocument')) {
return; return;
@ -161,4 +189,35 @@ function xapian_get_doc_terms($doc=NULL, $prefix='T') {
return NULL; return NULL;
} }
} }
/**
* Join xapian queries
*
* @param XapianQuery|array $query1
* @param XapianQuery|array $query2
* @param string $op
* @return XapianQuery query joined
*/
function xapian_join_queries($query1, $query2=NULL, $op='or') {
// let decide how to join, avoiding include xapian.php outside
switch ($op) {
case 'or': $op = XapianQuery::OP_OR; break;
case 'and': $op = XapianQuery::OP_AND; break;
default: $op = XapianQuery::OP_OR; break;
}
// review parameters to decide how to join
if (!is_array($query1)) {
$query1 = array($query1);
}
if (is_null($query2)) {
// join an array of queries with $op
return new XapianQuery($op, $query1);
}
if (!is_array($query2)) {
$query2 = array($query2);
}
return new XapianQuery($op, array_merge($query1, $query2));
}
?> ?>

@ -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;
}

@ -853,4 +853,9 @@ $ThereIsNotStillAResponsible = "No HR manager available";
$GlobalAgenda = "Global agenda"; $GlobalAgenda = "Global agenda";
$AdvancedfilemanagerTitle = "Advanced file manager for wysiwyg editor"; $AdvancedfilemanagerTitle = "Advanced file manager for wysiwyg editor";
$AdvancedfilemanagerComment = "Enable advanced file manager for WYSIWYG editor? This will add a considerable amount of additional options to the file manager that opens in a pop-up window when uploading files to the server."; $AdvancedfilemanagerComment = "Enable advanced file manager for WYSIWYG editor? This will add a considerable amount of additional options to the file manager that opens in a pop-up window when uploading files to the server.";
$SpecificSearchFields = "Specific search fields";
$AddSpecificSearchField = "Add a specific search field";
$EditSpecificSearchField = "Edit a specific search field";
$FieldName = "Field name";
$SpecificSearchFieldsIntro = "Adding a specific search field will enable the indexation of extra fields together with documents contents. This will add fields to the import and edition pages of the lectures tool.";
?> ?>

@ -212,4 +212,17 @@ $UplAlreadyExists = "File already exists";
$UnknownPackageFormat = "Unknown package format"; $UnknownPackageFormat = "Unknown package format";
$UplUnableToSaveFile = "Unable to save file"; $UplUnableToSaveFile = "Unable to save file";
$UnknownPackageFormat = "The format of this package could not be recognized. Please check this is a valid package."; $UnknownPackageFormat = "The format of this package could not be recognized. Please check this is a valid package.";
$GoToThisLearningPath = "Watch lecture";
//$LectureLibrary = "";
$ImageWillResizeMsg = "If necesarry, the image will be resized to fit the screen";
$SearchFeatureTerms = "Keywords for search engine";
$SaveLPSettings = "Save course settings";
$EditLPSettings = "Course settings";
$UploadMp3audio = 'Upload MP3 audio';
$UpdateAllAudioFragments = 'Update all audio fragments';
$Audio = 'Audio';
$RemoveAudio = 'Remove audio file';
$SaveAudio = 'Save audio files';
$LeaveEmptyToKeepCurrentFile = 'If you want to keep the current file you can leave the input field empty';
//$Lectures = "";
?> ?>

@ -630,4 +630,18 @@ $langNameOfLang['trad_chinese'] = "traditional chinese";
$langNameOfLang['turkce'] = "turkish"; $langNameOfLang['turkce'] = "turkish";
$langNameOfLang['ukrainian'] = "ukrainian"; $langNameOfLang['ukrainian'] = "ukrainian";
$langNameOfLang['yoruba'] = "yoruba"; $langNameOfLang['yoruba'] = "yoruba";
//$GoToLearningPath = "";
//$LectureLibrary = "";
//$ImagePreview= "";
$ShowTags = "Show/hide thesaurus";
$EraseTags = "Erase thesaurus";
$CleanTags = "Cancel";
$Keywords = "Keywords";
$IntroductionTextDeleted = "The introduction text has been deleted.";
//$LectureLibraryIntro = "";
$ThesaurusHelpTitle = "Thesaurus help";
$ThesaurusHelpComment = "Select keywords in one or more fields and click the \"Search\" button.<br /><br />To select more than one keyword in a field, use Ctrl+click.";
$Validate = "Validate";
$CombineSearchWith = "Combine with";
$SearchFeatureNotEnabledComment = "The full-text search feature is not enabled in Dokeos. Please contact the Dokeos administrator.";
?> ?>

@ -856,6 +856,10 @@ class learnpath {
$this->update_display_order();//updates the display order of all lps $this->update_display_order();//updates the display order of all lps
api_item_property_update(api_get_course_info(),TOOL_LEARNPATH,$this->lp_id,'delete',api_get_user_id()); api_item_property_update(api_get_course_info(),TOOL_LEARNPATH,$this->lp_id,'delete',api_get_user_id());
//TODO: also delete items and item-views //TODO: also delete items and item-views
if (api_get_setting('search_enabled') == 'true') {
require_once(api_get_path(LIBRARY_PATH) .'specific_fields_manager.lib.php');
$r = delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
}
} }
/** /**
@ -919,9 +923,19 @@ class learnpath {
$res_all = api_sql_query($sql_all,__FILE__,__LINE__); $res_all = api_sql_query($sql_all,__FILE__,__LINE__);
// remove from search engine if enabled // remove from search engine if enabled
if (api_get_setting('search_enabled') == 'true') { if (api_get_setting('search_enabled') == 'true') {
$tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
$sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d LIMIT 1';
$sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
$res = api_sql_query($sql, __FILE__, __LINE__);
if (Database::num_rows($res) > 0) {
$row2 = Database::fetch_array($res);
require_once(api_get_path(LIBRARY_PATH) .'search/DokeosIndexer.class.php'); require_once(api_get_path(LIBRARY_PATH) .'search/DokeosIndexer.class.php');
$di = new DokeosIndexer(); $di = new DokeosIndexer();
$di->remove_document($row['search_did']); $di->remove_document((int)$row2['search_did']);
}
$sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d LIMIT 1';
$sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
api_sql_query($sql, __FILE__, __LINE__);
} }
} }
@ -934,10 +948,10 @@ class learnpath {
* @param string Item description * @param string Item description
* @param string Prerequisites (optional) * @param string Prerequisites (optional)
* @param string Indexing terms (optional) * @param string Indexing terms (optional)
* @param array The array resulting of the $_FILES[mp3] element
* @return boolean True on success, false on error * @return boolean True on success, false on error
*/ */
function edit_item($id, $parent, $previous, $title, $description, $prerequisites=0, $terms=NULL) function edit_item($id, $parent, $previous, $title, $description, $prerequisites=0, $audio=NULL) {
{
if($this->debug > 0){error_log('New LP - In learnpath::edit_item()', 0);} if($this->debug > 0){error_log('New LP - In learnpath::edit_item()', 0);}
if(empty($id) or ($id != strval(intval($id))) or empty($title)){ return false; } if(empty($id) or ($id != strval(intval($id))) or empty($title)){ return false; }
@ -951,16 +965,17 @@ class learnpath {
$res_select = api_sql_query($sql_select, __FILE__, __LINE__); $res_select = api_sql_query($sql_select, __FILE__, __LINE__);
$row_select = Database::fetch_array($res_select); $row_select = Database::fetch_array($res_select);
$terms_update_sql=''; $audio_update_sql = '';
if (!is_null($terms)) { if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error']===0) {
//TODO: validate csv string //upload file in documents
$terms_update_sql = ", terms = '". $this->escape_string(htmlentities($terms)) . "'"; $pi = pathinfo($audio['name']);
if ($pi['extension'] == 'mp3') {
// save it to search engine $c_det = api_get_course_info($this->cc);
if (api_get_setting('search_enabled') == 'true') { $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
require_once(api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php'); $path = handle_uploaded_document($c_det,$audio,$bp,'/audio',api_get_user_id(),0,null,'',0,'rename',false,0);
$di = new DokeosIndexer(); $path = substr($path,7);
$di->update_terms($row_select['search_did'], explode(',', $terms)); //update reference in lp_item - audio path is the path from inside de document/audio/ dir
$audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
} }
} }
@ -976,7 +991,7 @@ class learnpath {
title = '" . $this->escape_string(htmlentities($title)) . "', title = '" . $this->escape_string(htmlentities($title)) . "',
prerequisite = '".$prerequisites."', prerequisite = '".$prerequisites."',
description = '" . $this->escape_string(htmlentities($description)) . "' description = '" . $this->escape_string(htmlentities($description)) . "'
". $terms_update_sql . " ". $audio_update_sql . "
WHERE id = " . $id; WHERE id = " . $id;
$res_update = api_sql_query($sql_update, __FILE__, __LINE__); $res_update = api_sql_query($sql_update, __FILE__, __LINE__);
} }
@ -1094,7 +1109,7 @@ class learnpath {
previous_item_id = " . $previous . ", previous_item_id = " . $previous . ",
next_item_id = " . $new_next . ", next_item_id = " . $new_next . ",
display_order = " . $new_order . " display_order = " . $new_order . "
". $terms_update_sql . " ". $audio_update_sql . "
WHERE id = " . $id; WHERE id = " . $id;
$res_update_next = api_sql_query($sql_update, __FILE__, __LINE__); $res_update_next = api_sql_query($sql_update, __FILE__, __LINE__);
//echo '<p>' . $sql_update . '</p>'; //echo '<p>' . $sql_update . '</p>';
@ -1375,36 +1390,25 @@ class learnpath {
} }
/** /**
* Get the index terms shared between all items of this path * Get the specific prefix index terms of this learning path
* @return string String of terms, split by a simple comma * @return array Array of terms
*/ */
function get_common_index_terms() function get_common_index_terms_by_prefix($prefix)
{ {
$terms = array(); require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
$i = 0; $terms = get_specific_field_values_list_by_prefix($prefix, $this->cc, TOOL_LEARNPATH, $this->lp_id);
foreach ( $this->items as $item ) { $prefix_terms = array();
$i_terms = split(',',$item->terms);
if ( $i == 0 ) { $terms = $i_terms; continue; }
foreach($terms as $term) foreach($terms as $term)
{ {
if ( !in_array($term,$i_terms) ) $prefix_terms[] = $term['value'];
{
$terms = array_diff($terms,array($term)); //remove term from terms array because it is not in the current item's terms (so it is not common to all)
}
}
} }
$s = implode(',',$terms); return $prefix_terms;
return $s;
} }
/** /**
* Gets the number of items currently completed * Gets the number of items currently completed
* @return integer The number of items currently completed * @return integer The number of items currently completed
*/ */
function get_complete_items_count() function get_complete_items_count()
{ {
@ -3020,6 +3024,20 @@ class learnpath {
return false; return false;
} }
} }
/**
* Checks if any of the items has an audio element attached
* @return bool True or false
*/
function has_audio() {
$has = false;
foreach ($this->items as $i=>$item) {
if (!empty($this->items[$i]->audio)) {
$has = true;
break;
}
}
return $has;
}
/** /**
* Logs a message into a file * Logs a message into a file
* @param string Message to log * @param string Message to log
@ -3661,20 +3679,80 @@ class learnpath {
return true; return true;
} }
/** /**
* Set index terms for all items in this path * Set index specified prefix terms for all items in this path
* @param string Comma-separated list of terms * @param string Comma-separated list of terms
* @param char Xapian term prefix
* @return boolean False on error, true otherwise * @return boolean False on error, true otherwise
*/ */
function set_terms($terms) { function set_terms_by_prefix($terms_string, $prefix) {
if ( empty($terms) ) return false; if (api_get_setting('search_enabled') !== 'true')
if ( trim($terms) == trim($this->get_common_index_terms()) ) return false; //indexing is costly, don't re-index if no change return FALSE;
foreach ( $this->items as $item ) { $terms_string = trim($terms_string);
$item->set_terms($terms); $terms = explode(',', $terms_string);
array_walk($terms, 'trim_value');
$stored_terms = $this->get_common_index_terms_by_prefix($prefix);
//var_dump($stored_terms);
//var_dump($terms);
// 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('xapian.php'); //TODO try catch every xapian use or make wrappers on api
require_once(api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php');
require_once(api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php');
require_once(api_get_path(LIBRARY_PATH).'search/IndexableChunk.class.php');
$items_table = Database::get_course_table('lp_item');
//TODO: make query secure agains XSS : use member attr instead of post var
$lp_id = $_POST['lp_id'];
$sql = "SELECT * FROM $items_table WHERE lp_id = $lp_id";
$result = api_sql_query($sql);
$di = new DokeosIndexer();
while($lp_item = Database::fetch_array($result))
{
// get search_did
$tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
$sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d LIMIT 1';
$sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
$res = api_sql_query($sql, __FILE__, __LINE__);
$se_ref = Database::fetch_array($res);
// compare terms
$doc = $di->get_document($se_ref['search_did']);
$xapian_terms = xapian_get_doc_terms($doc, $prefix);
//var_dump($xapian_terms);
$xterms = array();
foreach ($xapian_terms as $xapian_term) $xterms[] = substr($xapian_term['name'],1);
$dterms = $terms;
//var_dump($xterms);
//var_dump($dterms);
$missing_terms = array_diff($dterms, $xterms);
$deprecated_terms = array_diff($xterms, $dterms);
// save it to search engine
foreach ($missing_terms as $term)
{
$doc->add_term($prefix. $term, 1);
} }
foreach ($deprecated_terms as $term)
{
$doc->remove_term($prefix.$term);
}
$di->getDb()->replace_document((int)$se_ref['search_did'], $doc);
$di->getDb()->flush();
}
return true; return true;
} }
/** /**
* Sets the theme of the LP (local/remote) (and save) * Sets the theme of the LP (local/remote) (and save)
* @param string Optional string giving the new theme of this learnpath * @param string Optional string giving the new theme of this learnpath
@ -6465,12 +6543,14 @@ class learnpath {
$form->addElement('file','mp3',get_lang('UploadMp3audio'),'id="mp3" size="33"'); $form->addElement('file','mp3',get_lang('UploadMp3audio'),'id="mp3" size="33"');
$form->addRule('file', 'The extension of the Song file should be *.mp3', 'filename', '/^.*\.mp3$/'); $form->addRule('file', 'The extension of the Song file should be *.mp3', 'filename', '/^.*\.mp3$/');
/* Code deprecated - moved to lp level (not lp-item)
if ( api_get_setting('search_enabled') === 'true' ) if ( api_get_setting('search_enabled') === 'true' )
{ {
//add terms field //add terms field
$terms = $form->addElement('text','terms', get_lang('SearchFeatureTerms').'&nbsp;:','id="idTerms" class="learnpath_item_form"'); $terms = $form->addElement('text','terms', get_lang('SearchFeatureTerms').'&nbsp;:','id="idTerms" class="learnpath_item_form"');
$terms->setValue($item_terms); $terms->setValue($item_terms);
} }
*/
$arrHide=array(); $arrHide=array();
@ -8836,30 +8916,13 @@ EOD;
} }
return false; return false;
} }
/**
* Create a new attempt for the current learning path. This is similar to }
* the restart() method, except that this version is to be used statically.
* Call this function to define a new attempt for the given path and user, if (!function_exists('trim_value')) {
* then reload the learning path object with the user and it will offer function trim_value(&$value) {
* a new attempt. $value = trim($value);
* @param int Learning path ID
* @param int User ID
* @return New attempt ID (new view ID from the database)
*/
function create_new_attempt($lp_id,$user_id) {
$lp_id = (int) $lp_id;
$user_id = (int) $user_id;
$t = Database::get_course_table(TABLE_LP_VIEW);
$sql = "SELECT max(view_count) FROM $t WHERE lp_id = $lp_id AND user_id = $user_id";
$res = api_sql_query($sql,__FILE__,__LINE__);
if ($res === false) { return 0;}
if (Database::num_rows($res)!=1) { return 0;}
$row = Database::fetch_array($res);
$vc = $row[0]+1;
$sqlins = "INSERT INTO $t (lp_id,user_id,view_count,last_item,progress) VALUES ($lp_id,$user_id, $vc,0,0)";
$resins = api_sql_query($sql,__FILE__,__LINE__);
if ($resins === false) { return 0; }
return $vc;
} }
} }
?> ?>

@ -12,6 +12,7 @@
*/ */
class learnpathItem{ class learnpathItem{
var $attempt_id; //also called "objectives" SCORM-wise var $attempt_id; //also called "objectives" SCORM-wise
var $audio; //the path to an audio file (stored in document/audio/)
var $children = array(); //contains the ids of children items var $children = array(); //contains the ids of children items
var $condition; //if this item has a special condition embedded var $condition; //if this item has a special condition embedded
var $current_score; var $current_score;
@ -49,12 +50,11 @@ class learnpathItem{
var $prevent_reinit = 1; var $prevent_reinit = 1;
var $ref; var $ref;
var $save_on_close = true; var $save_on_close = true;
var $search_did = NULL;
var $status; var $status;
var $title; var $title;
var $type; // this attribute can contain chapter|link|student_publication|module|quiz|document|forum|thread var $type; // this attribute can contain chapter|link|student_publication|module|quiz|document|forum|thread
var $view_id; var $view_id;
var $terms;
var $search_did;
var $debug = 0; //logging param var $debug = 0; //logging param
/** /**
@ -100,11 +100,21 @@ class learnpathItem{
if(isset($row['launch_data'])){ if(isset($row['launch_data'])){
$this->launch_data = $row['launch_data']; $this->launch_data = $row['launch_data'];
} }
$this->terms = $row['terms']; $this->save_on_close = true;
$this->search_did = $row['search_did'];
$this->save_on_close = true;
$this->db_id = $id; $this->db_id = $id;
// get search_did
$tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
$sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d LIMIT 1';
// TODO: verify if it's possible to assume the actual course instead of getting it from db
$sql = sprintf($sql, $tbl_se_ref, api_get_course_id(), TOOL_LEARNPATH, $this->lp_id, $id);
$res = api_sql_query($sql, __FILE__, __LINE__);
if (Database::num_rows($res) > 0) {
$se_ref = Database::fetch_array($res);
$this->search_did = (int)$se_ref['search_did'];
}
$this->audio = $row['audio'];
//error_log('New LP - End of learnpathItem constructor for item '.$id,0); //error_log('New LP - End of learnpathItem constructor for item '.$id,0);
return true; return true;
} }
@ -199,10 +209,25 @@ class learnpathItem{
$lp_item = Database::get_course_table('lp_item'); $lp_item = Database::get_course_table('lp_item');
$sql_del_view = "DELETE FROM $lp_item_view WHERE item_id = ".$this->db_id; $sql_del_view = "DELETE FROM $lp_item_view WHERE item_id = ".$this->db_id;
//error_log('New LP - Deleting from lp_item_view: '.$sql_del_view,0); //error_log('New LP - Deleting from lp_item_view: '.$sql_del_view,0);
$res_del_view = api_sql_query($sql_del_view); $res_del_view = api_sql_query($sql_del_view);
$sql_sel = "SELECT * FROM $lp_item WHERE id = ".$this->db_id;
$res_sel = api_sql_query($sql_sel,__FILE__,__LINE__);
if(Database::num_rows($res_sel)<1){return false;}
$row = Database::fetch_array($res_sel);
$sql_del_item = "DELETE FROM $lp_item WHERE id = ".$this->db_id; $sql_del_item = "DELETE FROM $lp_item WHERE id = ".$this->db_id;
//error_log('New LP - Deleting from lp_item: '.$sql_del_view,0); //error_log('New LP - Deleting from lp_item: '.$sql_del_view,0);
$res_del_item = api_sql_query($sql_del_item); $res_del_item = api_sql_query($sql_del_item);
if (api_get_setting('search_enabled') == 'true') {
if (!is_null($this->search_did)) {
require_once(api_get_path(LIBRARY_PATH) .'search/DokeosIndexer.class.php');
$di = new DokeosIndexer();
$di->remove_document($this->search_did);
}
}
return true; return true;
} }
/** /**
@ -1054,20 +1079,15 @@ class learnpathItem{
} }
return $scorm_time; return $scorm_time;
} }
/**
* Returns search index's internal ID function get_terms()
* @return int The indexing engine's internal ID for this lp-item {
*/ $lp_item = Database::get_course_table(TABLE_LP_ITEM);
function get_search_id() { $sql = "SELECT * FROM $lp_item WHERE id='".Database::escape_string($this->db_id)."'";
return $this->search_did; $res = api_sql_query($sql,__FILE__,__LINE__);
} $row = Database::fetch_array($res);
/** return $row['terms'];
* Returns index terms }
* @return string Comma-separated list of index terms
*/
function get_terms() {
return $this->terms;
}
/** /**
* Returns the item's title * Returns the item's title
* @return string Title * @return string Title
@ -1939,9 +1959,9 @@ class learnpathItem{
return false; return false;
} }
/** /**
* Sets new index terms for item * Set the terms for this learnpath item
* @param string Comma-separated list of terms * @param string Terms, as a comma-split list
* @return boolean False on error, true otherwise * @return boolean Always return true
*/ */
function set_terms($terms) { function set_terms($terms) {
$lp_item = Database::get_course_table(TABLE_LP_ITEM); $lp_item = Database::get_course_table(TABLE_LP_ITEM);
@ -1957,14 +1977,22 @@ class learnpathItem{
//TODO: validate csv string //TODO: validate csv string
$terms_update_sql = "UPDATE $lp_item SET terms = '". Database::escape_string(htmlentities($new_terms_string)) . "' WHERE id=".$this->get_id(); $terms_update_sql = "UPDATE $lp_item SET terms = '". Database::escape_string(htmlentities($new_terms_string)) . "' WHERE id=".$this->get_id();
$res = api_sql_query($terms_update_sql,__FILE__,__LINE__); $res = api_sql_query($terms_update_sql,__FILE__,__LINE__);
// save it to search engine // save it to search engine
if (api_get_setting('search_enabled') == 'true') { if (api_get_setting('search_enabled') == 'true') {
$di = new DokeosIndexer(); $di = new DokeosIndexer();
$di->update_terms($this->get_search_id(), $new_terms); $di->update_terms($this->get_search_did(), $new_terms);
} }
return true; return true;
} }
/**
* Get the document ID from inside the text index database
* @return int Search index database document ID
*/
function get_search_did()
{
return $this->search_did;
}
/** /**
* Sets the item viewing time in a usable form, given that SCORM packages often give it as 00:00:00.0000 * Sets the item viewing time in a usable form, given that SCORM packages often give it as 00:00:00.0000
* @param string Time as given by SCORM * @param string Time as given by SCORM

@ -366,7 +366,7 @@ switch($action)
if(isset($_POST['submit_button']) && !empty($_POST['title'])) if(isset($_POST['submit_button']) && !empty($_POST['title']))
{ {
$_SESSION['oLP']->edit_item($_GET['id'], $_POST['parent'], $_POST['previous'], $_POST['title'], $_POST['description'], $_POST['prerequisites'], $_POST['terms']); $_SESSION['oLP']->edit_item($_GET['id'], $_POST['parent'], $_POST['previous'], $_POST['title'], $_POST['description'], $_POST['prerequisites']);
if(isset($_POST['content_lp'])) if(isset($_POST['content_lp']))
{ {
@ -577,21 +577,24 @@ switch($action)
$_SESSION['oLP']->set_name($_REQUEST['lp_name']); $_SESSION['oLP']->set_name($_REQUEST['lp_name']);
$author= $_REQUEST['lp_author']; $author= $_REQUEST['lp_author'];
//fixing the author name (no body or html tags) //fixing the author name (no body or html tags)
if( stripos($author) != 0 ) { $auth_init = stripos($author,'<p>');
$len = strripos($author,'</p>')-stripos($author,'<p>'); if ( $auth_init === false ) {
$author_fixed=substr($author,stripos($author,'<p>'), $len+4); $auth_init = stripos($author,'<body>');
$auth_end = $auth_init + stripos(substr($author,$auth_init+6),'</body>') + 7;
$len = $auth_end - $auth_init +6;
} else { } else {
$len = strripos($author,'</body>')-stripos($author,'<body>'); $auth_end = strripos($author,'</p>');
$author_fixed=substr($author,stripos($author,'<body>'), $len+7); $len = $auth_end - $auth_init + 4;
} }
$author_fixed=substr($author,$auth_init, $len);
//$author_fixed = $author;
$_SESSION['oLP']->set_author($author_fixed); $_SESSION['oLP']->set_author($author_fixed);
$_SESSION['oLP']->set_encoding($_REQUEST['lp_encoding']); $_SESSION['oLP']->set_encoding($_REQUEST['lp_encoding']);
$_SESSION['oLP']->set_maker($_REQUEST['lp_maker']); $_SESSION['oLP']->set_maker($_REQUEST['lp_maker']);
$_SESSION['oLP']->set_proximity($_REQUEST['lp_proximity']); $_SESSION['oLP']->set_proximity($_REQUEST['lp_proximity']);
$_SESSION['oLP']->set_theme($_REQUEST['lp_theme']); $_SESSION['oLP']->set_theme($_REQUEST['lp_theme']);
$_SESSION['oLP']->set_terms($_REQUEST['lp_terms']);
if ($_REQUEST['remove_picture']) if ($_REQUEST['remove_picture'])
{ {
$_SESSION['oLP']->delete_lp_image(); $_SESSION['oLP']->delete_lp_image();
@ -600,6 +603,27 @@ switch($action)
if ($_FILES['lp_preview_image']['size']>0) if ($_FILES['lp_preview_image']['size']>0)
$_SESSION['oLP']->upload_image($_FILES['lp_preview_image']); $_SESSION['oLP']->upload_image($_FILES['lp_preview_image']);
if (api_get_setting('search_enabled') === 'true')
{
require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
$specific_fields = get_specific_field_list();
foreach ($specific_fields as $specific_field) {
$_SESSION['oLP']->set_terms_by_prefix($_REQUEST[$specific_field['code']], $specific_field['code']);
$new_values = explode(',', trim($_REQUEST[$specific_field['code']]));
if ( !empty($new_values) ) {
array_walk($new_values, 'trim');
delete_all_specific_field_value(api_get_course_id(), $specific_field['id'], TOOL_LEARNPATH, $_SESSION['oLP']->lp_id);
foreach ($new_values as $value)
{
if ( !empty($value) ) {
add_specific_field_value($specific_field['id'], api_get_course_id(), TOOL_LEARNPATH, $_SESSION['oLP']->lp_id, $value);
}
}
}
}
}
require('lp_list.php'); require('lp_list.php');
} }
break; break;

@ -6,29 +6,29 @@
*/ */
require_once (api_get_path(LIBRARY_PATH).'formvalidator/FormValidator.class.php'); require_once (api_get_path(LIBRARY_PATH).'formvalidator/FormValidator.class.php');
require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
$show_description_field = false; //for now $show_description_field = false; //for now
$nameTools = get_lang("Doc"); $nameTools = get_lang("Doc");
event_access_tool(TOOL_LEARNPATH); event_access_tool(TOOL_LEARNPATH);
if (! $is_allowed_in_course) api_not_allowed(); if (! $is_allowed_in_course) api_not_allowed();
$interbreadcrumb[]= array ("url"=>"lp_controller.php?action=list", "name"=> get_lang("_learning_path")); $interbreadcrumb[]= array ("url"=>"lp_controller.php?action=list", "name"=> get_lang("_learning_path"));
$interbreadcrumb[]= array ("url"=>api_get_self()."?action=admin_view&lp_id=$learnpath_id", "name" => $_SESSION['oLP']->get_name()); $interbreadcrumb[]= array ("url"=>api_get_self()."?action=admin_view&lp_id=$learnpath_id", "name" => $_SESSION['oLP']->get_name());
Display::display_header(null,'Path'); Display::display_header(null,'Path');
//Page subtitle //Page subtitle
echo '<span style="font-weight: bold;padding-left:8px;">'.get_lang('_edit_learnpath').'</span>'; echo '<h4>'.get_lang('EditLPSettings').'</h4>';
$fck_attribute['Width'] = '400px'; $fck_attribute['Width'] = '400px';
$fck_attribute['Height'] = '150px'; $fck_attribute['Height'] = '150px';
$fck_attribute['ToolbarSet'] = 'Comment'; $fck_attribute['ToolbarSet'] = 'Comment';
$defaults=array(); $defaults=array();
$form = new FormValidator('form1', 'post', 'lp_controller.php'); $form = new FormValidator('form1', 'post', 'lp_controller.php');
//Title //Title
$form -> addElement('text', 'lp_name', ucfirst(get_lang('_title'))); $form -> addElement('text', 'lp_name', ucfirst(get_lang('_title')),array('size'=>47));
//Encoding //Encoding
$encoding_select = &$form->addElement('select', 'lp_encoding', get_lang('Charset')); $encoding_select = &$form->addElement('select', 'lp_encoding', get_lang('Charset'));
@ -84,7 +84,7 @@ if (api_get_setting('allow_course_theme') == 'true')
//$form -> addElement('text', 'lp_author', ucfirst(get_lang('Author'))); //$form -> addElement('text', 'lp_author', ucfirst(get_lang('Author')));
//$form->add_html_editor('lp_author', get_lang('Author')); //$form->add_html_editor('lp_author', get_lang('Author'));
$form->addElement('html_editor','lp_author',get_lang('Author')); $form->addElement('html_editor','lp_author',get_lang('Author'),array('size'=>80) );
// LP image // LP image
$form->add_progress_bar(); $form->add_progress_bar();
@ -113,10 +113,22 @@ $form->addElement('html', $div);
$form->addRule('lp_preview_image', get_lang('OnlyImagesAllowed'), 'mimetype', array('image/gif', 'image/jpeg', 'image/png')); $form->addRule('lp_preview_image', get_lang('OnlyImagesAllowed'), 'mimetype', array('image/gif', 'image/jpeg', 'image/png'));
// Search terms (only if search is activated) // Search terms (only if search is activated)
if (api_get_setting('search_enabled') == 'true') if (api_get_setting('search_enabled') === 'true')
{ {
$form -> addElement('text', 'lp_terms', get_lang('SearchFeatureTerms')); $specific_fields = get_specific_field_list();
$defaults['lp_terms'] = $_SESSION['oLP']->get_common_index_terms(); foreach ($specific_fields as $specific_field) {
$form -> addElement ('text', $specific_field['code'], $specific_field['name']);
$filter = array('course_code'=> "'". api_get_course_id() ."'", 'field_id' => $specific_field['id'], 'ref_id' => $_SESSION['oLP']->lp_id, 'tool_id' => '\''. TOOL_LEARNPATH .'\'');
$values = get_specific_field_values_list($filter, array('value'));
if ( !empty($values) ) {
$arr_str_values = array();
foreach ($values as $value) {
$arr_str_values[] = $value['value'];
}
$defaults[$specific_field['code']] = implode(', ', $arr_str_values);
}
}
} }
//default values //default values
@ -127,19 +139,16 @@ $defaults['lp_name']=$_SESSION['oLP']->get_name();
$defaults['lp_author']=$_SESSION['oLP']->get_author(); $defaults['lp_author']=$_SESSION['oLP']->get_author();
//Submit button //Submit button
$form->addElement('submit', 'Submit', get_lang('Ok')); $form->addElement('submit', 'Submit', get_lang('SaveLPSettings'));
//Hidden fields //Hidden fields
$form->addElement('hidden', 'action', 'update_lp'); $form->addElement('hidden', 'action', 'update_lp');
$form->addElement('hidden', 'lp_id', $_SESSION['oLP']->get_id()); $form->addElement('hidden', 'lp_id', $_SESSION['oLP']->get_id());
$form->setDefaults($defaults);
echo '<div style="padding-left:100px; padding-top:-10px;">';
echo '<table style="border="0" cellspacing="0" cellpadding="0"><tr><td width="530px"><br /><br /><br /><div style="width:530px;"></div>'; $form->setDefaults($defaults);
echo '<table><tr><td>';
$form -> display(); $form -> display();
echo '</td><td valign="top">'; echo '</td><td valign="top"><img src="../img/CourseSettingsPageLayout.png" /></td></tr></table>';
echo Display::display_icon('help_course_authoring.png','');
echo '</td></tr></table>';
echo '</div>';
Display::display_footer(); Display::display_footer();
?> ?>

@ -394,10 +394,6 @@ if (is_array($flat_list))
echo "</table>"; echo "</table>";
echo "<br/><br/>"; echo "<br/><br/>";
/* search widget */
if (api_get_setting('search_enabled') == 'true')
search_widget_show();
/* /*
============================================================================== ==============================================================================
FOOTER FOOTER

@ -6,7 +6,8 @@
*/ */
require api_get_path(LIBRARY_PATH).'search/search_widget.php'; require api_get_path(LIBRARY_PATH).'search/search_widget.php';
require api_get_path(LIBRARY_PATH).'search/DokeosQuery.php'; require api_get_path(LIBRARY_PATH).'search/DokeosQuery.php';
require_once(api_get_path(LIBRARY_PATH).'search/IndexableChunk.class.php');
require_once api_get_path(LIBRARY_PATH).'/specific_fields_manager.lib.php';
$htmlHeadXtra[] = ' $htmlHeadXtra[] = '
<style type="text/css"> <style type="text/css">
@ -33,13 +34,25 @@ $htmlHeadXtra[] = '
font-weight: bold; font-weight: bold;
height: 2em; height: 2em;
} }
.lightbox_link{
padding-top: 10px;
padding-bottom: 10px;
text-align:center;
font-size:26px;
}
.data_table{text-align:center;}
.table_pager_1{z-index:10;height:20px;padding:5px}
.cls{clear:both}
</style>'; </style>';
search_widget_prepare(&$htmlHeadXtra); search_widget_prepare(&$htmlHeadXtra);
event_access_tool(TOOL_LEARNPATH); event_access_tool(TOOL_SEARCH);
$interbreadcrumb[]= array ("url"=>"./lp_controller.php?action=list", "name"=> get_lang(ucfirst(TOOL_LEARNPATH))); $interbreadcrumb[]= array ("url"=>"./index.php", "name"=> get_lang('LectureLibrary'));//get_lang(ucfirst(TOOL_SEARCH))
Display::display_header(null,'Path'); Display::display_header(null,'Path');
if (api_get_setting('search_enabled') !== 'true') { if (api_get_setting('search_enabled') !== 'true') {
@ -51,118 +64,105 @@ else
search_widget_show(empty($search_action)?null:'index.php'); search_widget_show(empty($search_action)?null:'index.php');
} }
$tag_array = array();
/** $specific_fields = get_specific_field_list();
* Utility function to get a table for a specific course. foreach ($specific_fields as $specific_field) {
* if (isset($_REQUEST[ 'sf_'. $specific_field['code'] ])) {
* @param string $course_code The course_code as in cidReq $values = $_REQUEST[ 'sf_'. $specific_field['code'] ];
* @param string $table The desired table, this is just concat'd. foreach ($values as $term) {
* @return string if (!empty($term)) {
*/ $prefix = $specific_field['code'];
function get_course_table($course_code, $table) { $tag_array[] = dokeos_get_boolean_query($prefix . $term);
$ret = NULL; }
}
$course_table = Database::get_main_table(TABLE_MAIN_COURSE); }
$course_cat_table = Database::get_main_table(TABLE_MAIN_CATEGORY);
$sql = "SELECT
$course_table.db_name, $course_cat_table.code
FROM $course_table
LEFT JOIN $course_cat_table
ON
$course_table.category_code = $course_cat_table.code
WHERE
$course_table.code = '$course_code'
LIMIT 1";
$res = api_sql_query($sql, __FILE__, __LINE__);
$result = Database::fetch_array($res);
$ret = sprintf ("%s.%s",
$result[0],
$table);
return $ret;
} }
$tags = explode(",", trim($_REQUEST['tags'])); $query = $_REQUEST['query'];
$tags2 = ''; $query = stripslashes(htmlspecialchars_decode($query,ENT_QUOTES));
foreach ($tags as $tag) $op = 'or';
if (strlen($tag)> 1) if (!empty($_REQUEST['operator']) && $_REQUEST['operator'] == 'and') {
$tags2 .= " tag:".trim($tag); $op = $_REQUEST['operator'];
}
$fixed_queries = array();
$course_filter = NULL;
if ( ($cid=api_get_course_id()) != -1 ) {
// results only from actual course
$course_filter = dokeos_get_boolean_query(XAPIAN_PREFIX_COURSEID . $cid);
}
$query = $_REQUEST['query'] . ' ' . $tags2; if (count($tag_array)) {
$fixed_queries = dokeos_join_queries($tag_array,null,$op);
if ($course_filter != NULL) {
$fixed_queries = dokeos_join_queries($fixed_queries, $course_filter, 'and');
}
} else {
if (!empty($query)) {
$fixed_queries = array($course_filter);
}
}
$query_results = dokeos_query_query($query, 0, 1000); list($count, $results) = dokeos_query_query(mb_convert_encoding($query,'UTF-8',$charset), 0, 1000, $fixed_queries);
$count = $query_results[0];
$results = $query_results[1];
$blocks = array(); $blocks = array();
$url = api_get_path(WEB_CODE_PATH)."/newscorm/lp_controller.php"; $url = api_get_path(WEB_CODE_PATH)."/newscorm/lp_controller.php";
$search_url = sprintf('%s?action=search&query=%s&tags=%s', $search_url = sprintf('%s?action=search&query=%s',
$url, $_REQUEST['query'], $_REQUEST['tags']); $url, $_REQUEST['query']);
$link_format = $url.'?cidReq=%s&action=view&lp_id=%s&item_id=%s'; $link_format = $url.'?cidReq=%s&action=view&lp_id=%s&item_id=%s';
$learnings_id_list = array();
$mode = ($_GET['mode']!='default') ? 'gallery' : 'default';
if ($count > 0) { if ($count > 0) {
foreach ($results as $result) { foreach ($results as $result) {
/* FIXME:diegoe: marco debe darme los valores adecuados. */
$ids = explode(":", $result->ids);
$course_id = $ids[0];
$lp_id = $ids[1];
$doc_id = $ids[2];
if (!api_is_course_visible_for_user(NULL, $course_id))
continue;
$tags = ''; $tags = '';
foreach ($result->terms as $term) { foreach ($result->terms as $term) {
$tags .= trim($term['name'], 'T') . ", "; $tags .= $term['name'] . ", ";
} }
//remove trailing comma //remove trailing comma
$tags = substr($tags,0,-2); $tags = substr($tags,0,-2);
if (!empty($result['url'])) {
$a_prefix = '<a href="'.$result['url'].'">';
$a_sufix = '</a>';
}
else {
$a_prefix = '';
$a_sufix = '';
}
if ($mode == 'gallery') {
$title = $a_prefix.str_replace('_',' ',$result['title']). $a_sufix; //TODO: get author.(empty($row['author'])?'':''.$row['author']);
} else {
$title = '<div style="text-align:left;">'. $a_prefix . $result['title']. $a_sufix .(!empty($result['author'])?$result['author']:'').'<div>';
}
$lpi_table = get_course_table($course_id, TABLE_LP_ITEM); // Fill the result array
$lp_table = get_course_table($course_id, TABLE_LP_MAIN); if (empty($result['thumbnail'])) { // or !file_exists('../../courses/'.api_get_course_path($course_id)."/document/".$img_path)
$doc_table = get_course_table($course_id, TABLE_DOCUMENT); $result['thumbnail'] = '../img/no_document_thumb.jpg';
$sql = "SELECT }
$doc_table.title, $doc_table.path, $lpi_table.id,
$lp_table.name, $lp_table.author if ($mode == 'gallery') {
FROM $lp_table, $lpi_table INNER JOIN $doc_table $blocks[] = array(
ON $lpi_table.path = $doc_table.id //'<a name="'.htmlentities('<div class="lightbox_link"><a href="'.$result['url'].'" target="_top">'.get_lang('GoToLearningPath').'</a></div>').'" rel="lightbox" href="'.$result['image'].'"><img src="'.$result['thumbnail'].'"></a>',
WHERE $a_prefix .'<img src="'.$result['thumbnail'].'" />'. $a_sufix .'<br />'.$title.'<br />'.$result['author'],
$lpi_table.lp_id = $lp_id //$title,
AND );
$doc_table.id = $doc_id } else {
AND $blocks[] = array(
$lp_table.id = $lpi_table.lp_id $title,
LIMIT 1"; );
$dk_result = api_sql_query ($sql);
while ($row = Database::fetch_array ($dk_result)) {
/* Get the image path */
$img_path = str_replace ('.png.html', '_thumb.png', $row['path']);
$doc_id = $row['id'];
$title = get_lang('Title').': '.'<b>'.$row['name'].'</b><br /><br />'.$row['title'].(empty($row['author'])?'':'<br /><br />'.get_lang('Author').': '.$row['author']);
$href = sprintf($link_format, $course_id, $lp_id, $doc_id);
/* Fill the result array */
$blocks[] = array(
'<a href="'.$href.'"><img src="'.api_get_path(WEB_COURSE_PATH).api_get_course_path($course_id)."/document/".$img_path.'"></a>', //put a link to the learning path item directly on the image
$title,
$tags,
$href
);
} }
} }
} }
if (count($blocks) < 1) { if (count($blocks) < 1) {
Display::display_normal_message(get_lang('SearchFeatureSearchExplanation'), FALSE); // Display::display_normal_message(get_lang('SearchFeatureSearchExplanation'), FALSE);
} }
else else
{ {
@ -171,18 +171,41 @@ else
} }
$s = new SortableTableFromArray($blocks); $s = new SortableTableFromArray($blocks);
$s->additional_parameters = array( $s->display_mode = $mode;//default
'action' => 'search', $s->display_mode_params = 3;
'query' => $_REQUEST['query'], $s->per_page = 9;
'tags' => $_REQUEST['tags'], $additional_parameters = array(
); 'mode' => $mode,
$s->set_header(0, get_lang('Preview'),false,array('width="300px"')); 'action' => 'search',
$s->set_header(1, get_lang('Title')); 'query' => $_REQUEST['query'],
$s->set_header(2, get_lang('Tags')); );
$s->set_header(3, get_lang('Learning path')); $get_params = '';
$s->set_column_filter(3,'to_link'); foreach ($specific_fields as $specific_field) {
if (isset($_REQUEST[ 'sf_'. $specific_field['code'] ])) {
$values = $_REQUEST[ 'sf_'. $specific_field['code'] ];
$additional_parameters[ 'sf_'. $specific_field['code'] ] = $values;
foreach ( $values as $value ) {
$get_params .= '&sf_' . $specific_field['code'] .'[]='. $value;
}
$get_params .= '&';
}
}
$s->additional_parameters = $additional_parameters;
if ($mode == 'default') {
$s->set_header(0, get_lang('Lectures'));
}
$search_url = api_get_path(WEB_CODE_PATH)."search/index.php";
echo '<div style="width:940px;border:1px solid #979797;position:relative;background-image:url(\'../img/search_background_bar.jpg\');background-repeat: repeat-x">';
echo '<div style="width:100px;padding:4px;position:absolute;top:0px;z-index:9"><a ' .
'href="'.$search_url.'?mode=gallery&action=search&query='.$_REQUEST['query'].$get_params.'"><img src="../img/'.(($mode=='gallery')?'ButtonGallOn':'ButtonGallOff').'.png"/></a><a ' .
'href="'.$search_url.'?mode=default&action=search&query='.$_REQUEST['query'].$get_params.'"><img src="../img/'.(($mode=='default')?'ButtonListOn':'ButtonListOff').'.png"/></a>
</div>';
$s->display(); $s->display();
echo '</div>';
} }
Display::display_footer(); Display::display_footer();
?> ?>

@ -12,11 +12,9 @@
* @package dokeos.learnpath.openofficedocument * @package dokeos.learnpath.openofficedocument
*/ */
require_once('openoffice_document.class.php'); require_once('openoffice_document.class.php');
if(api_get_setting('search_enabled')==='true') //the true condition here should be if xapian module is installed require_once(api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php');
{ require_once(api_get_path(LIBRARY_PATH).'search/IndexableChunk.class.php');
require_once(api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php'); require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
require_once(api_get_path(LIBRARY_PATH).'search/IndexableChunk.class.php');
}
class OpenofficePresentation extends OpenofficeDocument { class OpenofficePresentation extends OpenofficeDocument {
@ -67,8 +65,13 @@ class OpenofficePresentation extends OpenofficeDocument {
$document_id = add_document($_course,$this->created_dir.'/'.urlencode($file_name),'file',filesize($this->base_work_dir.$this->created_dir.'/'.$file_name),$slide_name); $document_id = add_document($_course,$this->created_dir.'/'.urlencode($file_name),'file',filesize($this->base_work_dir.$this->created_dir.'/'.$file_name),$slide_name);
api_item_property_update($_course,TOOL_DOCUMENT,$document_id,'DocumentAdded',api_get_user_id(),0,0); api_item_property_update($_course,TOOL_DOCUMENT,$document_id,'DocumentAdded',api_get_user_id(),0,0);
// Generating the thumbnail // Generating the thumbnail
$image = $this->base_work_dir.$this->created_dir .'/'. $file_name; $image = $this->base_work_dir.$this->created_dir .'/'. $file_name;
$pattern = '/(\w+)\.png$/';
$replacement = '${1}_thumb.png';
$thumb_name = preg_replace($pattern, $replacement, $file_name);
// calculate thumbnail size // calculate thumbnail size
list($width, $height) = getimagesize($image); list($width, $height) = getimagesize($image);
$thumb_width = 300; $thumb_width = 300;
@ -79,10 +82,15 @@ class OpenofficePresentation extends OpenofficeDocument {
// resize // resize
imagecopyresized($thumb, $source, 0, 0, 0, 0, $thumb_width, $thumb_height, $width, $height); imagecopyresized($thumb, $source, 0, 0, 0, 0, $thumb_width, $thumb_height, $width, $height);
// output // output
$pattern = '/(\w+)\.png$/';
$replacement = '${1}_thumb.png';
$thumb_name = preg_replace($pattern, $replacement, $file_name);
imagepng($thumb, $this->base_work_dir.$this->created_dir .'/'. $thumb_name); imagepng($thumb, $this->base_work_dir.$this->created_dir .'/'. $thumb_name);
/*
// new resizing method usign imagemagick
$im = new Imagick( $image );
$im->thumbnailImage($thumb_width, $thumb_height);
//file_put_contents($this->base_work_dir.$this->created_dir .'/'. $thumb_name,$im);
$im->writeImage($this->base_work_dir.$this->created_dir .'/'. $thumb_name);
*/
// adding the thumbnail to documents // adding the thumbnail to documents
$document_id_thumb = add_document($_course, $this->created_dir.'/'.urlencode($thumb_name), 'file', filesize($this->base_work_dir.$this->created_dir.'/'.$thumb_name), $slide_name); $document_id_thumb = add_document($_course, $this->created_dir.'/'.urlencode($thumb_name), 'file', filesize($this->base_work_dir.$this->created_dir.'/'.$thumb_name), $slide_name);
api_item_property_update($_course, TOOL_THUMBNAIL, $document_id_thumb,'DocumentAdded',api_get_user_id(),0,0); api_item_property_update($_course, TOOL_THUMBNAIL, $document_id_thumb,'DocumentAdded',api_get_user_id(),0,0);
@ -111,40 +119,52 @@ class OpenofficePresentation extends OpenofficeDocument {
} }
} }
// code for text indexing // code for text indexing
if (isset($_POST['index_document']) && $_POST['index_document'] && api_get_setting('search_enabled')==='true') { if (isset($_POST['index_document']) && $_POST['index_document']) {
//Display::display_normal_message(print_r($_POST)); //Display::display_normal_message(print_r($_POST));
$di = new DokeosIndexer(); $di = new DokeosIndexer();
isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english'; isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
$di->connectDb(NULL, NULL, $lang); $di->connectDb(NULL, NULL, $lang);
$ic_slide = new IndexableChunk(); $ic_slide = new IndexableChunk();
$ic_slide->addValue("title", $slide_name); $ic_slide->addValue("title", $slide_name);
if (isset($_POST['terms'])) { $specific_fields = get_specific_field_list();
foreach (explode(',', Database::escape_string($_POST['terms'])) as $term){ $all_specific_terms = '';
$ic_slide->addTerm(trim($term),'T'); foreach ($specific_fields as $specific_field) {
} if (isset($_REQUEST[$specific_field['code']])) {
$sterms = trim($_REQUEST[$specific_field['code']]);
$all_specific_terms .= ' '. $sterms;
if (!empty($sterms)) {
$sterms = explode(',', $sterms);
foreach ($sterms as $sterm) {
$ic_slide->addTerm(trim($sterm), $specific_field['code']);
}
}
}
} }
$slide_body = $all_specific_terms .' '. $slide_body;
$ic_slide->addValue("content", $slide_body); $ic_slide->addValue("content", $slide_body);
/* FIXME: cidReq:lp_id:doc_id al indexar */ /* FIXME: cidReq:lp_id:doc_id al indexar */
// add a comment to say terms separated by commas // add a comment to say terms separated by commas
$courseid=api_get_course_id(); $courseid=api_get_course_id();
$ic_slide->addTerm($courseid,'C'); $ic_slide->addCourseId($courseid);
//TODO: add dokeos tool type instead of filetype $ic_slide->addToolId(TOOL_LEARNPATH);
$lp_id = $this->lp_id; $lp_id = $this->lp_id;
// TODO: get "path" field $xapian_data = array(
$ic_slide->addValue('ids', $courseid .':'. $this->lp_id SE_COURSE_ID => $courseid,
.':'.$document_id ); SE_TOOL_ID => TOOL_LEARNPATH,
SE_DATA => array('lp_id' => $lp_id, 'lp_item'=> $previous, 'document_id' => $document_id),
SE_USER => (int)api_get_user_id(),
);
$ic_slide->xapian_data = serialize($xapian_data);
$di->addChunk($ic_slide); $di->addChunk($ic_slide);
//index and return search engine document id //index and return search engine document id
$did = $di->index(); $did = $di->index();
if ($did) { if ($did) {
// save it to db // save it to db
$tbl_lp_item = Database::get_course_table('lp_item'); $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
$sql_update = " $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did)
UPDATE " . $tbl_lp_item . " VALUES (NULL , \'%s\', \'%s\', %s, %s, %s)';
SET search_did = " . $did . ", $sql = sprintf($sql, $tbl_se_ref, api_get_course_id(), TOOL_LEARNPATH, $lp_id, $previous, $did);
terms = '".Database::escape_string($_POST['terms'])."' api_sql_query($sql,__FILE__,__LINE__);
WHERE id = " . $previous;
api_sql_query($sql_update, __FILE__, __LINE__);
} }
} }
} }

@ -11,6 +11,9 @@
* @package dokeos.learnpath.openofficedocument * @package dokeos.learnpath.openofficedocument
*/ */
require_once('openoffice_document.class.php'); require_once('openoffice_document.class.php');
require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
require_once(api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php');
require_once(api_get_path(LIBRARY_PATH).'search/IndexableChunk.class.php');
class OpenOfficeTextDocument extends OpenofficeDocument { class OpenOfficeTextDocument extends OpenofficeDocument {
@ -202,6 +205,8 @@ class OpenOfficeTextDocument extends OpenofficeDocument {
$document_id = add_document($_course,$this->created_dir.$html_file,'file',filesize($this->base_work_dir.$this->created_dir.$html_file),$html_file); $document_id = add_document($_course,$this->created_dir.$html_file,'file',filesize($this->base_work_dir.$this->created_dir.$html_file),$html_file);
$slide_name = '';
if ($document_id){ if ($document_id){
//put the document in item_property update //put the document in item_property update
@ -213,6 +218,54 @@ class OpenOfficeTextDocument extends OpenofficeDocument {
if($this->first_item == 0){ if($this->first_item == 0){
$this->first_item = $previous; $this->first_item = $previous;
} }
// code for text indexing
if (isset($_POST['index_document']) && $_POST['index_document']) {
//Display::display_normal_message(print_r($_POST));
$di = new DokeosIndexer();
isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
$di->connectDb(NULL, NULL, $lang);
$ic_slide = new IndexableChunk();
$ic_slide->addValue("title", $slide_name);
$specific_fields = get_specific_field_list();
$all_specific_terms = '';
foreach ($specific_fields as $specific_field) {
if (isset($_REQUEST[$specific_field['code']])) {
$sterms = trim($_REQUEST[$specific_field['code']]);
$all_specific_terms .= ' '. $sterms;
if (!empty($sterms)) {
$sterms = explode(',', $sterms);
foreach ($sterms as $sterm) {
$ic_slide->addTerm(trim($sterm), $specific_field['code']);
}
}
}
}
$page_content = $all_specific_terms .' '. $page_content;
$ic_slide->addValue("content", $page_content);
// add a comment to say terms separated by commas
$courseid=api_get_course_id();
$ic_slide->addCourseId($courseid);
$ic_slide->addToolId(TOOL_LEARNPATH);
$lp_id = $this->lp_id;
$xapian_data = array(
SE_COURSE_ID => $courseid,
SE_TOOL_ID => TOOL_LEARNPATH,
SE_DATA => array('lp_id' => $lp_id, 'lp_item'=> $previous, 'document_id' => $document_id),
SE_USER => (int)api_get_user_id(),
);
$ic_slide->xapian_data = serialize($xapian_data);
$di->addChunk($ic_slide);
//index and return search engine document id
$did = $di->index();
if ($did) {
// save it to db
$tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
$sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did)
VALUES (NULL , \'%s\', \'%s\', %s, %s, %s)';
$sql = sprintf($sql, $tbl_se_ref, api_get_course_id(), TOOL_LEARNPATH, $lp_id, $previous, $did);
api_sql_query($sql,__FILE__,__LINE__);
}
}
} }
} }

@ -473,6 +473,58 @@ class scorm extends learnpath {
$upd_res = api_sql_query($upd); $upd_res = api_sql_query($upd);
//update previous item id //update previous item id
$previous = $item_id; $previous = $item_id;
// code for indexing, now only index specific fields like terms and the title
if (isset($_POST['index_document']) && $_POST['index_document']) {
require_once(api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php');
require_once(api_get_path(LIBRARY_PATH).'search/IndexableChunk.class.php');
require_once(api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php');
$di = new DokeosIndexer();
isset($_POST['language'])? $lang=Database::escape_string($_POST['language']): $lang = 'english';
$di->connectDb(NULL, NULL, $lang);
$ic_slide = new IndexableChunk();
$ic_slide->addValue("title", $title);
$specific_fields = get_specific_field_list();
$all_specific_terms = '';
foreach ($specific_fields as $specific_field) {
if (isset($_REQUEST[$specific_field['code']])) {
$sterms = trim($_REQUEST[$specific_field['code']]);
$all_specific_terms .= ' '. $sterms;
if (!empty($sterms)) {
$sterms = explode(',', $sterms);
foreach ($sterms as $sterm) {
$ic_slide->addTerm(trim($sterm), $specific_field['code']);
}
}
}
}
$body_to_index = $all_specific_terms .' '. $title;
$ic_slide->addValue("content", $body_to_index);
//TODO: add a comment to say terms separated by commas
$courseid = api_get_course_id();
$ic_slide->addCourseId($courseid);
$ic_slide->addToolId(TOOL_LEARNPATH);
$xapian_data = array(
SE_COURSE_ID => $courseid,
SE_TOOL_ID => TOOL_LEARNPATH,
SE_DATA => array('lp_id' => $lp_id, 'lp_item'=> $previous, 'document_id' => ''), //TODO unify with other lp types
SE_USER => (int)api_get_user_id(),
);
$ic_slide->xapian_data = serialize($xapian_data);
$di->addChunk($ic_slide);
//index and return search engine document id
$did = $di->index();
if ($did) {
// save it to db
$tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
$sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did)
VALUES (NULL , \'%s\', \'%s\', %s, %s, %s)';
$sql = sprintf($sql, $tbl_se_ref, api_get_course_id(), TOOL_LEARNPATH, $lp_id, $previous, $did);
api_sql_query($sql,__FILE__,__LINE__);
}
}
} }
} }
} }

@ -3,15 +3,16 @@
* This file includes lp_list_search to avoid duplication of code, it * This file includes lp_list_search to avoid duplication of code, it
* bootstraps dokeos api enough to make lp_list_search work. * bootstraps dokeos api enough to make lp_list_search work.
*/ */
include_once ('../inc/global.inc.php'); include_once ('../inc/global.inc.php');
//var_dump(api_get_path(LIBRARY_PATH));
include_once (api_get_path(LIBRARY_PATH).'course.lib.php'); include_once (api_get_path(LIBRARY_PATH).'course.lib.php');
include_once (api_get_path(LIBRARY_PATH).'debug.lib.inc.php'); include_once (api_get_path(LIBRARY_PATH).'debug.lib.inc.php');
include_once (api_get_path(LIBRARY_PATH).'system_announcements.lib.php'); include_once (api_get_path(LIBRARY_PATH).'system_announcements.lib.php');
include_once (api_get_path(LIBRARY_PATH).'groupmanager.lib.php'); include_once (api_get_path(LIBRARY_PATH).'groupmanager.lib.php');
include_once (api_get_path(LIBRARY_PATH).'usermanager.lib.php'); include_once (api_get_path(LIBRARY_PATH).'usermanager.lib.php');
include_once (api_get_path(LIBRARY_PATH).'events.lib.inc.php');
//api_block_anonymous_users(); // only users who are logged in can proceed
api_block_anonymous_users(); // only users who are logged in can proceed
$search_action = 'index.php';
require '../newscorm/lp_list_search.php'; require '../newscorm/lp_list_search.php';
?> ?>

@ -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);
?>

@ -36,9 +36,11 @@ $interbreadcrumb[]= array ("url"=>"../newscorm/lp_controller.php?action=list", "
Display::display_header($nameTools,"Path"); Display::display_header($nameTools,"Path");
//show the title //show the title
api_display_tool_title(get_lang("Learnpath")." - ".$nameTools.$add_group_to_title); api_display_tool_title(get_lang("Learnpath")." - ".$nameTools.$add_group_to_title);
//TODO: Include right language file
require_once (api_get_path(LIBRARY_PATH).'formvalidator/FormValidator.class.php'); require_once (api_get_path(LIBRARY_PATH).'formvalidator/FormValidator.class.php');
include('../newscorm/content_makers.inc.php'); include('../newscorm/content_makers.inc.php');
require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
$form = new FormValidator('','POST','upload.php','','id="upload_form" enctype="multipart/form-data" style="background-image: url(\'../img/scorm.jpg\'); background-repeat: no-repeat; background-position: 600px;"'); $form = new FormValidator('','POST','upload.php','','id="upload_form" enctype="multipart/form-data" style="background-image: url(\'../img/scorm.jpg\'); background-repeat: no-repeat; background-position: 600px;"');
@ -58,6 +60,15 @@ $select_content_proximity = &$form->addElement('select','content_proximity',get_
$select_content_proximity->addOption(get_lang('Remote'),"remote"); $select_content_proximity->addOption(get_lang('Remote'),"remote");
$select_content_proximity -> setSelected("local"); $select_content_proximity -> setSelected("local");
if(api_get_setting('search_enabled')=='true')
{
$form -> addElement ('checkbox', 'index_document','', get_lang('SearchFeatureDoIndexDocument'));
$specific_fields = get_specific_field_list();
foreach ($specific_fields as $specific_field) {
$form -> addElement ('text', $specific_field['code'], $specific_field['name'].' : ');
}
}
$form->addElement('submit', 'submit', get_lang('Send')); $form->addElement('submit', 'submit', get_lang('Send'));
$form->addElement('html', '<br><br><br>'); $form->addElement('html', '<br><br><br>');
@ -77,6 +88,7 @@ else{
$form->add_real_progress_bar('uploadScorm','user_file'); $form->add_real_progress_bar('uploadScorm','user_file');
$defaults = array('index_document'=>'checked="checked"');
$form->setDefaults($defaults); $form->setDefaults($defaults);
$form->display(); $form->display();

@ -18,7 +18,22 @@ if($error=='not_a_learning_path')
{ {
$msg = urlencode(get_lang('UnknownPackageFormat')); $msg = urlencode(get_lang('UnknownPackageFormat'));
}else{ }else{
$msg = urlencode(get_lang('UplUploadSucceeded')); require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
$specific_fields = get_specific_field_list();
foreach ($specific_fields as $specific_field) {
$values = explode(',', trim($_POST[$specific_field['code']]));
if ( !empty($values) ) {
foreach ($values as $value) {
$value = trim($value);
if ( !empty($value) ) {
add_specific_field_value($specific_field['id'], api_get_course_id(), TOOL_LEARNPATH, $oScorm->lp_id, $value);
}
}
}
}
$msg = urlencode(get_lang('UplUploadSucceeded'));
} }
header('location: ../newscorm/lp_controller.php?action=list&dialog_box='.$msg); header('location: ../newscorm/lp_controller.php?action=list&dialog_box='.$msg);
?> ?>

@ -19,6 +19,7 @@ include("../inc/global.inc.php");
require_once(api_get_path(LIBRARY_PATH) . 'fileUpload.lib.php'); require_once(api_get_path(LIBRARY_PATH) . 'fileUpload.lib.php');
require_once(api_get_path(LIBRARY_PATH) . 'events.lib.inc.php'); require_once(api_get_path(LIBRARY_PATH) . 'events.lib.inc.php');
require_once(api_get_path(LIBRARY_PATH) . 'document.lib.php'); require_once(api_get_path(LIBRARY_PATH) . 'document.lib.php');
require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
require_once (api_get_path(LIBRARY_PATH).'formvalidator/FormValidator.class.php'); require_once (api_get_path(LIBRARY_PATH).'formvalidator/FormValidator.class.php');
@ -31,6 +32,13 @@ $form_style= '
background: url("../img/scorm.gif") 0px 0px no-repeat; background: url("../img/scorm.gif") 0px 0px no-repeat;
padding: 2px 0px 2px 22px; padding: 2px 0px 2px 22px;
} }
.sub-form{
width: 25em;
}
.sub-form input{
float:right;
margin-top:-1.2em;
}
#dynamic_div_container{float:left;margin-right:10px;} #dynamic_div_container{float:left;margin-right:10px;}
#dynamic_div_waiter_container{float:left;} #dynamic_div_waiter_container{float:left;}
</style>'; </style>';
@ -40,6 +48,7 @@ $htmlHeadXtra[] = '<script type="text/javascript">
var myUpload = new upload(0); var myUpload = new upload(0);
</script>'; </script>';
$htmlHeadXtra[] = $form_style; $htmlHeadXtra[] = $form_style;
$specific_fields = get_specific_field_list();
if(isset($_POST['convert'])){ if(isset($_POST['convert'])){
$cwdir = getcwd(); $cwdir = getcwd();
@ -50,6 +59,17 @@ if(isset($_POST['convert'])){
{ {
require('../newscorm/lp_upload.php'); require('../newscorm/lp_upload.php');
if(isset($o_ppt) && $first_item_id != 0){ if(isset($o_ppt) && $first_item_id != 0){
foreach ($specific_fields as $specific_field) {
$values = explode(',', trim($_POST[$specific_field['code']]));
if ( !empty($values) ) {
foreach ($values as $value) {
$value = trim($value);
if ( !empty($value) ) {
add_specific_field_value($specific_field['id'], api_get_course_id(), TOOL_LEARNPATH, $o_ppt->lp_id, $value);
}
}
}
}
header('Location: ../newscorm/lp_controller.php?'.api_get_cidreq().'&lp_id='.$o_ppt->lp_id.'&action=view_item&id='.$first_item_id); header('Location: ../newscorm/lp_controller.php?'.api_get_cidreq().'&lp_id='.$o_ppt->lp_id.'&action=view_item&id='.$first_item_id);
} }
else { else {
@ -134,7 +154,7 @@ $form = new FormValidator('upload_ppt', 'POST', '', '');
// build the form // build the form
$form -> addElement ('html','<br>'); $form -> addElement ('html','<br />');
$div_upload_limit = '&nbsp;&nbsp;'.get_lang('UploadMaxSize').' : '.ini_get('post_max_size'); $div_upload_limit = '&nbsp;&nbsp;'.get_lang('UploadMaxSize').' : '.ini_get('post_max_size');
@ -164,18 +184,25 @@ $renderer->setElementTemplate($user_file_template);
$form -> addElement ('file', 'user_file','<img src="../img/powerpoint_big.gif" align="absbottom" />&nbsp;&nbsp;'); $form -> addElement ('file', 'user_file','<img src="../img/powerpoint_big.gif" align="absbottom" />&nbsp;&nbsp;');
$form -> addElement ('checkbox', 'take_slide_name','', get_lang('TakeSlideName')); $form -> addElement ('checkbox', 'take_slide_name','', get_lang('TakeSlideName'));
if(api_get_setting('search_enabled')==='true') if(api_get_setting('search_enabled')=='true')
{ {
$form -> addElement ('checkbox', 'index_document','', get_lang('SearchFeatureDoIndexDocument')); $form -> addElement ('checkbox', 'index_document','', get_lang('SearchFeatureDoIndexDocument'));
$form -> addElement ('text', 'terms', get_lang('SearchFeatureDocumentTagsIfIndexing').': '); //$form -> addElement ('text', 'terms', get_lang('SearchFeatureDocumentTagsIfIndexing').': ');
$form -> addElement ('html', get_lang('SearchFeatureDocumentLanguage').': '. api_get_languages_combo()); $form -> addElement ('html','<br />');
$form -> addElement ('html', get_lang('SearchFeatureDocumentLanguage').': &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'. api_get_languages_combo());
$form -> addElement ('html','<div class="sub-form">');
foreach ($specific_fields as $specific_field) {
$form -> addElement ('text', $specific_field['code'], $specific_field['name'].' : ');
}
$form -> addElement ('html','</div>');
} }
$form -> addElement ('submit', 'convert', get_lang('ConvertToLP'), 'class="convert_button"'); $form -> addElement ('submit', 'convert', get_lang('ConvertToLP'), 'class="convert_button"');
$form -> addElement ('hidden', 'ppt2lp', 'true'); $form -> addElement ('hidden', 'ppt2lp', 'true');
$form -> add_real_progress_bar(md5(rand(0,10000)), 'user_file', 1, true); $form -> add_real_progress_bar(md5(rand(0,10000)), 'user_file', 1, true);
$defaults = array('take_slide_name'=>'checked="checked"'); $defaults = array('take_slide_name'=>'checked="checked"','index_document'=>'checked="checked"');
$form->setDefaults($defaults); $form->setDefaults($defaults);
// display the form // display the form
$form -> display(); $form -> display();

@ -20,6 +20,7 @@ require_once(api_get_path(LIBRARY_PATH) . 'fileUpload.lib.php');
require_once(api_get_path(LIBRARY_PATH) . 'events.lib.inc.php'); require_once(api_get_path(LIBRARY_PATH) . 'events.lib.inc.php');
require_once(api_get_path(LIBRARY_PATH) . 'document.lib.php'); require_once(api_get_path(LIBRARY_PATH) . 'document.lib.php');
require_once (api_get_path(LIBRARY_PATH).'formvalidator/FormValidator.class.php'); require_once (api_get_path(LIBRARY_PATH).'formvalidator/FormValidator.class.php');
require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
$form_style= ' $form_style= '
@ -40,6 +41,7 @@ $htmlHeadXtra[] = '<script type="text/javascript">
var myUpload = new upload(0); var myUpload = new upload(0);
</script>'; </script>';
$htmlHeadXtra[] = $form_style; $htmlHeadXtra[] = $form_style;
$specific_fields = get_specific_field_list();
if(isset($_POST['convert'])){ if(isset($_POST['convert'])){
$cwdir = getcwd(); $cwdir = getcwd();
@ -50,6 +52,17 @@ if(isset($_POST['convert'])){
{ {
require('../newscorm/lp_upload.php'); require('../newscorm/lp_upload.php');
if(isset($o_doc) && $first_item_id != 0){ if(isset($o_doc) && $first_item_id != 0){
foreach ($specific_fields as $specific_field) {
$values = explode(',', trim($_POST[$specific_field['code']]));
if ( !empty($values) ) {
foreach ($values as $value) {
$value = trim($value);
if ( !empty($value) ) {
add_specific_field_value($specific_field['id'], api_get_course_id(), TOOL_LEARNPATH, $o_doc->lp_id, $value);
}
}
}
}
header('Location: ../newscorm/lp_controller.php?'.api_get_cidreq().'&lp_id='.$o_doc->lp_id.'&action=view_item&id='.$first_item_id); header('Location: ../newscorm/lp_controller.php?'.api_get_cidreq().'&lp_id='.$o_doc->lp_id.'&action=view_item&id='.$first_item_id);
} }
else { else {
@ -155,6 +168,17 @@ EOT;
$renderer->setElementTemplate($user_file_template); $renderer->setElementTemplate($user_file_template);
$form -> addElement ('file', 'user_file','<img src="../img/word_big.gif" align="absbottom" />'); $form -> addElement ('file', 'user_file','<img src="../img/word_big.gif" align="absbottom" />');
if (api_get_setting('search_enabled')=='true')
{
$form -> addElement ('checkbox', 'index_document','', get_lang('SearchFeatureDoIndexDocument'));
$form -> addElement ('html','<br />');
$form -> addElement ('html', get_lang('SearchFeatureDocumentLanguage').': &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'. api_get_languages_combo());
$form -> addElement ('html','<div class="sub-form">');
foreach ($specific_fields as $specific_field) {
$form -> addElement ('text', $specific_field['code'], $specific_field['name'].' : ');
}
$form -> addElement ('html','</div>');
}
/* /*
* commented because SplitStepsPerChapter is not stable at all * commented because SplitStepsPerChapter is not stable at all
@ -169,7 +193,7 @@ $form -> addElement ('hidden', 'woogie', 'true');
$form -> add_real_progress_bar(md5(rand(0,10000)), 'user_file', 1, true); $form -> add_real_progress_bar(md5(rand(0,10000)), 'user_file', 1, true);
$defaults['split_steps'] = 'per_page'; $defaults = array('split_steps'=>'per_page','index_document'=>'checked="checked"');
$form -> setDefaults($defaults); $form -> setDefaults($defaults);
// display the form // display the form

Loading…
Cancel
Save