[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. 82
      main/inc/lib/search/DokeosIndexer.class.php
  18. 64
      main/inc/lib/search/DokeosQuery.php
  19. 127
      main/inc/lib/search/IndexableChunk.class.php
  20. 408
      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. 216
      main/inc/lib/search/xapian/XapianIndexer.class.php
  25. 125
      main/inc/lib/search/xapian/XapianQuery.php
  26. 226
      main/inc/lib/specific_fields_manager.lib.php
  27. 7
      main/lang/english/admin.inc.php
  28. 15
      main/lang/english/learnpath.inc.php
  29. 16
      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. 6
      main/newscorm/lp_list.php
  35. 227
      main/newscorm/lp_list_search.php
  36. 72
      main/newscorm/openoffice_presentation.class.php
  37. 55
      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
@ -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="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>
<?php if (api_get_setting('search_enabled')=='true') { ?>
<li><a href="specific_fields.php"><?php echo get_lang('SpecificSearchFields'); ?></a></li>
<?php } ?>
</ul>
</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)
$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__);

@ -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 */
/**
==============================================================================
@ -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_OPTIONS', 'user_field_options');
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_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_LINK', 'link');
define('TOOL_COURSE_DESCRIPTION', 'course_description');
define('TOOL_SEARCH', 'search');
define('TOOL_LEARNPATH', 'learnpath');
define('TOOL_ANNOUNCEMENT', 'announcement');
define('TOOL_FORUM', 'forum');

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

@ -7,19 +7,34 @@ require 'xapian/XapianQuery.php';
/**
* Wrapper for queries
*
*
* @param string $query_string The search string
* @param int $offset Offset to the first item to retrieve. Optional
* @param int lenght Number of items to retrieve. Optional
* @param array extra Extra queries to join with. Optional
* @return array
*/
function dokeos_query_query($query_string, $offset=0, $length=10) {
return xapian_query($query_string, NULL, $offset, $length);
function dokeos_query_query($query_string, $offset=0, $length=10, $extra=NULL) {
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);
}
/**
* Wrapper for getting tags
*
*
* @param int $count Number of terms to retrieve. Optional.
* @return array
*/
@ -29,11 +44,48 @@ function dokeos_query_get_tags($count=100) {
/**
* Wrapper for getting specific document tags
*
*
* @param mixed Document entry, with apropiate class
* @return array
*/
function dokeos_query_tags_for_doc($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
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 */
protected $id;
// in some cases we need top differenciate xapian documents of the same tool
define('SE_DOCTYPE_EXERCISE_EXERCISE', 0);
define('SE_DOCTYPE_EXERCISE_QUESTION', 1);
/* boolean */
public $parent;
// xapian prefixes
define('XAPIAN_PREFIX_COURSEID','C');
define('XAPIAN_PREFIX_TAG','T');
define('XAPIAN_PREFIX_TOOLID','O');
/* int */
public $parentId;
abstract class _IndexableChunk
{
/* struct (array)
* {
@ -18,7 +24,17 @@ abstract class _IndexableChunk
* string ids; <- los flags a guardar "cidReq:lp_id:path"
* }
*/
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(
@ -28,83 +44,70 @@ abstract class _IndexableChunk
*/
public $terms;
/**
* Add a value to the indexed item
* @param string Key
* @param string Value
* @return void
*/
/**
* Add a value to the indexed item
* @param string Key
* @param string Value
* @return void
*/
function addValue($key, $value) {
$this->data[$key] = $value;
}
/**
/**
* Add a term (like xapian definition)
* @param string Term
* @param string Term
* @param string Flag (one character)
*/
function addTerm($term, $flag) {
public function addTerm($term, $flag) {
global $charset;
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
* @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
*/
/**
* Class constructor. Just generates an empty 'data' array attribute
*/
function __construct() {
$this->data = array();
}
/**
* Class desctructor. Unsets attributes.
*/
/**
* Class desctructor. Unsets attributes.
*/
function __destruct() {
unset($this->data);
unset($this->terms);
unset($this->parent);
}
}
/**
* Extension of the _IndexableChunk class to make IndexableChunk extensible.
*/
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,7 +1,20 @@
<?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.
*
*
* Note that $htmlHeadXtra should be passed by reference and not value,
* otherwise this function will have no effect and your form will be broken.
*
@ -13,7 +26,7 @@ function search_widget_prepare(&$htmlHeadXtra) {
.tags {
display: block;
margin-top: 20px;
width: 70%;
width: 90%;
}
.tag {
float: left;
@ -25,93 +38,394 @@ function search_widget_prepare(&$htmlHeadXtra) {
border: 1px solid #ddd;
}
.tag:hover {
background: #ddd;
cursor: pointer;
background: gray;
color:white;
cursor:pointer;
/* font-weight:bold;*/
}
.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 {
background: #fff;
.lower-submit {
float:right;
margin: 0 0.9em 0 0.5em;
}
#tags-clean {
float: right;
}
.sf-select-splitter {
margin-top: 4em;
}
.search-links-box {
background-color: #ddd;
border: 1px solid #888;
padding: 1em;
-moz-border-radius: 0.8em;
}
.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>';
$htmlHeadXtra[] = '
<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[] = "
<script type=\"text/javascript\">
var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
$(document).ready(function() {
$('#dokeos_search').submit(function (e) {
var tags = String();
$('.lighttagcolor').each(function (b, a) {
tags = tags.concat(a.id+',');
});
$('#tag_holder').val(tags);
return true;
$('#dokeos_search').submit(function (e) {
var tags = String();
$('.lighttagcolor').each(function (b, a) {
// tags = tags.concat(a.id +',');
tags += $(this).attr('title') + jQuery.trim($(this).html().replace(".'"\n"'.",'')) + ',';
});
$('#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>";
}
/**
* 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
*
* TODO: reorganize an clean this to reuse code
*
* The form will post to lp_controller.php by default, you can pass a value to
* $action to use a custom action.
* IMPORTANT: you have to call search_widget_prepare() before calling this
* function or otherwise the form will not behave correctly.
*
*
* @param string $action Just in case your action is not
* 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';
$dktags = dokeos_query_get_tags();
$sf_terms = array();
$specific_fields = get_specific_field_list();
$post_tags = array();
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();
if (isset($_REQUEST['tags'])) {
$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');
}
?>
<form id="dokeos_search" action="<?php echo $action ?>"
$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.'?mode='.htmlentities($_GET['mode']) ?>"
method="get">
<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") ?>" />
<br/>
<h2><?php echo get_lang("Tags") ?></h2>
<!--span id="keywords" style="font-size:12px;font-weight:bold"><?php echo get_lang("Keywords") ?>:</span-->
<span id="key_wrapper"><?php echo $tags_list ?></span>
<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
foreach ($dktags as $tag)
{
$tag = trim($tag['name'], 'T ');
$tag = str_replace(' ', '_', $tag);
$color = "";
if ($filter) {
if (array_search($tag, $post_tags) !== FALSE)
$color = "lighttagcolor";
}
?>
<span class="tag <?php echo $color?>" id="<?php echo $tag ?>">
<?php echo $tag ?></span>
<script type="text/javascript">
$('#<?php echo $tag ?>').click(function waaa (e) {
if ( $('.lighttagcolor').size() < 3) {
$('#<?php echo $tag ?>').toggleClass('lighttagcolor');
} //*/
// Process each prefix type term
echo '<table><tr>';
$i = 0;
$max = count($sf_terms);
foreach ($sf_terms as $sf_term_array) {
//$multiple_select = '<div style="float:left;">';
$multiple_select = '';
//sorting the array of tags and keywords
if ($i>0) {
//print "+" image
$multiple_select .= '<td><img class="sf-select-splitter" src="../img/search-big-plus.gif" alt="plus-sign-decoration"/></td>';
}
$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 {
$('#<?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
}
$('#<?php echo $tag ?>').toggleClass('lighttagcolor');
} 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>
</form>
<br style="clear: both;"/>
<?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
require 'xapian.php';
<?php
require_once 'xapian.php';
require_once dirname(__FILE__) . '/../IndexableChunk.class.php';
/**
* Abstract helper class
@ -7,21 +8,20 @@ require 'xapian.php';
abstract class XapianIndexer {
/* XapianWritableDatabase */
protected $db;
protected $parents;
/* IndexableChunk[] */
protected $chunks;
protected $chunks;
/* XapianTermGenerator */
public $indexer;
public $indexer;
/* XapianStem */
public $stemmer;
/**
* Generates a list of languages Xapian manages
*
* This method enables the definition of more matches between
* Dokeos languages and Xapian languages (through hardcoding)
* @return array Array of languages codes -> Xapian languages
*/
public $stemmer;
/**
* Generates a list of languages Xapian manages
*
* This method enables the definition of more matches between
* Dokeos languages and Xapian languages (through hardcoding)
* @return array Array of languages codes -> Xapian languages
*/
public final function xapian_languages() {
/* http://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html */
return array(
@ -46,11 +46,13 @@ 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') {
if ($dbMode == NULL)
if ($this->db != NULL)
return $this->db;
if ($dbMode == NULL)
$dbMode = Xapian::DB_CREATE_OR_OPEN;
if ($path == NULL)
@ -63,7 +65,7 @@ abstract class XapianIndexer {
if (!in_array($lang, $this->xapian_languages())) {
$lang = 'english';
}
$this->stemmer = new XapianStem($lang);
$this->indexer->set_stemmer($this->stemmer);
@ -74,67 +76,49 @@ abstract class XapianIndexer {
return 1;
}
}
/**
* Simple getter for the db attribute
* @return object The db attribute
* @return object The db attribute
*/
function getDb() {
return $this->db;
}
/**
* Add this chunk to the chunk array attribute
* @param string Chunk of text
* @return void
*/
/**
* Add this chunk to the chunk array attribute
* @param string Chunk of text
* @return void
*/
function addChunk($chunk) {
/*
if ($chunk->parent) {
$this->parents[] = $chunk;
} else {
$this->chunks[] = $chunk;
}
*/
$this->chunks[] = $chunk;
}
/**
* 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() {
try {
foreach ($this->chunks as $chunk) {
$doc = new XapianDocument();
$this->indexer->set_document($doc);
foreach ($chunk->terms as $term) {
/* FIXME: think of getting weight */
$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) {
//if text is empty, we don't index (because it triggers a Xapian error)
if ($key != 'ids' && !empty($value))
$this->indexer->index_text($value, 1);
$this->indexer->index_text($value, 1);
}
/* Hard-coded approach */
/*
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);
$doc->set_data($chunk->xapian_data, 1);
$did = $this->db->add_document($doc);
//write to disk
$this->db->flush();
@ -152,13 +136,43 @@ abstract class XapianIndexer {
* Get a specific document from xapian db
*
* @param int did Xapian::docid
* @return XapianDocument
* @return mixed XapianDocument, or false on error
*/
function get_document($did) {
if ($path == NULL) {
if ($this->db == NULL) {
$this->connectDb();
}
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();
}
return $this->db->get_document($did);
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 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);
if($doc===false){return false;}
$doc->clear_terms();
foreach ($terms as $term) {
//add directly
@ -176,6 +192,7 @@ abstract class XapianIndexer {
}
$this->db->replace_document($did, $doc);
$this->db->flush();
return true;
}
/**
@ -184,16 +201,85 @@ abstract class XapianIndexer {
* @param int did Xapian::docid
*/
function remove_document($did) {
if ($path == NULL) {
if ($this->db == NULL) {
$this->connectDb();
}
$this->db->delete_document($did);
$this->db->flush();
if (is_numeric($did) && $did>0) {
$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() {
$this->db = NULL;
$this->stemmer = NULL;

@ -1,58 +1,68 @@
<?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/');
/**
* Queries the database.
* Queries the database.
* The xapian_query function queries the database using both a query string
* and application-defined terms. Based on drupal-xapian
*
*
* @param string $query_string The search string. This string will
* be parsed and stemmed automatically.
* @param XapianDatabase $db Xapian database to connect
* @param int $start An integer defining the first
* @param int $start An integer defining the first
* document to return
* @param int $length The number of results to return.
* @param array $extra An array containing arrays of
* @param array $extra An array containing arrays of
* extra terms to search for.
* @param int $count_type Number of items to retrieve
* @return array An array of nids corresponding to the results.
*/
function xapian_query($query_string, $db = NULL, $start = 0, $length = 10,
function xapian_query($query_string, $db = NULL, $start = 0, $length = 10,
$extra = array(), $count_type = 0) {
try {
if (!is_object($db)) {
$db = new XapianDatabase(XAPIAN_DB);
}
$enquire = new XapianEnquire($db);
$query_parser = new XapianQueryParser();
$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('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));
// Build subqueries from $extra array. Now only used by tags search filter on search widget
$subqueries = array();
foreach ($extra as $subquery) {
if (!empty($subquery)) {
$subqueries[] = new XapianQuery($subquery);
}
}
$query = NULL;
$enquire = new XapianEnquire($db);
if (!empty($query_string)) {
$query_parser = new XapianQueryParser();
//TODO: choose stemmer
$stemmer = new XapianStem("english");
$query_parser->set_stemmer($stemmer);
$query_parser->set_database($db);
$query_parser->set_stemming_strategy(XapianQueryParser::STEM_SOME);
$query_parser->add_boolean_prefix('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);
$matches = $enquire->get_mset((int)$start, (int)$length);
$specific_fields = get_specific_field_list();
$results = array();
$i = $matches->begin();
$count = 0;
@ -60,9 +70,21 @@ function xapian_query($query_string, $db = NULL, $start = 0, $length = 10,
$count++;
$document = $i->get_document();
if (is_object($document)) {
$results[$count]->ids = ($document->get_data());
$results[$count]->score = ($i->get_percent());
$results[$count]->terms = xapian_get_doc_terms($document);
// process one item terms
$courseid_terms = xapian_get_doc_terms($document, XAPIAN_PREFIX_COURSEID);
$results[$count]['courseid'] = substr($courseid_terms[0]['name'], 1);
$toolid_terms = xapian_get_doc_terms($document, XAPIAN_PREFIX_TOOLID);
$results[$count]['toolid'] = substr($toolid_terms[0]['name'], 1);
// process each specific field prefix
foreach ($specific_fields as $specific_field) {
$results[$count]['sf-'.$specific_field['code']] = xapian_get_doc_terms($document, $specific_field['code']);
}
// rest of data
$results[$count]['xapian_data'] = unserialize($document->get_data());
$results[$count]['score'] = ($i->get_percent());
$results[$count]['tags'] = xapian_get_doc_terms($document);
}
$i->next();
}
@ -71,7 +93,7 @@ function xapian_query($query_string, $db = NULL, $start = 0, $length = 10,
case 1: // Lower bound
$count = $matches->get_matches_lower_bound();
break;
case 2: // Upper bound
$count = $matches->get_matches_upper_bound();
break;
@ -90,15 +112,21 @@ 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
*
*
* @param int $count Number of terms to retrieve
* @param char $prefix The prefix of the term to retrieve
* @param XapianDatabase $db Xapian database to connect
* @return array
*/
function xapian_get_all_terms($count=0, $prefix='T', $db=NULL) {
function xapian_get_all_terms($count=0, $prefix=XAPIAN_PREFIX_TAG, $db=NULL) {
try {
if (!is_object($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
* @return array
*/
function xapian_get_doc_terms($doc=NULL, $prefix='T') {
function xapian_get_doc_terms($doc=NULL, $prefix=XAPIAN_PREFIX_TAG) {
try {
if (!is_a($doc, 'XapianDocument')) {
return;
@ -161,4 +189,35 @@ function xapian_get_doc_terms($doc=NULL, $prefix='T') {
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";
$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.";
?>
$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";
$UplUnableToSaveFile = "Unable to save file";
$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['ukrainian'] = "ukrainian";
$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
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
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__);
// remove from search engine if enabled
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');
$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 Prerequisites (optional)
* @param string Indexing terms (optional)
* @param array The array resulting of the $_FILES[mp3] element
* @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(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__);
$row_select = Database::fetch_array($res_select);
$terms_update_sql='';
if (!is_null($terms)) {
//TODO: validate csv string
$terms_update_sql = ", terms = '". $this->escape_string(htmlentities($terms)) . "'";
// save it to search engine
if (api_get_setting('search_enabled') == 'true') {
require_once(api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php');
$di = new DokeosIndexer();
$di->update_terms($row_select['search_did'], explode(',', $terms));
$audio_update_sql = '';
if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error']===0) {
//upload file in documents
$pi = pathinfo($audio['name']);
if ($pi['extension'] == 'mp3') {
$c_det = api_get_course_info($this->cc);
$bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
$path = handle_uploaded_document($c_det,$audio,$bp,'/audio',api_get_user_id(),0,null,'',0,'rename',false,0);
$path = substr($path,7);
//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)) . "',
prerequisite = '".$prerequisites."',
description = '" . $this->escape_string(htmlentities($description)) . "'
". $terms_update_sql . "
". $audio_update_sql . "
WHERE id = " . $id;
$res_update = api_sql_query($sql_update, __FILE__, __LINE__);
}
@ -1094,7 +1109,7 @@ class learnpath {
previous_item_id = " . $previous . ",
next_item_id = " . $new_next . ",
display_order = " . $new_order . "
". $terms_update_sql . "
". $audio_update_sql . "
WHERE id = " . $id;
$res_update_next = api_sql_query($sql_update, __FILE__, __LINE__);
//echo '<p>' . $sql_update . '</p>';
@ -1375,36 +1390,25 @@ class learnpath {
}
/**
* Get the index terms shared between all items of this path
* @return string String of terms, split by a simple comma
* Get the specific prefix index terms of this learning path
* @return array Array of terms
*/
function get_common_index_terms()
function get_common_index_terms_by_prefix($prefix)
{
$terms = array();
$i = 0;
foreach ( $this->items as $item ) {
$i_terms = split(',',$item->terms);
if ( $i == 0 ) { $terms = $i_terms; continue; }
require_once api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php';
$terms = get_specific_field_values_list_by_prefix($prefix, $this->cc, TOOL_LEARNPATH, $this->lp_id);
$prefix_terms = array();
foreach($terms as $term)
{
if ( !in_array($term,$i_terms) )
{
$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)
}
}
$prefix_terms[] = $term['value'];
}
$s = implode(',',$terms);
return $s;
return $prefix_terms;
}
/**
* Gets the number of items currently completed
* @return integer The number of items currently completed
*/
function get_complete_items_count()
{
@ -3021,6 +3025,20 @@ class learnpath {
}
}
/**
* 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
* @param string Message to log
* @return boolean True on success, false on error or if msg empty
@ -3661,20 +3679,80 @@ class learnpath {
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 char Xapian term prefix
* @return boolean False on error, true otherwise
*/
function set_terms($terms) {
if ( empty($terms) ) return false;
if ( trim($terms) == trim($this->get_common_index_terms()) ) return false; //indexing is costly, don't re-index if no change
function set_terms_by_prefix($terms_string, $prefix) {
if (api_get_setting('search_enabled') !== 'true')
return FALSE;
$terms_string = trim($terms_string);
$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();
foreach ( $this->items as $item ) {
$item->set_terms($terms);
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;
}
/**
* Sets the theme of the LP (local/remote) (and save)
* @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->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' )
{
//add terms field
$terms = $form->addElement('text','terms', get_lang('SearchFeatureTerms').'&nbsp;:','id="idTerms" class="learnpath_item_form"');
$terms->setValue($item_terms);
}
*/
$arrHide=array();
@ -8836,30 +8916,13 @@ EOD;
}
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,
* then reload the learning path object with the user and it will offer
* a new attempt.
* @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;
}
if (!function_exists('trim_value')) {
function trim_value(&$value) {
$value = trim($value);
}
}
?>
?>

@ -12,6 +12,7 @@
*/
class learnpathItem{
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 $condition; //if this item has a special condition embedded
var $current_score;
@ -49,12 +50,11 @@ class learnpathItem{
var $prevent_reinit = 1;
var $ref;
var $save_on_close = true;
var $search_did = NULL;
var $status;
var $title;
var $type; // this attribute can contain chapter|link|student_publication|module|quiz|document|forum|thread
var $view_id;
var $terms;
var $search_did;
var $debug = 0; //logging param
/**
@ -100,10 +100,20 @@ class learnpathItem{
if(isset($row['launch_data'])){
$this->launch_data = $row['launch_data'];
}
$this->terms = $row['terms'];
$this->search_did = $row['search_did'];
$this->save_on_close = true;
$this->save_on_close = true;
$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);
return true;
@ -199,10 +209,25 @@ class learnpathItem{
$lp_item = Database::get_course_table('lp_item');
$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);
$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;
//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;
}
/**
@ -1054,20 +1079,15 @@ class learnpathItem{
}
return $scorm_time;
}
/**
* Returns search index's internal ID
* @return int The indexing engine's internal ID for this lp-item
*/
function get_search_id() {
return $this->search_did;
}
/**
* Returns index terms
* @return string Comma-separated list of index terms
*/
function get_terms() {
return $this->terms;
}
function get_terms()
{
$lp_item = Database::get_course_table(TABLE_LP_ITEM);
$sql = "SELECT * FROM $lp_item WHERE id='".Database::escape_string($this->db_id)."'";
$res = api_sql_query($sql,__FILE__,__LINE__);
$row = Database::fetch_array($res);
return $row['terms'];
}
/**
* Returns the item's title
* @return string Title
@ -1939,9 +1959,9 @@ class learnpathItem{
return false;
}
/**
* Sets new index terms for item
* @param string Comma-separated list of terms
* @return boolean False on error, true otherwise
* Set the terms for this learnpath item
* @param string Terms, as a comma-split list
* @return boolean Always return true
*/
function set_terms($terms) {
$lp_item = Database::get_course_table(TABLE_LP_ITEM);
@ -1957,14 +1977,22 @@ class learnpathItem{
//TODO: validate csv string
$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__);
// save it to search engine
if (api_get_setting('search_enabled') == 'true') {
$di = new DokeosIndexer();
$di->update_terms($this->get_search_id(), $new_terms);
$di->update_terms($this->get_search_did(), $new_terms);
}
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
* @param string Time as given by SCORM

@ -366,7 +366,7 @@ switch($action)
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']))
{
@ -577,21 +577,24 @@ switch($action)
$_SESSION['oLP']->set_name($_REQUEST['lp_name']);
$author= $_REQUEST['lp_author'];
//fixing the author name (no body or html tags)
if( stripos($author) != 0 ) {
$len = strripos($author,'</p>')-stripos($author,'<p>');
$author_fixed=substr($author,stripos($author,'<p>'), $len+4);
$auth_init = stripos($author,'<p>');
if ( $auth_init === false ) {
$auth_init = stripos($author,'<body>');
$auth_end = $auth_init + stripos(substr($author,$auth_init+6),'</body>') + 7;
$len = $auth_end - $auth_init +6;
} else {
$len = strripos($author,'</body>')-stripos($author,'<body>');
$author_fixed=substr($author,stripos($author,'<body>'), $len+7);
$auth_end = strripos($author,'</p>');
$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_encoding($_REQUEST['lp_encoding']);
$_SESSION['oLP']->set_maker($_REQUEST['lp_maker']);
$_SESSION['oLP']->set_proximity($_REQUEST['lp_proximity']);
$_SESSION['oLP']->set_theme($_REQUEST['lp_theme']);
$_SESSION['oLP']->set_terms($_REQUEST['lp_terms']);
if ($_REQUEST['remove_picture'])
{
$_SESSION['oLP']->delete_lp_image();
@ -600,6 +603,27 @@ switch($action)
if ($_FILES['lp_preview_image']['size']>0)
$_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');
}
break;

@ -6,29 +6,29 @@
*/
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
$nameTools = get_lang("Doc");
event_access_tool(TOOL_LEARNPATH);
if (! $is_allowed_in_course) api_not_allowed();
$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());
Display::display_header(null,'Path');
//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['Height'] = '150px';
$fck_attribute['ToolbarSet'] = 'Comment';
$defaults=array();
$form = new FormValidator('form1', 'post', 'lp_controller.php');
//Title
$form -> addElement('text', 'lp_name', ucfirst(get_lang('_title')));
$form -> addElement('text', 'lp_name', ucfirst(get_lang('_title')),array('size'=>47));
//Encoding
$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->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
$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'));
// 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'));
$defaults['lp_terms'] = $_SESSION['oLP']->get_common_index_terms();
$specific_fields = get_specific_field_list();
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
@ -127,19 +139,16 @@ $defaults['lp_name']=$_SESSION['oLP']->get_name();
$defaults['lp_author']=$_SESSION['oLP']->get_author();
//Submit button
$form->addElement('submit', 'Submit', get_lang('Ok'));
$form->addElement('submit', 'Submit', get_lang('SaveLPSettings'));
//Hidden fields
$form->addElement('hidden', 'action', 'update_lp');
$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();
echo '</td><td valign="top">';
echo Display::display_icon('help_course_authoring.png','');
echo '</td></tr></table>';
echo '</div>';
echo '</td><td valign="top"><img src="../img/CourseSettingsPageLayout.png" /></td></tr></table>';
Display::display_footer();
?>

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

@ -6,7 +6,8 @@
*/
require api_get_path(LIBRARY_PATH).'search/search_widget.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[] = '
<style type="text/css">
@ -23,7 +24,7 @@ $htmlHeadXtra[] = '
.doc_img img {
width: 120px;
}
.doc_text,
.doc_text,
.doc_title {
padding-left: 10px;
vertical-align: top;
@ -33,13 +34,25 @@ $htmlHeadXtra[] = '
font-weight: bold;
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>';
search_widget_prepare(&$htmlHeadXtra);
event_access_tool(TOOL_LEARNPATH);
$interbreadcrumb[]= array ("url"=>"./lp_controller.php?action=list", "name"=> get_lang(ucfirst(TOOL_LEARNPATH)));
event_access_tool(TOOL_SEARCH);
$interbreadcrumb[]= array ("url"=>"./index.php", "name"=> get_lang('LectureLibrary'));//get_lang(ucfirst(TOOL_SEARCH))
Display::display_header(null,'Path');
if (api_get_setting('search_enabled') !== 'true') {
@ -51,118 +64,105 @@ else
search_widget_show(empty($search_action)?null:'index.php');
}
/**
* Utility function to get a table for a specific course.
*
* @param string $course_code The course_code as in cidReq
* @param string $table The desired table, this is just concat'd.
* @return string
*/
function get_course_table($course_code, $table) {
$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;
$tag_array = array();
$specific_fields = get_specific_field_list();
foreach ($specific_fields as $specific_field) {
if (isset($_REQUEST[ 'sf_'. $specific_field['code'] ])) {
$values = $_REQUEST[ 'sf_'. $specific_field['code'] ];
foreach ($values as $term) {
if (!empty($term)) {
$prefix = $specific_field['code'];
$tag_array[] = dokeos_get_boolean_query($prefix . $term);
}
}
}
}
$tags = explode(",", trim($_REQUEST['tags']));
$tags2 = '';
$query = $_REQUEST['query'];
$query = stripslashes(htmlspecialchars_decode($query,ENT_QUOTES));
foreach ($tags as $tag)
if (strlen($tag)> 1)
$tags2 .= " tag:".trim($tag);
$op = 'or';
if (!empty($_REQUEST['operator']) && $_REQUEST['operator'] == 'and') {
$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);
$count = $query_results[0];
$results = $query_results[1];
list($count, $results) = dokeos_query_query(mb_convert_encoding($query,'UTF-8',$charset), 0, 1000, $fixed_queries);
$blocks = array();
$url = api_get_path(WEB_CODE_PATH)."/newscorm/lp_controller.php";
$search_url = sprintf('%s?action=search&query=%s&tags=%s',
$url, $_REQUEST['query'], $_REQUEST['tags']);
$search_url = sprintf('%s?action=search&query=%s',
$url, $_REQUEST['query']);
$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) {
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 = '';
foreach ($result->terms as $term) {
$tags .= trim($term['name'], 'T') . ", ";
$tags .= $term['name'] . ", ";
}
//remove trailing comma
$tags = substr($tags,0,-2);
$lpi_table = get_course_table($course_id, TABLE_LP_ITEM);
$lp_table = get_course_table($course_id, TABLE_LP_MAIN);
$doc_table = get_course_table($course_id, TABLE_DOCUMENT);
$sql = "SELECT
$doc_table.title, $doc_table.path, $lpi_table.id,
$lp_table.name, $lp_table.author
FROM $lp_table, $lpi_table INNER JOIN $doc_table
ON $lpi_table.path = $doc_table.id
WHERE
$lpi_table.lp_id = $lp_id
AND
$doc_table.id = $doc_id
AND
$lp_table.id = $lpi_table.lp_id
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 (!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>';
}
// Fill the result array
if (empty($result['thumbnail'])) { // or !file_exists('../../courses/'.api_get_course_path($course_id)."/document/".$img_path)
$result['thumbnail'] = '../img/no_document_thumb.jpg';
}
if ($mode == 'gallery') {
$blocks[] = array(
//'<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>',
$a_prefix .'<img src="'.$result['thumbnail'].'" />'. $a_sufix .'<br />'.$title.'<br />'.$result['author'],
//$title,
);
} else {
$blocks[] = array(
$title,
);
}
}
}
if (count($blocks) < 1) {
Display::display_normal_message(get_lang('SearchFeatureSearchExplanation'), FALSE);
// Display::display_normal_message(get_lang('SearchFeatureSearchExplanation'), FALSE);
}
else
{
@ -171,18 +171,41 @@ else
}
$s = new SortableTableFromArray($blocks);
$s->additional_parameters = array(
'action' => 'search',
'query' => $_REQUEST['query'],
'tags' => $_REQUEST['tags'],
);
$s->set_header(0, get_lang('Preview'),false,array('width="300px"'));
$s->set_header(1, get_lang('Title'));
$s->set_header(2, get_lang('Tags'));
$s->set_header(3, get_lang('Learning path'));
$s->set_column_filter(3,'to_link');
$s->display_mode = $mode;//default
$s->display_mode_params = 3;
$s->per_page = 9;
$additional_parameters = array(
'mode' => $mode,
'action' => 'search',
'query' => $_REQUEST['query'],
);
$get_params = '';
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();
echo '</div>';
}
Display::display_footer();
?>
?>

@ -12,12 +12,10 @@
* @package dokeos.learnpath.openofficedocument
*/
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).'search/IndexableChunk.class.php');
require_once(api_get_path(LIBRARY_PATH) . 'specific_fields_manager.lib.php');
class OpenofficePresentation extends OpenofficeDocument {
public $take_slide_name;
@ -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);
api_item_property_update($_course,TOOL_DOCUMENT,$document_id,'DocumentAdded',api_get_user_id(),0,0);
// Generating the thumbnail
$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
list($width, $height) = getimagesize($image);
$thumb_width = 300;
@ -79,10 +82,15 @@ class OpenofficePresentation extends OpenofficeDocument {
// resize
imagecopyresized($thumb, $source, 0, 0, 0, 0, $thumb_width, $thumb_height, $width, $height);
// 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);
/*
// 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
$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);
@ -111,40 +119,52 @@ class OpenofficePresentation extends OpenofficeDocument {
}
}
// 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));
$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);
if (isset($_POST['terms'])) {
foreach (explode(',', Database::escape_string($_POST['terms'])) as $term){
$ic_slide->addTerm(trim($term),'T');
}
$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']);
}
}
}
}
$slide_body = $all_specific_terms .' '. $slide_body;
$ic_slide->addValue("content", $slide_body);
/* FIXME: cidReq:lp_id:doc_id al indexar */
// add a comment to say terms separated by commas
$courseid=api_get_course_id();
$ic_slide->addTerm($courseid,'C');
//TODO: add dokeos tool type instead of filetype
$ic_slide->addCourseId($courseid);
$ic_slide->addToolId(TOOL_LEARNPATH);
$lp_id = $this->lp_id;
// TODO: get "path" field
$ic_slide->addValue('ids', $courseid .':'. $this->lp_id
.':'.$document_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_lp_item = Database::get_course_table('lp_item');
$sql_update = "
UPDATE " . $tbl_lp_item . "
SET search_did = " . $did . ",
terms = '".Database::escape_string($_POST['terms'])."'
WHERE id = " . $previous;
api_sql_query($sql_update, __FILE__, __LINE__);
$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__);
}
}
}

@ -11,6 +11,9 @@
* @package dokeos.learnpath.openofficedocument
*/
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 {
@ -201,7 +204,9 @@ class OpenOfficeTextDocument extends OpenofficeDocument {
fclose($handle);
$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){
//put the document in item_property update
@ -213,6 +218,54 @@ class OpenOfficeTextDocument extends OpenofficeDocument {
if($this->first_item == 0){
$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);
//update 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
* bootstraps dokeos api enough to make lp_list_search work.
*/
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).'debug.lib.inc.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).'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';
?>

@ -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");
//show the 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');
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;"');
@ -58,6 +60,15 @@ $select_content_proximity = &$form->addElement('select','content_proximity',get_
$select_content_proximity->addOption(get_lang('Remote'),"remote");
$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('html', '<br><br><br>');
@ -77,6 +88,7 @@ else{
$form->add_real_progress_bar('uploadScorm','user_file');
$defaults = array('index_document'=>'checked="checked"');
$form->setDefaults($defaults);
$form->display();

@ -18,7 +18,22 @@ if($error=='not_a_learning_path')
{
$msg = urlencode(get_lang('UnknownPackageFormat'));
}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);
?>

@ -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) . 'events.lib.inc.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');
@ -31,6 +32,13 @@ $form_style= '
background: url("../img/scorm.gif") 0px 0px no-repeat;
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_waiter_container{float:left;}
</style>';
@ -40,6 +48,7 @@ $htmlHeadXtra[] = '<script type="text/javascript">
var myUpload = new upload(0);
</script>';
$htmlHeadXtra[] = $form_style;
$specific_fields = get_specific_field_list();
if(isset($_POST['convert'])){
$cwdir = getcwd();
@ -50,6 +59,17 @@ if(isset($_POST['convert'])){
{
require('../newscorm/lp_upload.php');
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);
}
else {
@ -134,7 +154,7 @@ $form = new FormValidator('upload_ppt', 'POST', '', '');
// build the form
$form -> addElement ('html','<br>');
$form -> addElement ('html','<br />');
$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 ('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 ('text', 'terms', get_lang('SearchFeatureDocumentTagsIfIndexing').': ');
$form -> addElement ('html', get_lang('SearchFeatureDocumentLanguage').': '. api_get_languages_combo());
$form -> addElement ('checkbox', 'index_document','', get_lang('SearchFeatureDoIndexDocument'));
//$form -> addElement ('text', 'terms', get_lang('SearchFeatureDocumentTagsIfIndexing').': ');
$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 ('hidden', 'ppt2lp', '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);
// display the form
$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) . 'document.lib.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= '
@ -40,6 +41,7 @@ $htmlHeadXtra[] = '<script type="text/javascript">
var myUpload = new upload(0);
</script>';
$htmlHeadXtra[] = $form_style;
$specific_fields = get_specific_field_list();
if(isset($_POST['convert'])){
$cwdir = getcwd();
@ -50,6 +52,17 @@ if(isset($_POST['convert'])){
{
require('../newscorm/lp_upload.php');
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);
}
else {
@ -155,6 +168,17 @@ EOT;
$renderer->setElementTemplate($user_file_template);
$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
@ -169,7 +193,7 @@ $form -> addElement ('hidden', 'woogie', '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);
// display the form

Loading…
Cancel
Save