Juan Carlos Raña %!s(int64=13) %!d(string=hai) anos
pai
achega
8d965d673d

BIN=BIN
main/document/Wami.swf


+ 11 - 3
main/document/document.php

@@ -505,7 +505,7 @@ foreach ($docs_and_folders  as $file) {
                                 //errorAlerts: true,
                                 //warningAlerts: true,
                                 swfPath: "'.$js_path.'jquery-jplayer",
-                                //supplied: "m4a, oga, mp3, ogg, wav",
+                                supplied: "m4a, oga, mp3, ogg, wav",
                                 wmode: "window",
                                 //solution: "flash, html",  // Do not change this setting otherwise 
                                 cssSelectorAncestor: "#jp_container_'.$count.'", 
@@ -1073,7 +1073,7 @@ if ($is_allowed_to_edit || $group_member_with_upload_rights || is_my_shared_fold
 		<?php
 		}		
 		
-		// Record new audio
+		// Record new audio nanogong
 		if (api_get_setting('enable_nanogong') == 'true') {
 		?>
 			<a href="record_audio.php?<?php echo api_get_cidreq(); ?>&id=<?php echo $document_id.$req_gid; ?>">
@@ -1081,7 +1081,15 @@ if ($is_allowed_to_edit || $group_member_with_upload_rights || is_my_shared_fold
 		<?php
 		}		
 		
-		// Create new audio
+		// Record new audio wami record
+		if (api_get_setting('enable_wami_record') == 'true') {
+		?>
+			<a href="record_audio_wami.php?<?php echo api_get_cidreq(); ?>&id=<?php echo $document_id.$req_gid; ?>">
+		   	<?php Display::display_icon('new_recording.png', get_lang('RecordMyVoice'),'',ICON_SIZE_MEDIUM); ?></a>
+		<?php
+		}	
+		
+		// Create new audio from text
 		if (api_get_setting('enabled_text2audio') == 'true'){
 		?>
 			<a href="create_audio.php?<?php echo api_get_cidreq(); ?>&id=<?php echo $document_id.$req_gid; ?>">

+ 6 - 3
main/document/record_audio.php

@@ -135,15 +135,18 @@ function submitVoice() {
 	var lang_give_a_title="<?php echo get_lang('NanogongGiveTitle'); ?>";
 	var lang_failled_to_submit="<?php echo get_lang('NanogongFailledToSubmit'); ?>";
 	var lang_submitted="<?php echo get_lang('NanogongSubmitted'); ?>";
-	
+	// user and group id
+	var nano_user_id="<?php echo api_get_user_id(); ?>";
+	var nano_group_id="<?php echo api_get_group_id(); ?>";
+	var nano_session_id="<?php echo api_get_session_id(); ?>";
 	//path, url and filename
-	var filename = document.getElementById("audio_title").value+".wav";	
+	var filename = document.getElementById("audio_title").value+"_chnano_.wav";//adding name file, tag and extension
 	var filename = filename.replace(/\s/g, "_");//replace spaces by _
 	var filename = encodeURIComponent(filename);	
 	var filepath="<?php echo urlencode($filepath); ?>";
 	var dir="<?php echo urlencode($dir); ?>";
 	var course_code="<?php echo urlencode($course_code); ?>";
-	var urlnanogong="../inc/lib/nanogong/receiver.php?filename="+filename+"&filepath="+filepath+"&dir="+dir+"&course_code="+course_code;	
+	var urlnanogong="../inc/lib/nanogong/receiver.php?filename="+filename+"&filepath="+filepath+"&dir="+dir+"&course_code="+course_code+"&nano_group_id="+nano_group_id+"&nano_session_id="+nano_session_id+"&nano_user_id="+nano_user_id;
 	
 	//check	
 	var recorder

+ 199 - 0
main/document/record_audio_wami.php

@@ -0,0 +1,199 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ *	This file allows record wav files.
+ *
+ *	@package chamilo.document
+ *
+ * @author Juan Carlos Raña Trabado herodoto@telefonica.net
+ * @since 5/april/2012
+*/
+/**
+ * Code
+ */
+
+/*	INIT SECTION */
+
+// Name of the language file that needs to be included
+$language_file = array('document');
+
+require_once '../inc/global.inc.php';
+
+$_SESSION['whereami'] = 'document/voicerecord';
+$this_section = SECTION_COURSES;
+
+require_once api_get_path(SYS_CODE_PATH).'document/document.inc.php';
+require_once api_get_path(LIBRARY_PATH).'groupmanager.lib.php';
+
+$nameTools = get_lang('VoiceRecord');
+
+api_protect_course_script();
+api_block_anonymous_users();
+
+$document_data = DocumentManager::get_document_data_by_id($_GET['id'], api_get_course_id(), true);
+if (empty($document_data)) {
+    if (api_is_in_group()) {
+        $group_properties   = GroupManager::get_group_properties(api_get_group_id());        
+        $document_id        = DocumentManager::get_document_id(api_get_course_info(), $group_properties['directory']);
+        $document_data      = DocumentManager::get_document_data_by_id($document_id, api_get_course_id());
+    }
+}
+
+$document_id = $document_data['id'];
+$dir = $document_data['path'];
+
+//make some vars
+$wamidir=$dir;
+if($wamidir=="/"){
+ $wamidir="";
+}
+$wamiurlplay=api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document'.$wamidir."/";
+
+
+$is_allowed_to_edit = api_is_allowed_to_edit(null, true);
+
+// Please, do not modify this dirname formatting
+
+if (strstr($dir, '..')) {
+	$dir = '/';
+}
+
+if ($dir[0] == '.') {
+	$dir = substr($dir, 1);
+}
+
+if ($dir[0] != '/') {
+	$dir = '/'.$dir;
+}
+
+if ($dir[strlen($dir) - 1] != '/') {
+	$dir .= '/';
+}
+
+$filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
+
+if (!is_dir($filepath)) {
+	$filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
+	$dir = '/';
+}
+//make some vars
+//$waminame="audiorecord.wav";
+$wamiuserid=api_get_user_id();
+$userinfo=Database::get_user_info_from_id($wamiuserid);
+$waminame = api_get_person_name($userinfo['firstname'], $userinfo['lastname']).'.wav';
+$waminame = replace_dangerous_char($waminame, 'strict');
+
+
+$waminame_noex=basename($waminame, ".wav");
+$dirBaseDocuments = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';
+$saveDir=$dirBaseDocuments.$wamidir;
+if (file_exists($saveDir.'/'.$waminame)){ 
+		$i = 1;		
+		while (file_exists($saveDir.'/'.$waminame_noex.'_'.$i.'.wav')) $i++;
+		$waminame = $waminame_noex . '_' . $i . '.wav';
+}
+
+//groups //TODO: clean
+if (isset ($_SESSION['_gid']) && $_SESSION['_gid'] != 0) {
+	$req_gid = '&amp;gidReq='.$_SESSION['_gid'];
+	$interbreadcrumb[] = array ("url" => "../group/group_space.php?gidReq=".$_SESSION['_gid'], "name" => get_lang('GroupSpace'));
+	$noPHP_SELF = true;
+	$to_group_id = $_SESSION['_gid'];
+	$group = GroupManager :: get_group_properties($to_group_id);
+	$path = explode('/', $dir);
+	if ('/'.$path[1] != $group['directory']) {
+		api_not_allowed(true);
+	}
+}
+
+$interbreadcrumb[] = array ("url" => "./document.php?id=".$document_id.$req_gid, "name" => get_lang('Documents'));
+
+if (!$is_allowed_in_course) {
+	api_not_allowed(true);
+}
+
+if (!($is_allowed_to_edit || $_SESSION['group_member_with_upload_rights'] || is_my_shared_folder(api_get_user_id(), Security::remove_XSS($dir),api_get_session_id()))) {
+	api_not_allowed(true);
+}
+
+
+/*	Header */
+event_access_tool(TOOL_DOCUMENT);
+
+$display_dir = $dir;
+if (isset ($group)) {
+	$display_dir = explode('/', $dir);
+	unset ($display_dir[0]);
+	unset ($display_dir[1]);
+	$display_dir = implode('/', $display_dir);
+}
+
+// Interbreadcrumb for the current directory root path
+$counter = 0;   
+if (isset($document_data['parents'])) {
+    foreach($document_data['parents'] as $document_sub_data) {
+        //fixing double group folder in breadcrumb
+        if (api_get_group_id()) {
+            if ($counter == 0) {
+                $counter++;
+                continue;  
+            }
+        }
+        $interbreadcrumb[] = array('url' => $document_sub_data['document_url'], 'name' => $document_sub_data['title']);
+        $counter++;
+    }
+}
+
+Display :: display_header($nameTools, 'Doc');
+echo '<div class="actions">';
+		echo '<a href="document.php?id='.$document_id.'">'.Display::return_icon('back.png',get_lang('BackTo').' '.get_lang('DocumentsOverview'),'',ICON_SIZE_MEDIUM).'</a>';
+echo '</div>';
+
+
+?>
+<!-- swfobject is a commonly used library to embed Flash content https://ajax.googleapis.com/ajax/libs/swfobject/2.2/ -->
+<script type="text/javascript" src="<?php echo api_get_path(WEB_LIBRARY_PATH) ?>swfobject/swfobject.js"></script>
+
+<!-- Setup the recorder interface -->
+<script type="text/javascript" src="<?php echo api_get_path(WEB_LIBRARY_PATH) ?>wami-recorder/recorder.js"></script>
+
+<!-- GUI code... take it or leave it -->
+<script type="text/javascript" src="<?php echo api_get_path(WEB_LIBRARY_PATH) ?>wami-recorder/gui.js"></script>
+
+<script>
+	function setupRecorder() {
+		Wami.setup({
+			id : "wami",
+			onReady : setupGUI
+		});
+	}
+
+	function setupGUI() {
+		var gui = new Wami.GUI({
+			id : "wami",
+			recordUrl : "<?php echo api_get_path(WEB_LIBRARY_PATH) ?>wami-recorder/record_document.php?waminame=<?php echo $waminame; ?>&wamidir=<?php echo $wamidir; ?>&wamiuserid=<?php echo $wamiuserid; ?>",
+			playUrl : 	"<?php echo $wamiurlplay.$waminame; ?>",
+			buttonUrl : "<?php echo api_get_path(WEB_LIBRARY_PATH) ?>wami-recorder/buttons.png",
+			swfUrl : 	"<?php echo api_get_path(WEB_LIBRARY_PATH) ?>wami-recorder/Wami.swf",
+		});
+
+		gui.setPlayEnabled(false);
+	}
+
+</script>
+
+<div id="wami" style="margin-left: 460px; margin-top:50px;"></div>
+<div align="center" style="margin-top:200px;">
+<?php
+
+Display::display_normal_message(get_lang('VoiceRecord'), false);
+
+?>
+
+</div>
+<body onLoad="setupRecorder()">
+
+<?php
+
+Display :: display_footer();

+ 57 - 15
main/document/save_pixlr.php

@@ -15,20 +15,28 @@ require_once '../inc/global.inc.php';
 require_once api_get_path(LIBRARY_PATH).'fileUpload.lib.php';
 require_once api_get_path(LIBRARY_PATH).'document.lib.php';
 
+
 api_protect_course_script();
 api_block_anonymous_users();
 
-if(!isset($_GET['title']) && !isset($_GET['type']) && !isset($_GET['image'])) {
+
+if ($_user['user_id']!= api_get_user_id() || api_get_user_id()==0 || $_user['user_id']==0) {
+	api_not_allowed();
+	die();
+}
+
+if(!isset($_GET['title']) || !isset($_GET['type']) || !isset($_GET['image'])) {
 	api_not_allowed();
 	die();
 }
 
 if(!isset($_SESSION['paint_dir']) || !isset($_SESSION['whereami']) ){
-	api_not_allowed();//
+	api_not_allowed();
 	die();	
 }
 
 //pixlr return
+
 $filename=Security::remove_XSS($_GET['title']);//The user preferred file name of the image.
 $extension=Security::remove_XSS($_GET['type']);//The image type, "pdx", "jpg", "bmp" or "png".
 $urlcontents=Security::remove_XSS($_GET['image']);//A URL to the image on Pixlr.com server or the raw file post of the saved image.
@@ -42,9 +50,18 @@ $relativeUrlPath=$_SESSION['paint_dir'];
 $currentTool=$_SESSION['whereami'];
 $dirBaseDocuments = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';
 $saveDir=$dirBaseDocuments.$_SESSION['paint_dir'];
+
 $contents = file_get_contents($urlcontents);
 
-//Verify that the URL is pointing to a file @ pixlr.com domain or an ip @ pixlr.com
+//Security. Verify that the URL is pointing to a file @ pixlr.com domain or an ip @ pixlr.com. Comment because sometimes return a ip number
+/*
+if (strpos($urlcontents, "pixlr.com") === 0){
+	echo "Invalid referrer";
+	exit;
+}
+*/
+
+//Security. Allway get from pixlr.com. Comment because for now this does not run
 /*
 $urlcontents1='http://pixlr.com/';
 $urlcontents2 = strstr($urlcontents, '_temp');
@@ -52,26 +69,50 @@ $urlcontents_to_save=$urlcontents1.$urlcontents2;
 $contents = file_get_contents($urlcontents_to_save);//replace line 45.
 */
 
-//Verify that the file is an image
-$headers = get_headers($urlcontents, 1);
-$content_type = explode("/", $headers['Content-Type']);
-if ($content_type[0] != "image"){
-	echo "Invalid file type";
-	exit;
-}
-
 //a bit title security
 $filename = addslashes(trim($filename));
 $filename = Security::remove_XSS($filename);
 $filename = replace_dangerous_char($filename, 'strict');
 $filename = disable_dangerous_file($filename);
 
-// a bit extension security
-if($extension!= 'jpg' && $extension!= 'png' && $extension!= 'bmp' && $extension!= 'pxd'){
+if (strlen(trim($filename))==0) {
+	 echo "The title is empty";//if title is empty, headers Content-Type = application/octet-stream, then not create a new title here please
+	 exit;
+}
+
+//check file_get_contents
+if ($contents === false) { 
+	echo "I cannot read: ".$urlcontents;
+    exit;
+}
+
+// Extension security
+if($extension!= 'jpg' && $extension!= 'png' && $extension!= 'pxd'){
 	die();
 }
+if($extension=='pxd') {
+	echo "pxd file type does not supported";// not secure because check security headers and finfo() return  Content-Type = application/octet-stream
+    exit;
+}
 
-//TODO: a bit mime security
+//Verify that the file is an image. Headers method
+$headers = get_headers($urlcontents, 1);
+$content_type = explode("/", $headers['Content-Type']);
+if ($content_type[0] != "image") {
+	echo "Invalid file type";
+	exit;
+}
+
+//Verify that the file is an image. Fileinfo method
+if (phpversion() >= '5.3' && extension_loaded('fileinfo')) {
+	$finfo = new finfo(FILEINFO_MIME);
+	$current_mime=$finfo->buffer($contents);
+	finfo_close($finfo);
+	if(strpos($current_mime, 'image')===false) {
+		echo "Invalid mime type file";
+		exit;
+	}
+}
 
 //path, file and title
 $paintFileName = $filename.'.'.$extension;
@@ -127,7 +168,8 @@ if($currentTool=='document/createpaint'){
 
 
 //delete temporal file
-unlink($_SESSION['temp_realpath_image']);
+$temp_file_2delete=$_SESSION['temp_realpath_image'];
+unlink($temp_file_2delete);
 
 //Clean sessions and return to Chamilo file list
 unset($_SESSION['paint_dir']);

+ 25 - 8
main/inc/lib/nanogong/receiver.php

@@ -6,7 +6,7 @@
  *
  *	@package chamilo.document
  *
- * @author Juan Carlos Raa Trabado
+ * @author Juan Carlos Raña Trabado
  * @since 5/mar/2011
 */
 /**
@@ -15,18 +15,23 @@
 require_once '../../../inc/global.inc.php';
 require_once api_get_path(LIBRARY_PATH).'fileUpload.lib.php';
 
-//security. Nanogong need less security because under Firefox, Chrome..., not save user_id...
+//security. Nanogong need less security because under Firefox, Chrome..., not stay  SESSION 
 if (api_get_setting('enable_nanogong') == 'false'){
 	api_protect_course_script();
 	api_block_anonymous_users();
 }
 
-if (!isset($_GET['filename']) || !isset($_GET['filepath']) || !isset($_GET['dir']) || !isset($_GET['course_code'])){
-	api_not_allowed(true);
+if (!isset($_GET['filename']) || !isset($_GET['filepath']) || !isset($_GET['dir']) || !isset($_GET['course_code'])|| !isset($_GET['nano_group_id']) || !isset($_GET['nano_session_id']) || !isset($_GET['nano_user_id'])){
+	echo 'Error. Not allowed';
+	exit;
 }
 if (!is_uploaded_file($_FILES['voicefile']['tmp_name'])) exit;
 
 //clean
+$nano_user_id=Security::remove_XSS($_GET['nano_user_id']);
+$nano_group_id=Security::remove_XSS($_GET['nano_group_id']);
+$nano_session_id=Security::remove_XSS($_GET['nano_session_id']);
+
 $filename=Security::remove_XSS($_GET['filename']);
 $filename=urldecode($filename);
 $filepath=Security::remove_XSS(urldecode($_GET['filepath']));
@@ -41,21 +46,33 @@ $filename = Database::escape_string($filename);
 $filename = replace_dangerous_char($filename, $strict = 'loose');// or strict
 $filename = disable_dangerous_file($filename);
 
-$title= str_replace('_',' ',$filename);
+$title= trim(str_replace('_chnano_.','.',$filename));//hide nanogong wav tag at title
+$title= str_replace('_',' ',$title);
+
 //
 $documentPath = $filepath.$filename;
 
+/*
+//comment because here api_get_user_id() return alway 0
+if ($nano_user_id!= api_get_user_id() || api_get_user_id()==0 || $nano_user_id==0) {
+	echo 'Not allowed';
+	exit;
+}
+*/
+
+//Do not use here check Fileinfo method because return: text/plain
+
 if (!file_exists($documentPath)){
 	//add document to disk
 	move_uploaded_file($_FILES['voicefile']['tmp_name'], $documentPath);
 	
 	//add document to database
-	$current_session_id = api_get_session_id();
-	$groupId=$_SESSION['_gid'];
+	$current_session_id = $nano_session_id; // $nano_session_id instead api_get_session_id() because here $_SESSION is lost
+	$groupId=$nano_group_id; // $nano_group_id instead $_SESSION['_gid'], because here $_SESSION is lost.
 	$file_size = filesize($documentPath);
 	$relativeUrlPath=$dir;		
 	$doc_id = add_document($_course, $relativeUrlPath.$filename, 'file', filesize($documentPath), $title);
-	api_item_property_update($_course, TOOL_DOCUMENT, $doc_id, 'DocumentAdded', $_user['user_id'], $groupId, null, null, null, $current_session_id);
+	api_item_property_update($_course, TOOL_DOCUMENT, $doc_id, 'DocumentAdded', $nano_user_id, $groupId, null, null, null, $current_session_id);// $nano_user_id instead $_user['user_id'], because here $_user['user_id'] is lost.
 } else {
 	return get_lang('FileExistRename');
 }

+ 1 - 1
main/inc/lib/svg-edit/extensions/filesave.php

@@ -83,7 +83,7 @@ if ($suffix!= 'svg' && $suffix!= 'png'){
 //a bit mime security
 //comment because finfo seems stopping the save process files in some php vers.
 /*
-if (phpversion() >= '5.3') {
+if (phpversion() >= '5.3' && extension_loaded('fileinfo')) {
 	$finfo = new finfo(FILEINFO_MIME);
 	$current_mime=$finfo->buffer($contents);
 	finfo_close($finfo);

BIN=BIN
main/inc/lib/wami-recorder/Wami.swf


BIN=BIN
main/inc/lib/wami-recorder/buttons.png


+ 349 - 0
main/inc/lib/wami-recorder/gui.js

@@ -0,0 +1,349 @@
+var Wami = window.Wami || {};
+
+// Upon a creation of a new Wami.GUI(options), we assume that a WAMI recorder
+// has been initialized.
+Wami.GUI = function(options) {
+	var RECORD_BUTTON = 1;
+	var PLAY_BUTTON = 2;
+
+	setOptions(options);
+	setupDOM();
+
+	var recordButton, playButton;
+	var recordInterval, playInterval;
+
+	function createDiv(id, style) {
+		var div = document.createElement("div");
+		if (id) {
+			div.setAttribute('id', id);
+		}
+		if (style) {
+			div.style.cssText = style;
+		}
+		return div;
+	}
+
+	function setOptions(options) {
+		if (!options.buttonUrl) {
+			options.buttonUrl = "buttons.png";
+		}
+
+		if (typeof options.listen == 'undefined' || options.listen) {
+			listen();
+		}
+	}
+
+	function setupDOM() {
+		var guidiv = createDiv(null,
+				"position: absolute; width: 214px; height: 137px;");
+		document.getElementById(options.id).appendChild(guidiv);
+
+		var rid = Wami.createID();
+		var recordDiv = createDiv(rid,
+				"position: absolute; left: 40px; top: 25px");
+		guidiv.appendChild(recordDiv);
+
+		recordButton = new Button(rid, RECORD_BUTTON, options.buttonUrl);
+		recordButton.onstart = startRecording;
+		recordButton.onstop = stopRecording;
+
+		recordButton.setEnabled(true);
+
+		if (!options.singleButton) {
+			var pid = Wami.createID();
+			var playDiv = createDiv(pid,
+					"position: absolute; right: 40px; top: 25px");
+			guidiv.appendChild(playDiv);
+
+			playButton = new Button(pid, PLAY_BUTTON, options.buttonUrl);
+			playButton.onstart = startPlaying;
+			playButton.onstop = stopPlaying;
+		}
+	}
+
+	/**
+	 * These methods are called on clicks from the GUI.
+	 */
+	function startRecording() {
+		if (!options.recordUrl) {
+			alert("No record Url specified!");
+		}
+		recordButton.setActivity(0);
+		playButton.setEnabled(false);
+		Wami.startRecording(options.recordUrl,
+				Wami.nameCallback(onRecordStart), Wami
+						.nameCallback(onRecordFinish), Wami
+						.nameCallback(onError));
+	}
+
+	function stopRecording() {
+		Wami.stopRecording();
+		clearInterval(recordInterval);
+		recordButton.setEnabled(true);
+	}
+
+	function startPlaying() {
+		if (!options.playUrl) {
+			alert('No play URL specified!');
+		}
+
+		playButton.setActivity(0);
+		recordButton.setEnabled(false);
+
+		Wami.startPlaying(options.playUrl, Wami.nameCallback(onPlayStart), Wami
+				.nameCallback(onPlayFinish), Wami.nameCallback(onError));
+	}
+
+	function stopPlaying() {
+		Wami.stopPlaying();
+	}
+
+	this.setPlayUrl = function(url) {
+		options.playUrl = url;
+	}
+
+	this.setRecordUrl = function(url) {
+		options.recordUrl = url;
+	}
+
+	this.setPlayEnabled = function(val) {
+		playButton.setEnabled(val);
+	}
+
+	this.setRecordEnabled = function(val) {
+		recordButton.setEnabled(val);
+	}
+
+	/**
+	 * Callbacks from the flash indicating certain events
+	 */
+
+	function onError(e) {
+		alert(e);
+	}
+
+	function onRecordStart() {
+		recordInterval = setInterval(function() {
+			if (recordButton.isActive()) {
+				var level = Wami.getRecordingLevel();
+				recordButton.setActivity(level);
+			}
+		}, 200);
+		if (options.onRecordStart) {
+			options.onRecordStart();
+		}
+	}
+
+	function onRecordFinish() {
+		playButton.setEnabled(true);
+		if (options.onRecordFinish) {
+			options.onRecordFinish();
+		}
+	}
+
+	function onPlayStart() {
+		playInterval = setInterval(function() {
+			if (playButton.isActive()) {
+				var level = Wami.getPlayingLevel();
+				playButton.setActivity(level);
+			}
+		}, 200);
+		if (options.onPlayStart) {
+			options.onPlayStart();
+		}
+	}
+
+	function onPlayFinish() {
+		clearInterval(playInterval);
+		recordButton.setEnabled(true);
+		playButton.setEnabled(true);
+		if (options.onPlayFinish) {
+			options.onPlayFinish();
+		}
+	}
+
+	function listen() {
+		Wami.startListening();
+		// Continually listening when the window is in focus allows us to
+		// buffer a little audio before the users clicks, since sometimes
+		// people talk too soon. Without "listening", the audio would record
+		// exactly when startRecording() is called.
+		window.onfocus = function() {
+			Wami.startListening();
+		};
+
+		// Note that the use of onfocus and onblur should probably be replaced
+		// with a more robust solution (e.g. jQuery's $(window).focus(...)
+		window.onblur = function() {
+			Wami.stopListening();
+		};
+	}
+
+	function Button(buttonid, type, url) {
+		var self = this;
+		self.active = false;
+		self.type = type;
+
+		init();
+
+		// Get the background button image position
+		// Index: 1) normal 2) pressed 3) mouse-over
+		function background(index) {
+			if (index == 1)
+				return "-56px 0px";
+			if (index == 2)
+				return "0px 0px";
+			if (index == 3)
+				return "-112px 0";
+			alert("Background not found: " + index);
+		}
+
+		// Get the type of meter and its state
+		// Index: 1) enabled 2) meter 3) disabled
+		function meter(index, offset) {
+			var top = 5;
+			if (offset)
+				top += offset;
+			if (self.type == RECORD_BUTTON) {
+				if (index == 1)
+					return "-169px " + top + "px";
+				if (index == 2)
+					return "-189px " + top + "px";
+				if (index == 3)
+					return "-249px " + top + "px";
+			} else {
+				if (index == 1)
+					return "-269px " + top + "px";
+				if (index == 2)
+					return "-298px " + top + "px";
+				if (index == 3)
+					return "-327px " + top + "px";
+			}
+			alert("Meter not found: " + self.type + " " + index);
+		}
+
+		function silhouetteWidth() {
+			if (self.type == RECORD_BUTTON) {
+				return "20px";
+			} else {
+				return "29px";
+			}
+		}
+
+		function mouseHandler(e) {
+			var rightclick;
+			if (!e)
+				var e = window.event;
+			if (e.which)
+				rightclick = (e.which == 3);
+			else if (e.button)
+				rightclick = (e.button == 2);
+
+			if (!rightclick) {
+				if (self.active && self.onstop) {
+					self.active = false;
+					self.onstop();
+				} else if (!self.active && self.onstart) {
+					self.active = true;
+					self.onstart();
+				}
+			}
+		}
+
+		function init() {
+			var div = document.createElement("div");
+			var elem = document.getElementById(buttonid);
+			if (elem) {
+				elem.appendChild(div);
+			} else {
+				alert('Could not find element on page named ' + buttonid);
+			}
+
+			self.guidiv = document.createElement("div");
+			self.guidiv.style.width = '56px';
+			self.guidiv.style.height = '63px';
+			self.guidiv.style.cursor = 'pointer';
+			self.guidiv.style.background = "url(" + url + ") no-repeat";
+			self.guidiv.style.backgroundPosition = background(1);
+			div.appendChild(self.guidiv);
+
+			// margin auto doesn't work in IE quirks mode
+			// http://stackoverflow.com/questions/816343/why-will-this-div-img-not-center-in-ie8
+			// text-align is a hack to force it to work even if you forget the
+			// doctype.
+			self.guidiv.style.textAlign = 'center';
+
+			self.meterDiv = document.createElement("div");
+			self.meterDiv.style.width = silhouetteWidth();
+			self.meterDiv.style.height = '63px';
+			self.meterDiv.style.margin = 'auto';
+			self.meterDiv.style.cursor = 'pointer';
+			self.meterDiv.style.position = 'relative';
+			self.meterDiv.style.background = "url(" + url + ") no-repeat";
+			self.meterDiv.style.backgroundPosition = meter(2);
+			self.guidiv.appendChild(self.meterDiv);
+
+			self.coverDiv = document.createElement("div");
+			self.coverDiv.style.width = silhouetteWidth();
+			self.coverDiv.style.height = '63px';
+			self.coverDiv.style.margin = 'auto';
+			self.coverDiv.style.cursor = 'pointer';
+			self.coverDiv.style.position = 'relative';
+			self.coverDiv.style.background = "url(" + url + ") no-repeat";
+			self.coverDiv.style.backgroundPosition = meter(1);
+			self.meterDiv.appendChild(self.coverDiv);
+
+			self.active = false;
+			self.guidiv.onmousedown = mouseHandler;
+		}
+
+		self.isActive = function() {
+			return self.active;
+		}
+
+		self.setActivity = function(level) {
+			self.guidiv.onmouseout = function() {
+			};
+			self.guidiv.onmouseover = function() {
+			};
+			self.guidiv.style.backgroundPosition = background(2);
+			self.coverDiv.style.backgroundPosition = meter(1, 5);
+			self.meterDiv.style.backgroundPosition = meter(2, 5);
+
+			var totalHeight = 31;
+			var maxHeight = 9;
+
+			// When volume goes up, the black image loses height,
+			// creating the perception of the colored one increasing.
+			var height = (maxHeight + totalHeight - Math.floor(level / 100
+					* totalHeight));
+			self.coverDiv.style.height = height + "px";
+		}
+
+		self.setEnabled = function(enable) {
+			var guidiv = self.guidiv;
+			self.active = false;
+			if (enable) {
+				self.coverDiv.style.backgroundPosition = meter(1);
+				self.meterDiv.style.backgroundPosition = meter(1);
+				guidiv.style.backgroundPosition = background(1);
+				guidiv.onmousedown = mouseHandler;
+				guidiv.onmouseover = function() {
+					guidiv.style.backgroundPosition = background(3);
+				};
+				guidiv.onmouseout = function() {
+					guidiv.style.backgroundPosition = background(1);
+				};
+			} else {
+				self.coverDiv.style.backgroundPosition = meter(3);
+				self.meterDiv.style.backgroundPosition = meter(3);
+				guidiv.style.backgroundPosition = background(1);
+				guidiv.onmousedown = null;
+				guidiv.onmouseout = function() {
+				};
+				guidiv.onmouseover = function() {
+				};
+			}
+		}
+	}
+}

+ 6 - 0
main/inc/lib/wami-recorder/index.html

@@ -0,0 +1,6 @@
+<html>
+<head>
+</head>
+<body>
+</body>
+</html>

+ 64 - 0
main/inc/lib/wami-recorder/record_document.php

@@ -0,0 +1,64 @@
+<?php
+require_once '../../../inc/global.inc.php';
+require_once api_get_path(LIBRARY_PATH).'fileUpload.lib.php';
+require_once api_get_path(LIBRARY_PATH).'document.lib.php';
+////Add security from Chamilo
+api_protect_course_script();
+api_block_anonymous_users();
+//
+# Save the audio to a URL-accessible directory for playback.
+parse_str($_SERVER['QUERY_STRING'], $params);
+
+if(isset($params['waminame']) && isset($params['wamidir']) && isset($params['wamiuserid'])) {
+	$waminame = $params['waminame'];
+	$wamidir = $params['wamidir'];
+	$wamiuserid = $params['wamiuserid'];
+}
+else {
+	api_not_allowed();
+	die();
+}
+
+if ($wamiuserid!= api_get_user_id() || api_get_user_id()==0 || $wamiuserid==0) {
+	api_not_allowed();
+	die();
+}
+	
+
+//clean
+$waminame = Security::remove_XSS($waminame);
+$waminame = addslashes(trim($waminame));
+$waminame = replace_dangerous_char($waminame, 'strict');
+$waminame = disable_dangerous_file($waminame);
+$wamidir = Security::remove_XSS($wamidir);
+
+
+$content = file_get_contents('php://input');
+
+//security extension
+$ext = explode('.', $waminame);
+$ext = strtolower($ext[sizeof($ext) - 1]);
+
+if($ext!= 'wav'){
+	die();
+}
+
+//Do not use here check Fileinfo method because return: text/plain
+
+$dirBaseDocuments = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';
+$saveDir=$dirBaseDocuments.$wamidir;
+$current_session_id = api_get_session_id();
+$groupId=$_SESSION['_gid'];
+$documentPath = $saveDir.'/'.$waminame;
+$title=str_replace('_',' ',$waminame);
+//$title=basename($waminame, ".wav");//save title whitout extension
+
+//add to disk
+$fh = fopen($documentPath, 'w') or die("can't open file");
+fwrite($fh, $content);
+fclose($fh);
+
+//add document to database
+	$doc_id = add_document($_course, $wamidir.'/'.$waminame, 'file', filesize($documentPath), $title);
+	api_item_property_update($_course, TOOL_DOCUMENT, $doc_id, 'DocumentAdded', $_user['user_id'], $groupId, null, null, null, $current_session_id);
+?>

+ 273 - 0
main/inc/lib/wami-recorder/recorder.js

@@ -0,0 +1,273 @@
+var Wami = window.Wami || {};
+
+// Returns a (very likely) unique string with of random letters and numbers
+Wami.createID = function() {
+	return "wid" + ("" + 1e10).replace(/[018]/g, function(a) {
+		return (a ^ Math.random() * 16 >> a / 4).toString(16)
+	});
+}
+
+// Creates a named callback in WAMI and returns the name as a string.
+Wami.nameCallback = function(cb, cleanup) {
+	Wami._callbacks = Wami._callbacks || {};
+	var id = Wami.createID();
+	Wami._callbacks[id] = function() {
+		if (cleanup) {
+			Wami._callbacks[id] = null;
+		}
+		cb.apply(null, arguments);
+	};
+	var named = "Wami._callbacks['" + id + "']";
+	return named;
+}
+
+// This method ensures that a WAMI recorder is operational, and that
+// the following API is available in the Wami namespace. All functions
+// must be named (i.e. cannot be anonymous).
+//
+// Wami.startPlaying(url, startfn = null, finishedfn = null, failedfn = null);
+// Wami.stopPlaying()
+//
+// Wami.startRecording(url, startfn = null, finishedfn = null, failedfn = null);
+// Wami.stopRecording()
+//
+// Wami.getRecordingLevel() // Returns a number between 0 and 100
+// Wami.getPlayingLevel() // Returns a number between 0 and 100
+//
+// Wami.hide()
+// Wami.show()
+//
+// Manipulate the WAMI recorder's settings. In Flash
+// we need to check if the microphone permission has been granted.
+// We might also set/return sample rate here, etc.
+//
+// Wami.getSettings();
+// Wami.setSettings(options);
+//
+// Optional way to set up browser so that it's constantly listening
+// This is to prepend audio in case the user starts talking before
+// they click-to-talk.
+//
+// Wami.startListening()
+//
+Wami.setup = function(options) {
+	if (Wami.startRecording) {
+		// Wami's already defined.
+		if (options.onReady) {
+			options.onReady();
+		}
+		return;
+	}
+
+	// Assumes that swfobject.js is included if Wami.swfobject isn't
+	// already defined.
+	Wami.swfobject = Wami.swfobject || swfobject;
+
+	if (!Wami.swfobject) {
+		alert("Unable to find swfobject to help embed the SWF.");
+	}
+
+	var _options;
+	setOptions(options);
+	embedWamiSWF(_options.id, Wami.nameCallback(delegateWamiAPI));
+
+	function supportsTransparency() {
+		// Detecting the OS is a big no-no in Javascript programming, but
+		// I can't think of a better way to know if wmode is supported or
+		// not... since NOT supporting it (like Flash on Ubuntu) is a bug.
+		return (navigator.platform.indexOf("Linux") == -1);
+	}
+
+	function setOptions(options) {
+		// Start with default options
+		_options = {
+			swfUrl : "Wami.swf",
+			onReady : function() {
+				Wami.hide();
+			},
+			onSecurity : checkSecurity,
+			onError : function(error) {
+				alert(error);
+			}
+		};
+
+		if (typeof options == 'undefined') {
+			alert('Need at least an element ID to place the Flash object.');
+		}
+
+		if (typeof options == 'string') {
+			_options.id = options;
+		} else {
+			_options.id = options.id;
+		}
+
+		if (options.swfUrl) {
+			_options.swfUrl = options.swfUrl;
+		}
+
+		if (options.onReady) {
+			_options.onReady = options.onReady;
+		}
+
+		if (options.onLoaded) {
+			_options.onLoaded = options.onLoaded;
+		}
+
+		if (options.onSecurity) {
+			_options.onSecurity = options.onSecurity;
+		}
+
+		if (options.onError) {
+			_options.onError = options.onError;
+		}
+
+		// Create a DIV for the SWF under _options.id
+
+		var container = document.createElement('div');
+		container.style.position = 'absolute';
+		_options.cid = Wami.createID();
+		container.setAttribute('id', _options.cid);
+
+		var swfdiv = document.createElement('div');
+		var id = Wami.createID();
+		swfdiv.setAttribute('id', id);
+
+		container.appendChild(swfdiv);
+		document.getElementById(_options.id).appendChild(container);
+
+		_options.id = id;
+	}
+
+	function checkSecurity() {
+		var settings = Wami.getSettings();
+		if (settings.microphone.granted) {
+			_options.onReady();
+		} else {
+			// Show any Flash settings panel you want:
+			// http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/system/SecurityPanel.html
+			Wami.showSecurity("privacy", "Wami.show", Wami
+					.nameCallback(_options.onSecurity), Wami
+					.nameCallback(_options.onError));
+		}
+	}
+
+	// Embed the WAMI SWF and call the named callback function when loaded.
+	function embedWamiSWF(id, initfn) {
+		var flashVars = {
+			visible : false,
+			loadedCallback : initfn
+		}
+
+		var params = {
+			allowScriptAccess : "always"
+		}
+
+		if (supportsTransparency()) {
+			params.wmode = "transparent";
+		}
+
+		if (typeof console !== 'undefined') {
+			flashVars.console = true;
+		}
+
+		var version = '10.0.0';
+		document.getElementById(id).innerHTML = "WAMI requires Flash "
+				+ version
+				+ " or greater<br />https://get.adobe.com/flashplayer/";
+
+		// This is the minimum size due to the microphone security panel
+		Wami.swfobject.embedSWF(_options.swfUrl, id, 214, 137, version, null,
+				flashVars, params);
+
+		// Without this line, Firefox has a dotted outline of the flash
+		Wami.swfobject.createCSS("#" + id, "outline:none");
+	}
+
+	// To check if the microphone settings were 'remembered', we
+	// must actually embed an entirely new Wami client and check
+	// whether its microphone is granted. If it is, it was remembered.
+	function checkRemembered(finishedfn) {
+		var id = Wami.createID();
+		var div = document.createElement('div');
+		div.style.top = '-999px';
+		div.style.left = '-999px';
+		div.setAttribute('id', id);
+		var body = document.getElementsByTagName('body').item(0);
+		body.appendChild(div);
+
+		var fn = Wami.nameCallback(function() {
+			var swf = document.getElementById(id);
+			Wami._remembered = swf.getSettings().microphone.granted;
+			Wami.swfobject.removeSWF(id);
+			eval(finishedfn + "()");
+		});
+
+		embedWamiSWF(id, fn);
+	}
+
+	// Attach all the audio methods to the Wami namespace in the callback.
+	function delegateWamiAPI() {
+		var recorder = document.getElementById(_options.id);
+
+		function delegate(name) {
+			Wami[name] = function() {
+				return recorder[name].apply(recorder, arguments);
+			}
+		}
+		delegate('startPlaying');
+		delegate('stopPlaying');
+		delegate('startRecording');
+		delegate('stopRecording');
+		delegate('startListening');
+		delegate('stopListening');
+		delegate('getRecordingLevel');
+		delegate('getPlayingLevel');
+		delegate('setSettings');
+
+		// Append extra information about whether mic settings are sticky
+		Wami.getSettings = function() {
+			var settings = recorder.getSettings();
+			settings.microphone.remembered = Wami._remembered;
+			return settings;
+		}
+
+		Wami.showSecurity = function(panel, startfn, finishedfn, failfn) {
+			// Flash must be on top for this.
+			var container = document.getElementById(_options.cid);
+
+			var augmentedfn = Wami.nameCallback(function() {
+				checkRemembered(finishedfn);
+				container.style.cssText = "position: absolute;";
+			});
+
+			container.style.cssText = "position: absolute; z-index: 99999";
+
+			recorder.showSecurity(panel, startfn, augmentedfn, failfn);
+		}
+
+		Wami.show = function() {
+			if (!supportsTransparency()) {
+				recorder.style.visibility = "visible";
+			}
+		}
+
+		Wami.hide = function() {
+			// Hiding flash in all the browsers is tricky. Please read:
+			// https://code.google.com/p/wami-recorder/wiki/HidingFlash
+			if (!supportsTransparency()) {
+				recorder.style.visibility = "hidden";
+			}
+		}
+
+		// If we already have permissions, they were previously 'remembered'
+		Wami._remembered = recorder.getSettings().microphone.granted;
+
+		if (_options.onLoaded) {
+			_options.onLoaded();
+		}
+
+		if (!_options.noSecurityCheck) {
+			checkSecurity();
+		}
+	}
+}

+ 5 - 2
main/install/db_main.sql

@@ -864,7 +864,8 @@ VALUES
 ('login_is_email', NULL, 'radio', 'Platform', 'false', 'LoginIsEmailTitle', 'LoginIsEmailComment', NULL, NULL, 0),
 ('courses_default_creation_visibility', NULL, 'radio', 'Course', '2', 'CoursesDefaultCreationVisibilityTitle', 'CoursesDefaultCreationVisibilityComment', NULL, NULL, 1),
 ('allow_browser_sniffer', NULL, 'radio', 'Tuning', 'false', 'AllowBrowserSnifferTitle', 'AllowBrowserSnifferComment', NULL, NULL, 0),
-('chamilo_database_version',NULL,'textfield',NULL, '1.9.0.17053','DokeosDatabaseVersion','', NULL, NULL, 0);
+('enable_wami_record',NULL,'radio','Tools','false','EnableWamiRecordTitle','EnableWamiRecordComment',NULL,NULL, 0),
+('chamilo_database_version',NULL,'textfield',NULL, '1.9.0.17054','DokeosDatabaseVersion','', NULL, NULL, 0);
 
 /*
 ('show_tabs', 'custom_tab_1', 'checkbox', 'Platform', 'true', 'ShowTabsTitle', 'ShowTabsComment', NULL, 'TabsCustom1', 1),
@@ -1201,7 +1202,9 @@ VALUES
 ('courses_default_creation_visibility', '1', 'Private'),
 ('courses_default_creation_visibility', '0', 'CourseVisibilityClosed'),
 ('allow_browser_sniffer', 'true', 'Yes'),
-('allow_browser_sniffer', 'false', 'No');
+('allow_browser_sniffer', 'false', 'No'),
+('enable_wami_record', 'true', 'Yes'),
+('enable_wami_record', 'false', 'No');
 
 UNLOCK TABLES;
 /*

+ 5 - 0
main/install/migrate-db-1.8.8-1.9.0-pre.sql

@@ -148,6 +148,11 @@ INSERT INTO settings_current (variable, subkey, type, category, selected_value,
 INSERT INTO settings_options (variable, value, display_text) VALUES ('allow_browser_sniffer', 'true', 'Yes');
 INSERT INTO settings_options (variable, value, display_text) VALUES ('allow_browser_sniffer', 'false', 'No');
 
+INSERT INTO settings_current (variable, subkey, type, category, selected_value, title, comment, scope, subkeytext, access_url_changeable) VALUES ('enable_wami_record', NULL, 'radio', 'Tools', 'false', 'EnableWamiRecordTitle', 'EnableWamiRecordComment', NULL, NULL, 0);
+INSERT INTO settings_options (variable, value, display_text) VALUES ('enable_wami_record', 'true', 'Yes');
+INSERT INTO settings_options (variable, value, display_text) VALUES ('enable_wami_record', 'false', 'No');
+
+
 -- Course ranking
 
 CREATE TABLE track_course_ranking (id   int unsigned not null PRIMARY KEY AUTO_INCREMENT,c_id  int unsigned not null, session_id  int unsigned not null default 0, url_id  int unsigned not null default 0, accesses int unsigned not null default 0, total_score int unsigned not null default 0, users int unsigned not null default 0, creation_date datetime not null);