Implement new chat system

This commit is contained in:
Dmitriy Simushev 2012-10-02 10:53:52 +00:00
parent f1224a98ee
commit 30cb668a73
11 changed files with 856 additions and 522 deletions

View File

@ -1,25 +1,30 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
var FrameUtils={getDocument:function(a){return a.contentDocument?a.contentDocument:a.contentWindow?a.contentWindow.document:a.document?a.document:null},initFrame:function(a){var b=this.getDocument(a);b.open();b.write("<html><head>");b.write('<link rel="stylesheet" type="text/css" media="all" href="'+Chat.cssfile+'">');b.write("</head><body bgcolor='#FFFFFF' text='#000000' link='#C28400' vlink='#C28400' alink='#C28400'>");b.write("<table width='100%' cellspacing='0' cellpadding='0' border='0'><tr><td valign='top' class='message' id='content'></td></tr></table><a id='bottom' name='bottom'></a>");
b.write("</body></html>");b.close();a.onload=function(){a.myHtml&&(FrameUtils.getDocument(a).getElementById("content").innerHTML+=a.myHtml,FrameUtils.scrollDown(a))}},insertIntoFrame:function(a,b){var c=this.getDocument(a).getElementById("content");if(null==c){if(!a.myHtml)a.myHtml="";a.myHtml+=b}else c.innerHTML+=b},scrollDown:function(a){var b=this.getDocument(a).getElementById("bottom");if("opera"==myAgent)try{a.contentWindow.scrollTo(0,this.getDocument(a).getElementById("content").clientHeight)}catch(c){}b&&
b.scrollIntoView(!1)}};Ajax.ChatThreadUpdater=Class.create();
Class.inherit(Ajax.ChatThreadUpdater,Ajax.Base,{initialize:function(a){this.setOptions(a);this._options.onComplete=this.requestComplete.bind(this);this._options.onException=this.handleException.bind(this);this._options.onTimeout=this.handleTimeout.bind(this);this._options.timeout=5E3;this.updater={};this.frequency=this._options.frequency||2;this.lastupdate=0;this.focused=this.skipNextsound=this.cansend=!0;this.ownThread=null!=this._options.message;FrameUtils.initFrame(this._options.container);if(this._options.message)this._options.message.onkeydown=
this.handleKeyDown.bind(this),this._options.message.onfocus=function(){this.focused=!0}.bind(this),this._options.message.onblur=function(){this.focused=!1}.bind(this);this.update()},handleException:function(){this.setStatus("offline, reconnecting");this.stopUpdate();this.timer=setTimeout(this.update.bind(this),1E3)},handleTimeout:function(){this.setStatus("timeout, reconnecting");this.stopUpdate();this.timer=setTimeout(this.update.bind(this),1E3)},updateOptions:function(a){this._options.parameters=
"act="+a+"&thread="+(this._options.threadid||0)+"&token="+(this._options.token||0)+"&lastid="+(this._options.lastid||0);this._options.user&&(this._options.parameters+="&user=true");"refresh"==a&&this._options.message&&""!=this._options.message.value&&(this._options.parameters+="&typed=1")},enableInput:function(a){if(this._options.message)this._options.message.disabled=!a},stopUpdate:function(){this.enableInput(!0);if(this.updater._options)this.updater._options.onComplete=void 0;clearTimeout(this.timer)},
update:function(){this.updateOptions("refresh");this.updater=new Ajax.Request(this._options.servl,this._options)},requestComplete:function(a){try{this.enableInput(!0);this.cansend=!0;var b=Ajax.getXml(a);b&&"thread"==b.tagName?this.updateContent(b):this.handleError(a,b,"refresh messages failed")}catch(c){}this.skipNextsound=!1;this.timer=setTimeout(this.update.bind(this),1E3*this.frequency)},postMessage:function(a){if(""!=a&&this.cansend){this.cansend=!1;this.stopUpdate();this.skipNextsound=!0;this.updateOptions("post");
var b={}.extend(this._options);b.parameters+="&message="+encodeURIComponent(a);b.onComplete=function(a){this.requestComplete(a);if(this._options.message)this._options.message.value="",this._options.message.focus()}.bind(this);"opera"!=myRealAgent&&this.enableInput(!1);this.updater=new Ajax.Request(this._options.servl,b)}},changeName:function(a){this.skipNextsound=!0;new Ajax.Request(this._options.servl,{parameters:"act=rename&thread="+(this._options.threadid||0)+"&token="+(this._options.token||0)+
"&name="+encodeURIComponent(a)})},onThreadClosed:function(a){var b=Ajax.getXml(a);b&&"closed"==b.tagName?setTimeout("window.close()",2E3):this.handleError(a,b,"cannot close")},closeThread:function(){if("undefined"!=typeof Chat.localizedStrings.closeConfirmation&&Chat.localizedStrings.closeConfirmation&&!confirm(Chat.localizedStrings.closeConfirmation))return!1;var a="act=close&thread="+(this._options.threadid||0)+"&token="+(this._options.token||0);this._options.user&&(a+="&user=true");new Ajax.Request(this._options.servl,
{parameters:a,onComplete:this.onThreadClosed.bind(this)})},processMessage:function(a,b){var c=NodeUtils.getNodeText(b);FrameUtils.insertIntoFrame(a,c)},showTyping:function(a){if($("typingdiv"))$("typingdiv").style.display=a?"inline":"none"},setupAvatar:function(a){a=NodeUtils.getNodeText(a);if(this._options.avatar&&this._options.user)this._options.avatar.innerHTML=""!=a?'<img src="'+Chat.webimRoot+'/images/free.gif" width="7" height="1" border="0" alt="" /><img src="'+a+'" border="0" alt=""/>':""},
updateContent:function(a){var b=!1,c=this._options.container,d=NodeUtils.getAttrValue(a,"lastid");if(d)this._options.lastid=d;(d=NodeUtils.getAttrValue(a,"typing"))&&this.showTyping("1"==d);if((d=NodeUtils.getAttrValue(a,"canpost"))&&("1"==d&&!this.ownThread||this.ownThread&&"1"!=d))window.location.href=window.location.href;for(d=0;d<a.childNodes.length;d++){var e=a.childNodes[d];"message"==e.tagName?(b=!0,this.processMessage(c,e)):"avatar"==e.tagName&&this.setupAvatar(e)}0<=window.location.search.indexOf("trace=on")?
(a="updated",0<this.lastupdate&&(c=((new Date).getTime()-this.lastupdate)/1E3,a=a+", "+c+" secs",10<c&&alert(a)),this.lastupdate=(new Date).getTime(),this.setStatus(a)):this.clearStatus();b&&(FrameUtils.scrollDown(this._options.container),this.skipNextsound||(b=$("soundimg"),(null==b||b.className.match(/\bisound\b/))&&playSound(Chat.webimRoot+"/sounds/new_message.wav")),this.focused||window.focus())},isSendkey:function(a,b){return 13==b&&(a||this._options.ignorectrl)||10==b},handleKeyDown:function(a){a?
(ctrl=a.ctrlKey,a=a.which):(a=event.keyCode,ctrl=event.ctrlKey);return this._options.message&&this.isSendkey(ctrl,a)?(a=this._options.message.value,this._options.ignorectrl&&(a=a.replace(/[\r\n]+$/,"")),this.postMessage(a),!1):!0},handleError:function(a,b){b&&"error"==b.tagName?this.setStatus(NodeUtils.getNodeValue(b,"descr")):this.setStatus("reconnecting")},showStatusDiv:function(a){if($("engineinfo"))$("engineinfo").style.display="inline",$("engineinfo").innerHTML=a},setStatus:function(a){this.statusTimeout&&
clearTimeout(this.statusTimeout);this.showStatusDiv(a);this.statusTimeout=setTimeout(this.clearStatus.bind(this),4E3)},clearStatus:function(){$("engineinfo").style.display="none"}});
b.write("</body></html>");b.close();a.onload=function(){a.myHtml&&(FrameUtils.getDocument(a).getElementById("content").innerHTML+=a.myHtml,FrameUtils.scrollDown(a))}},insertIntoFrame:function(a,b){var c=this.getDocument(a).getElementById("content");null==c?(a.myHtml||(a.myHtml=""),a.myHtml+=b):c.innerHTML+=b},scrollDown:function(a){var b=this.getDocument(a).getElementById("bottom");if("opera"==myAgent)try{a.contentWindow.scrollTo(0,this.getDocument(a).getElementById("content").clientHeight)}catch(c){}b&&
b.scrollIntoView(!1)}};ChatServer=Class.create();
ChatServer.prototype={initialize:function(a){this.updateTimer=null;this.options={servl:"",requestsFrequency:2,onTimeout:function(){},onTransportError:function(){},onCallError:function(){},onUpdateError:function(){},onResponseError:function(){}}.extend(a);this.callbacks={};this.callPeriodically=[];this.ajaxOptions={_method:"post",asynchronous:!0,timeout:5E3,onComplete:this.receiveResponse.bind(this),onException:this.onTransportError.bind(this),onTimeout:this.onTimeout.bind(this)};this.ajaxRequest=
null;this.buffer=[];this.functions={};this.mibewAPI=new MibewAPI(new MibewAPICoreInteraction)},callFunctions:function(a,b,c){try{if(!(a instanceof Array))throw Error("The first arguments must be an array");for(var d in a)a.hasOwnProperty(d)&&this.mibewAPI.checkFunction(a[d],!1);var e=this.generateToken();this.callbacks[e]=b;this.buffer.push({token:e,functions:a});c&&this.update()}catch(f){return this.options.onCallError(f),!1}return!0},callFunctionsPeriodically:function(a,b){this.callPeriodically.push({functionsListBuilder:a,
callbackFunction:b})},generateToken:function(){var a;do a="wnd"+(new Date).getTime().toString()+Math.round(50*Math.random()).toString();while(a in this.callbacks);return a},processRequest:function(a){var b=new MibewAPIExecutionContext,c=this.mibewAPI.getResultFunction(a.functions,this.callbacks.hasOwnProperty(a.token));if(null===c)for(var d in a.functions)a.functions.hasOwnProperty(d)&&(this.processFunction(a.functions[d],b),this.buffer.push(this.mibewAPI.buildResult(b.getResults(),a.token)));else this.callbacks.hasOwnProperty(a.token)&&
(this.callbacks[a.token](c.arguments),delete this.callbacks[a.token])},processFunction:function(a,b){if(this.functions.hasOwnProperty(a["function"])){var c=b.getArgumentsList(a),d={},e;for(e in this.functions[a["function"]])this.functions[a["function"]].hasOwnProperty(e)&&d.extend(this.functions[a["function"]][e](c));b.storeFunctionResults(a,d)}},sendRequests:function(a){this.ajaxRequest=new Ajax.Request(this.options.servl,this.ajaxOptions.extend({parameters:"data="+this.mibewAPI.encodePackage(a)}))},
runUpdater:function(){null==this.updateTimer&&this.update();this.updateTimer=setTimeout(this.update.bind(this),1E3*this.options.requestsFrequency)},restartUpdater:function(){this.updateTimer&&clearTimeout(this.updateTimer);this.ajaxRequest._options&&(this.ajaxRequest._options.onComplete=void 0);this.update();this.updateTimer=setTimeout(this.update.bind(this),1E3)},update:function(){this.updateTimer&&clearTimeout(this.updateTimer);for(var a=0;a<this.callPeriodically.length;a++)this.callFunctions(this.callPeriodically[a].functionsListBuilder(),
this.callPeriodically[a].callbackFunction);if(0==this.buffer.length)this.runUpdater();else try{this.sendRequests(this.buffer),this.buffer=[]}catch(b){this.options.onUpdateError(b)}},receiveResponse:function(a){""==a.response&&this.runUpdater();try{var b=this.mibewAPI.decodePackage(a.response),c;for(c in b.requests)this.processRequest(b.requests[c])}catch(d){this.options.onResponseError(d)}finally{this.runUpdater()}},registerFunction:function(a,b){a in this.functions||(this.functions[a]=[]);this.functions[a].push(b)},
onTransportError:function(a,b){this.restartUpdater();this.options.onTransportError(b)},onTimeout:function(){this.restartUpdater();this.options.onTimeout()}};ChatThreadUpdater=Class.create();
ChatThreadUpdater.prototype={initialize:function(a,b,c){this._options=c;this.thread={threadid:0,token:0,lastid:0,user:!1}.extend(b||{});this.chatServer=a;this.focused=this.skipNextsound=this.cansend=!0;this.ownThread=null!=this._options.message;FrameUtils.initFrame(this._options.container);this._options.message&&(this._options.message.onkeydown=this.handleKeyDown.bind(this),this._options.message.onfocus=function(){this.focused=!0}.bind(this),this._options.message.onblur=function(){this.focused=!1}.bind(this));
this.chatServer.callFunctionsPeriodically(this.updateFunctionBuilder.bind(this),this.updateChatState.bind(this));this.chatServer.registerFunction("updateMessages",this.updateMessages.bind(this));this.chatServer.registerFunction("setupAvatar",this.setupAvatar.bind(this));this.chatServer.runUpdater()},handleException:function(){this.setStatus("offline, reconnecting");this.enableInput(!0)},handleTimeout:function(){this.setStatus("timeout, reconnecting");this.enableInput(!0)},enableInput:function(a){this._options.message&&
(this._options.message.disabled=!a)},refresh:function(){this.chatServer.restartUpdater()},postMessage:function(a){""!=a&&this.cansend&&(this.cansend=!1,this.skipNextsound=!0,"opera"!=myRealAgent&&this.enableInput(!1),this.chatServer.callFunctions([{"function":"post",arguments:{references:{},"return":{},message:a,threadId:this.thread.threadid,token:this.thread.token,user:this.thread.user}}],function(){this.enableInput(!0);this.cansend=!0;this.skipNextsound=!1;this._options.message&&(this._options.message.value=
"",this._options.message.focus())}.bind(this),!0))},changeName:function(a){this.skipNextsound=!0;this.chatServer.callFunctions([{"function":"rename",arguments:{references:{},"return":{},threadId:this.thread.threadid,token:this.thread.token,name:a}}],function(a){a.errorCode&&this.handleError(a,"cannot rename")}.bind(this),!0)},closeThread:function(){(!this._options.localizedStrings.closeConfirmation||confirm(this._options.localizedStrings.closeConfirmation))&&this.chatServer.callFunctions([{"function":"close",
arguments:{references:{},"return":{closed:"closed"},threadId:this.thread.threadid,token:this.thread.token,lastId:this.thread.lastid,user:this.thread.user}}],this.onThreadClosed.bind(this),!0)},onThreadClosed:function(a){a.closed?window.close():this.handleError(a,"cannot close")},processMessage:function(a,b){FrameUtils.insertIntoFrame(a,b)},showTyping:function(a){$("typingdiv")&&($("typingdiv").style.display=a?"inline":"none")},setupAvatar:function(a){this._options.avatar&&this.thread.user&&(this._options.avatar.innerHTML=
""!=a.imageLink?'<img src="'+this._options.webimRoot+'/images/free.gif" width="7" height="1" border="0" alt="" /><img src="'+a.imageLink+'" border="0" alt=""/>':"")},updateMessages:function(a){a.lastId&&(this.thread.lastid=a.lastId);for(var b=0;b<a.messages.length;b++)this.processMessage(this._options.container,a.messages[b]);this.clearStatus();0<a.messages.length&&(FrameUtils.scrollDown(this._options.container),this.skipNextsound||(a=$("soundimg"),(null==a||a.className.match(/\bisound\b/))&&playSound(this._options.webimRoot+
"/sounds/new_message.wav")),this.focused||window.focus());this.skipNextsound=!1},updateFunctionBuilder:function(){return[{"function":"update",arguments:{"return":{typing:"typing",canPost:"canPost"},references:{},threadId:this.thread.threadid,token:this.thread.token,lastId:this.thread.lastid,typed:this._options.message&&""!=this._options.message.value,user:this.thread.user}}]},updateChatState:function(a){if(a.errorCode)this.handleError(a,"refresh failed");else if("undefined"!=typeof a.typing&&this.showTyping(a.typing),
"undefined"!=typeof a.canPost&&(a.canPost&&!this.ownThread||this.ownThread&&!a.canPost))window.location.href=window.location.href},isSendkey:function(a,b){return 13==b&&(a||this._options.ignorectrl)||10==b},handleKeyDown:function(a){a?(ctrl=a.ctrlKey,a=a.which):(a=event.keyCode,ctrl=event.ctrlKey);return this._options.message&&this.isSendkey(ctrl,a)?(a=this._options.message.value,this._options.ignorectrl&&(a=a.replace(/[\r\n]+$/,"")),this.postMessage(a),!1):!0},handleError:function(a){a.errorCode?
this.setStatus(a.errorMessage):this.setStatus("reconnecting")},showStatusDiv:function(a){$("engineinfo")&&($("engineinfo").style.display="inline",$("engineinfo").innerHTML=a)},setStatus:function(a){this.statusTimeout&&clearTimeout(this.statusTimeout);this.showStatusDiv(a);this.statusTimeout=setTimeout(this.clearStatus.bind(this),4E3)},clearStatus:function(){$("engineinfo").style.display="none"}};
var Chat={threadUpdater:{},applyName:function(){Chat.threadUpdater.changeName($("uname").value);$("changename1").style.display="none";$("changename2").style.display="inline";$("unamelink").innerHTML=htmlescape($("uname").value)},showNameField:function(){$("changename1").style.display="inline";$("changename2").style.display="none"}};
Behaviour.register({"#postmessage a":function(a){a.onclick=function(){var a=$("msgwnd");a&&Chat.threadUpdater.postMessage(a.value)}},"select#predefined":function(a){a.onchange=function(){var a=$("msgwnd");if(0!=this.selectedIndex)a.value=Chat.predefinedAnswers[this.selectedIndex-1];this.selectedIndex=0;a.focus()}},"div#changename2 a":function(a){a.onclick=function(){Chat.showNameField();return!1}},"div#changename1 a":function(a){a.onclick=function(){Chat.applyName();return!1}},"div#changename1 input#uname":function(a){a.onkeydown=
function(a){13==(a||event).keyCode&&Chat.applyName()}},"a#refresh":function(a){a.onclick=function(){Chat.threadUpdater.stopUpdate();Chat.threadUpdater.update()}},"a#togglesound":function(a){a.onclick=function(){var a=$("soundimg");if(a)a.className=a.className.match(/\bisound\b/)?"tplimage inosound":"tplimage isound",(a=$("msgwnd"))&&a.focus()}},"a.closethread":function(a){a.onclick=function(){Chat.threadUpdater.closeThread()}}});
EventHelper.register(window,"onload",function(){Chat.webimRoot=threadParams.wroot;Chat.cssfile=threadParams.cssfile;Chat.predefinedAnswers="undefined"!=typeof predefinedAnswers?predefinedAnswers:[];Chat.localizedStrings=localizedStrings;Chat.threadUpdater=new Ajax.ChatThreadUpdater({ignorectrl:-1,container:"safari"==myRealAgent?self.frames[0]:$("chatwnd"),avatar:$("avatarwnd"),message:$("msgwnd")}.extend(threadParams||{}))});
Behaviour.register({"#postmessage a":function(a){a.onclick=function(){var a=$("msgwnd");a&&Chat.threadUpdater.postMessage(a.value)}},"select#predefined":function(a){a.onchange=function(){var a=$("msgwnd");0!=this.selectedIndex&&(a.value=Chat.predefinedAnswers[this.selectedIndex-1]);this.selectedIndex=0;a.focus()}},"div#changename2 a":function(a){a.onclick=function(){Chat.showNameField();return!1}},"div#changename1 a":function(a){a.onclick=function(){Chat.applyName();return!1}},"div#changename1 input#uname":function(a){a.onkeydown=
function(a){13==(a||event).keyCode&&Chat.applyName()}},"a#refresh":function(a){a.onclick=function(){Chat.threadUpdater.refresh()}},"a#togglesound":function(a){a.onclick=function(){var a=$("soundimg");a&&(a.className=a.className.match(/\bisound\b/)?"tplimage inosound":"tplimage isound",(a=$("msgwnd"))&&a.focus())}},"a.closethread":function(a){a.onclick=function(){Chat.threadUpdater.closeThread()}}});
EventHelper.register(window,"onload",function(){Chat.cssfile=chatParams.cssfile;Chat.predefinedAnswers=chatParams.predefinedAnswers||[];Chat.localizedStrings=chatParams.localizedStrings;Chat.threadUpdater=new ChatThreadUpdater(new ChatServer(chatParams.serverParams),chatParams.threadParams,{ignorectrl:-1,container:"safari"==myRealAgent?self.frames[0]:$("chatwnd"),avatar:$("avatarwnd"),message:$("msgwnd")}.extend(chatParams.threadUpdaterParams||{}))});

View File

@ -59,220 +59,734 @@ var FrameUtils = {
}
};
Ajax.ChatThreadUpdater = Class.create();
Class.inherit( Ajax.ChatThreadUpdater, Ajax.Base, {
ChatServer = Class.create();
/**
* @todo Think about error handling
*/
ChatServer.prototype = {
/**
* @constructor
*/
initialize: function(options) {
var chatServer = this;
initialize: function(_options) {
this.setOptions(_options);
this._options.onComplete = this.requestComplete.bind(this);
this._options.onException = this.handleException.bind(this);
this._options.onTimeout = this.handleTimeout.bind(this);
this._options.timeout = 5000;
this.updater = {};
this.frequency = (this._options.frequency || 2);
this.lastupdate = 0;
this.cansend = true;
this.skipNextsound = true;
this.focused = true;
this.ownThread = this._options.message != null;
FrameUtils.initFrame(this._options.container);
if( this._options.message ) {
this._options.message.onkeydown = this.handleKeyDown.bind(this);
this._options.message.onfocus = (function() { this.focused = true; }).bind(this);
this._options.message.onblur = (function() { this.focused = false; }).bind(this)
}
this.update();
/**
* Update timer
*/
this.updateTimer = null;
/**
* Options for the ChatServer object
* @private
* @todo Check onResponseError handler
*/
this.options = {
// Server gateway URL
servl: "",
// Frequency for automatic updater
requestsFrequency: 2,
// Call on request timeout
onTimeout: function() {},
// Call when transport error was caught
onTransportError: function(e) {},
// Call when callFunctions related error was caught
onCallError: function(e) {},
// Call when update related error was caught
onUpdateError: function(e) {},
// Call when response related error was caught
onResponseError: function(e) {}
}.extend(options);
/**
* Binds request's token and callback function
* @type Object
* @private
*/
this.callbacks = {};
/**
* Array of periodically called functions
* @type Array
* @private
*/
this.callPeriodically = [];
/**
* Options for an Ajax.Request object
* @type Array
* @private
*/
this.ajaxOptions = {
_method: 'post',
asynchronous: true,
timeout: 5000,
onComplete: chatServer.receiveResponse.bind(chatServer),
onException: chatServer.onTransportError.bind(chatServer),
onTimeout: chatServer.onTimeout.bind(chatServer)
}
/**
* An object of the Ajax.Request class
* @type Ajax.Request
* @private
*/
this.ajaxRequest = null;
/**
* This buffer store requests and responses between sending packages
* @private
*/
this.buffer = [];
/**
* Contains object of registered functions handlers
* @private
*/
this.functions = {}
/**
* An instance of the MibewAPI class
* @type MibewAPI
* @private
*/
this.mibewAPI = new MibewAPI(new MibewAPICoreInteraction());
},
handleException: function(_request, ex) {
this.setStatus("offline, reconnecting");
this.stopUpdate();
this.timer = setTimeout(this.update.bind(this), 1000);
/**
* Make call to the chat server
*
* @param {Oblect[]} functionsList List of the function objects. See Mibew API
* for details.
* @param {Function} callbackFunction
* @param {Boolean} forceSend Force requests buffer send right after call
* @returns {Boolean} boolean true on success and false on failure
*/
callFunctions: function(functionsList, callbackFunction, forceSend) {
try {
// Check function objects
if (!(functionsList instanceof Array)) {
throw new Error("The first arguments must be an array");
}
for (var i in functionsList) {
// Filter 'Prototype' properties
if (! functionsList.hasOwnProperty(i)) {
continue;
}
this.mibewAPI.checkFunction(functionsList[i], false);
}
// Generate request token
var token = this.generateToken();
// Store callback function
this.callbacks[token] = callbackFunction;
// Add request to buffer
this.buffer.push({
'token': token,
'functions': functionsList
});
if (forceSend) {
// Force update
this.update();
}
} catch (e) {
// Handle errors
this.options.onCallError(e);
return false;
}
return true;
},
handleTimeout: function(_request) {
this.setStatus("timeout, reconnecting");
this.stopUpdate();
this.timer = setTimeout(this.update.bind(this), 1000);
/**
* Call function at every request to build functions list
*
* @param {Function} functionsListBuilder Call before every request to build a
* list of functions that must be called
* @param {Function} callbackFunction Call after response received
*/
callFunctionsPeriodically: function(functionsListBuilder, callbackFunction) {
this.callPeriodically.push({
functionsListBuilder: functionsListBuilder,
callbackFunction: callbackFunction
});
},
updateOptions: function(act) {
this._options.parameters = 'act='+act+'&thread=' + (this._options.threadid || 0) +
'&token=' + (this._options.token || 0)+
'&lastid=' + (this._options.lastid || 0);
if( this._options.user )
this._options.parameters += "&user=true";
if( act == 'refresh' && this._options.message && this._options.message.value != '' )
this._options.parameters += "&typed=1";
/**
* Generates unique request token
*
* @private
* @returns {String} Request token
*/
generateToken: function() {
var token;
do {
// Create token
token = "wnd" +
(new Date()).getTime().toString() +
(Math.round(Math.random() * 50)).toString();
// Check token uniqueness
} while(token in this.callbacks);
return token;
},
enableInput: function(val) {
if( this._options.message )
this._options.message.disabled = !val;
/**
* Process request
*
* @param {Object} requestObject Request object. See Mibew API for details.
* @private
*/
processRequest: function(requestObject) {
var context = new MibewAPIExecutionContext();
// Get result function
var resultFunction = this.mibewAPI.getResultFunction(
requestObject.functions,
this.callbacks.hasOwnProperty(requestObject.token)
);
if (resultFunction === null) {
// Result function not found
for (var i in requestObject.functions) {
if (! requestObject.functions.hasOwnProperty(i)) {
continue;
}
// Execute functions
this.processFunction(requestObject.functions[i], context);
// Build and store result
this.buffer.push(this.mibewAPI.buildResult(
context.getResults(),
requestObject.token
));
}
} else {
// Result function found
if (this.callbacks.hasOwnProperty(requestObject.token)) {
// Invoke callback
this.callbacks[requestObject.token](resultFunction.arguments);
// Remove callback
delete this.callbacks[requestObject.token];
}
}
},
stopUpdate: function() {
this.enableInput(true);
if( this.updater._options )
this.updater._options.onComplete = undefined;
clearTimeout(this.timer);
/**
* Process function
*
* @param {Object} functionObject Function object. See Mibew API for details
* @param {MibewAPIExecutionContext} context Execution context
* @private
*/
processFunction: function(functionObject, context) {
if (! this.functions.hasOwnProperty(functionObject["function"])) {
return;
}
// Get function arguments with replaced refences
var functionArguments = context.getArgumentsList(functionObject);
var results = {};
for (var i in this.functions[functionObject["function"]]) {
if (! this.functions[functionObject["function"]].hasOwnProperty(i)) {
continue;
}
// Get results
results.extend(this.functions[functionObject["function"]][i](
functionArguments
));
}
// Add function results to the execution context
context.storeFunctionResults(functionObject, results);
},
/**
* Send the request to the chat server
*
* @param {Object[]} requestsList Array of requests that must be sent to the
* chat server
* @private
*/
sendRequests: function(requestsList) {
// Create new AJAX request
this.ajaxRequest = new Ajax.Request(
this.options.servl,
this.ajaxOptions.extend({
parameters: 'data=' + this.mibewAPI.encodePackage(requestsList)
})
);
},
/**
* Sets up next automatic updater iteration
*/
runUpdater: function() {
if (this.updateTimer == null) {
this.update();
}
this.updateTimer = setTimeout(
this.update.bind(this),
this.options.requestsFrequency * 1000
);
},
/**
* Restarts the automatic updater
*/
restartUpdater: function() {
// Clear timeout
if (this.updateTimer) {
clearTimeout(this.updateTimer);
}
// Clear request onComplete callback
if (this.ajaxRequest._options) {
this.ajaxRequest._options.onComplete = undefined;
}
// Update thread
this.update();
// Restart updater. Try to reconnect after a while
this.updateTimer = setTimeout(
this.update.bind(this),
1000
);
},
/**
* Send request for update thread and client code's requests
* @private
*/
update: function() {
this.updateOptions("refresh");
this.updater = new Ajax.Request(this._options.servl, this._options);
if (this.updateTimer) {
clearTimeout(this.updateTimer);
}
for (var i = 0; i < this.callPeriodically.length; i++) {
this.callFunctions(
this.callPeriodically[i].functionsListBuilder(),
this.callPeriodically[i].callbackFunction
);
}
// Check buffer length
if (this.buffer.length == 0) {
// Rerun updater later
this.runUpdater();
return;
}
try {
// Send requests
this.sendRequests(this.buffer);
// Clear requests buffer
this.buffer = [];
} catch (e) {
// Handle errors
this.options.onUpdateError(e);
}
},
requestComplete: function(_response) {
try {
/**
* Process response from the Core
*
* @param {String} responseObject The response object provided by
* Ajax.Request class
* @private
*/
receiveResponse: function(responseObject) {
// Do not parse empty responses
if (responseObject.response == '') {
this.runUpdater();
}
try {
var packageObject = this.mibewAPI.decodePackage(responseObject.response);
for (var i in packageObject.requests) {
this.processRequest(packageObject.requests[i]);
}
} catch (e) {
this.options.onResponseError(e);
} finally {
this.runUpdater();
}
},
/**
* Add function that can be called by the Core
*
* @param {String} functionName Name of the function
* @param {Function} handler Provided function
*/
registerFunction: function(functionName, handler) {
if (!(functionName in this.functions)) {
this.functions[functionName] = [];
}
this.functions[functionName].push(handler);
},
/**
* Call on all AJAX transport errors
* @param {Ajax.Request} transport AJAX Transport object
* @param {Error} e Error object
*/
onTransportError: function (transport, e) {
this.restartUpdater();
this.options.onTransportError(e);
},
/**
* Call on all timeouts
*/
onTimeout: function(transport) {
this.restartUpdater();
this.options.onTimeout()
}
}
ChatThreadUpdater = Class.create();
ChatThreadUpdater.prototype = {
/**
* @constructor
* @todo Add error handlers to chatServer
* @todo Think about code format
*/
initialize: function(chatServer, thread, options) {
/**
* Array of options
* @type Array
* @private
*/
this._options = options;
/**
* An instance of the Thread class
* @type ChatThread
*/
this.thread = {
threadid: 0,
token: 0,
lastid: 0,
user: false
}.extend(thread || {});
/**
* An instance of the ChatServer class
* @type ChatServer
*/
this.chatServer = chatServer;
/**
* Indicates if user can post messages
* @type Boolean
*/
this.cansend = true;
/**
* Indicates if next message's sound must be skipped
* @type Boolean
*/
this.skipNextsound = true;
/**
* Indicates if message input area ihn focus
* @type Boolean
*/
this.focused = true;
/**
* Indicates the thread belong to this operator
* @type Boolean
*/
this.ownThread = this._options.message != null;
FrameUtils.initFrame(this._options.container);
if (this._options.message) {
this._options.message.onkeydown = this.handleKeyDown.bind(this);
this._options.message.onfocus = (function() {this.focused = true;}).bind(this);
this._options.message.onblur = (function() {this.focused = false;}).bind(this);
}
// Add periodic functions
this.chatServer.callFunctionsPeriodically(
this.updateFunctionBuilder.bind(this),
this.updateChatState.bind(this)
);
// Register functions
this.chatServer.registerFunction(
'updateMessages',
this.updateMessages.bind(this)
);
this.chatServer.registerFunction(
'setupAvatar',
this.setupAvatar.bind(this)
);
this.chatServer.runUpdater();
},
/**
* Exception handler. Updates status message
*/
handleException: function(e) {
this.setStatus("offline, reconnecting");
this.enableInput(true);
this.cansend = true;
var xmlRoot = Ajax.getXml(_response);
if( xmlRoot && xmlRoot.tagName == 'thread' ) {
this.updateContent( xmlRoot );
} else {
this.handleError(_response, xmlRoot, 'refresh messages failed');
}
} catch (e) {
}
this.skipNextsound = false;
this.timer = setTimeout(this.update.bind(this), this.frequency * 1000);
},
/**
* Timeout handler. Updates status message
*/
handleTimeout: function() {
this.setStatus("timeout, reconnecting");
this.enableInput(true);
},
/**
* Enables or disables input field
* @param {Boolean} val Use boolean true for enable input and false otherwise
*/
enableInput: function(val) {
if( this._options.message ) {
this._options.message.disabled = !val;
}
},
/**
* Load new messages by restarting thread updater.
*/
refresh: function() {
this.chatServer.restartUpdater();
},
/**
* Sends message to the chat server
* @param {String} msg Message for send
*/
postMessage: function(msg) {
if( msg == "" || !this.cansend) {
return;
}
this.cansend = false;
this.stopUpdate();
this.skipNextsound = true;
this.updateOptions("post");
var postOptions = {}.extend(this._options);
postOptions.parameters += "&message=" + encodeURIComponent(msg);
postOptions.onComplete = (function(presponse) {
this.requestComplete( presponse );
if( this._options.message ) {
this._options.message.value = '';
this._options.message.focus();
}
}).bind(this);
if( myRealAgent != 'opera' )
this.enableInput(false);
this.updater = new Ajax.Request(this._options.servl, postOptions);
// Check if message can be sent
if(msg == "" || !this.cansend) {
return;
}
// Disable message sending
this.cansend = false;
// Disable next sound
this.skipNextsound = true;
// Disable input
if(myRealAgent != 'opera') {
this.enableInput(false);
}
// Post message
this.chatServer.callFunctions(
[{
"function": "post",
"arguments": {
"references": {},
"return": {},
"message": msg,
"threadId": this.thread.threadid,
"token": this.thread.token,
"user": this.thread.user
}
}],
(function(){
this.enableInput(true);
this.cansend = true;
this.skipNextsound = false;
if(this._options.message) {
this._options.message.value = '';
this._options.message.focus();
}
}).bind(this),
true
);
},
/**
* Change user name
* @param {String} newname A new user name
*/
changeName: function(newname) {
this.skipNextsound = true;
new Ajax.Request(this._options.servl, {parameters:'act=rename&thread=' + (this._options.threadid || 0) +
'&token=' + (this._options.token || 0) + '&name=' + encodeURIComponent(newname)});
},
onThreadClosed: function(_response) {
var xmlRoot = Ajax.getXml(_response);
if( xmlRoot && xmlRoot.tagName == 'closed' ) {
setTimeout('window.close()', 2000);
} else {
this.handleError(_response, xmlRoot, 'cannot close');
}
this.skipNextsound = true;
this.chatServer.callFunctions(
[{
"function": "rename",
"arguments": {
"references": {},
"return": {},
"threadId": this.thread.threadid,
"token": this.thread.token,
"name": newname
}
}],
(function(args){
if (args.errorCode) {
this.handleError(args, 'cannot rename');
}
}).bind(this),
true
);
},
/**
* Send request for close chat to the core
*/
closeThread: function() {
if(typeof Chat.localizedStrings.closeConfirmation != 'undefined' && Chat.localizedStrings.closeConfirmation){
if(! confirm(Chat.localizedStrings.closeConfirmation)){
return false;
}
}
var _params = 'act=close&thread=' + (this._options.threadid || 0) + '&token=' + (this._options.token || 0);
if( this._options.user )
_params += "&user=true";
new Ajax.Request(this._options.servl, {parameters:_params, onComplete: this.onThreadClosed.bind(this)});
// Show confirmation message if can
if(this._options.localizedStrings.closeConfirmation){
if(! confirm(this._options.localizedStrings.closeConfirmation)){
return;
}
}
// Send request
this.chatServer.callFunctions(
[{
"function": "close",
"arguments": {
"references": {},
"return": {"closed": "closed"},
"threadId": this.thread.threadid,
"token": this.thread.token,
"lastId": this.thread.lastid,
"user": this.thread.user
}
}],
this.onThreadClosed.bind(this),
true
);
},
/**
* Callback function for close chat request.
*
* Close chat window if closing success or warn on fail
*/
onThreadClosed: function(args) {
if (args.closed) {
window.close();
} else {
this.handleError(args, 'cannot close');
}
},
/**
* Add message to the message window
* @param {Object} _target Target DOM element
* @param {String} message HTML message to insert
*/
processMessage: function(_target, message) {
var destHtml = NodeUtils.getNodeText(message);
FrameUtils.insertIntoFrame(_target, destHtml );
FrameUtils.insertIntoFrame(_target, message);
},
/**
* Displays typing status
* @param {Boolean} istyping Indicates the other side of conversation is
* typing a message or not
*/
showTyping: function(istyping) {
if( $("typingdiv") ) {
$("typingdiv").style.display=istyping ? 'inline' : 'none';
}
},
setupAvatar: function(avatar) {
var imageLink = NodeUtils.getNodeText(avatar);
if( this._options.avatar && this._options.user ) {
this._options.avatar.innerHTML = imageLink != ""
? "<img src=\""+Chat.webimRoot+"/images/free.gif\" width=\"7\" height=\"1\" border=\"0\" alt=\"\" /><img src=\""
+imageLink+ "\" border=\"0\" alt=\"\"/>"
: "";
/**
* Update operator's avatar
* @param {Array} args Array of arguments passed from the core
*/
setupAvatar: function(args) {
if (this._options.avatar && this.thread.user) {
this._options.avatar.innerHTML = args.imageLink != ""
? "<img src=\""+this._options.webimRoot+"/images/free.gif\" width=\"7\" height=\"1\" border=\"0\" alt=\"\" /><img src=\""
+args.imageLink+ "\" border=\"0\" alt=\"\"/>"
: "";
}
},
updateContent: function(xmlRoot) {
var haveMessage = false;
var result_div = this._options.container;
var _lastid = NodeUtils.getAttrValue(xmlRoot, "lastid");
if( _lastid ) {
this._options.lastid = _lastid;
}
var typing = NodeUtils.getAttrValue(xmlRoot, "typing");
if( typing ) {
this.showTyping(typing == '1');
}
var canpost = NodeUtils.getAttrValue(xmlRoot, "canpost");
if( canpost ) {
if( canpost == '1' && !this.ownThread || this.ownThread && canpost != '1' ) {
window.location.href = window.location.href;
}
}
for( var i = 0; i < xmlRoot.childNodes.length; i++ ) {
var node = xmlRoot.childNodes[i];
if( node.tagName == 'message' ) {
haveMessage = true;
this.processMessage(result_div, node);
} else if( node.tagName == 'avatar' ) {
this.setupAvatar(node);
}
}
if(window.location.search.indexOf('trace=on')>=0) {
var val = "updated";
if(this.lastupdate > 0) {
var seconds = ((new Date()).getTime() - this.lastupdate)/1000;
val = val + ", " + seconds + " secs";
if(seconds > 10) {
alert(val);
}
}
this.lastupdate = (new Date()).getTime();
this.setStatus(val);
} else {
this.clearStatus();
}
if( haveMessage ) {
FrameUtils.scrollDown(this._options.container);
if(!this.skipNextsound) {
var tsound = $('soundimg');
if(tsound == null || tsound.className.match(new RegExp("\\bisound\\b")) ) {
playSound(Chat.webimRoot+'/sounds/new_message.wav');
}
}
if( !this.focused ) {
window.focus();
}
}
/**
* Add new messages to chat window
* @param {Object} args object of function arguments passed from the server
* @todo Fix skipNextSound
*/
updateMessages: function(args){
// Update last message id
if (args.lastId) {
this.thread.lastid = args.lastId;
}
// Add messages
for (var i = 0; i < args.messages.length; i++) {
// TODO: Add template engine
this.processMessage(this._options.container, args.messages[i]);
}
// Clear status string
this.clearStatus();
// There are some new messages
if (args.messages.length > 0) {
FrameUtils.scrollDown(this._options.container);
if (!this.skipNextsound) {
var tsound = $('soundimg');
if (tsound == null || tsound.className.match(new RegExp("\\bisound\\b"))) {
playSound(this._options.webimRoot+'/sounds/new_message.wav');
}
}
if (!this.focused) {
window.focus();
}
}
this.skipNextsound = false;
},
/**
* Build update function to call at the core
*/
updateFunctionBuilder: function() {
return [
{
"function": "update",
"arguments": {
"return": {'typing': 'typing', 'canPost': 'canPost'},
"references": {},
"threadId": this.thread.threadid,
"token": this.thread.token,
"lastId": this.thread.lastid,
"typed": (this._options.message && this._options.message.value != ''),
"user": this.thread.user
}
}
];
},
/**
* Set current chat state message
* @param {Array} args Array of arguments passed from the core
*/
updateChatState: function(args) {
if (args.errorCode) {
// Something went wrong
this.handleError(args, 'refresh failed');
return;
}
// Update typing indicator
if (typeof args.typing != 'undefined') {
this.showTyping(args.typing);
}
// Check if user can post messages
if (typeof args.canPost != 'undefined') {
if ((args.canPost && !this.ownThread) || (this.ownThread && ! args.canPost)) {
// Refresh the page
window.location.href = window.location.href;
}
}
},
/**
* Check if send key (Enter or Ctrl+Enter) pressed
* @param {Boolean} ctrlpressed Indicates ctrl key is pressed or not
* @param {Number} key Key code
*/
isSendkey: function(ctrlpressed, key) {
return ((key==13 && (ctrlpressed || this._options.ignorectrl)) || (key==10));
},
/**
* Key down handler
*
* @param {Object} k Event object
*/
handleKeyDown: function(k) {
if( k ){ ctrl=k.ctrlKey;k=k.which; } else { k=event.keyCode;ctrl=event.ctrlKey; }
if( k ){ctrl=k.ctrlKey;k=k.which;} else {k=event.keyCode;ctrl=event.ctrlKey;}
if( this._options.message && this.isSendkey(ctrl, k) ) {
var mmsg = this._options.message.value;
if( this._options.ignorectrl ) {
@ -284,14 +798,26 @@ Class.inherit( Ajax.ChatThreadUpdater, Ajax.Base, {
return true;
},
handleError: function(_response, xmlRoot, _action) {
if( xmlRoot && xmlRoot.tagName == 'error' ) {
this.setStatus(NodeUtils.getNodeValue(xmlRoot,"descr"));
} else {
this.setStatus("reconnecting");
}
/**
* Update status message
*
* @param {Array} args Array of arguments. Must contain 'errorCode' and
* 'errorMessage' keys
* @param {String} descr Error description
*/
handleError: function(args, descr) {
if (args.errorCode) {
this.setStatus(args.errorMessage);
} else {
this.setStatus('reconnecting');
}
},
/**
* Displays status div and sets the status string into it
*
* @param {String} k Status string
*/
showStatusDiv: function(k) {
if( $("engineinfo") ) {
$("engineinfo").style.display='inline';
@ -299,6 +825,11 @@ Class.inherit( Ajax.ChatThreadUpdater, Ajax.Base, {
}
},
/**
* Sets the status
*
* @param {String} k Status string
*/
setStatus: function(k) {
if( this.statusTimeout )
clearTimeout(this.statusTimeout);
@ -306,11 +837,13 @@ Class.inherit( Ajax.ChatThreadUpdater, Ajax.Base, {
this.statusTimeout = setTimeout(this.clearStatus.bind(this), 4000);
},
/**
* Hide the status string
*/
clearStatus: function() {
$("engineinfo").style.display='none';
}
});
}
var Chat = {
threadUpdater : {},
@ -368,8 +901,7 @@ Behaviour.register({
},
'a#refresh' : function(el) {
el.onclick = function() {
Chat.threadUpdater.stopUpdate();
Chat.threadUpdater.update();
Chat.threadUpdater.refresh();
};
},
'a#togglesound' : function(el) {
@ -396,9 +928,17 @@ Behaviour.register({
});
EventHelper.register(window, 'onload', function(){
Chat.webimRoot = threadParams.wroot;
Chat.cssfile = threadParams.cssfile;
Chat.predefinedAnswers = (typeof predefinedAnswers != 'undefined')?predefinedAnswers:Array();
Chat.localizedStrings = localizedStrings;
Chat.threadUpdater = new Ajax.ChatThreadUpdater(({ignorectrl:-1,container:myRealAgent=='safari'?self.frames[0]:$("chatwnd"),avatar:$("avatarwnd"),message:$("msgwnd")}).extend( threadParams || {} ));
Chat.cssfile = chatParams.cssfile;
Chat.predefinedAnswers = chatParams.predefinedAnswers || [];
Chat.localizedStrings = chatParams.localizedStrings;
Chat.threadUpdater = new ChatThreadUpdater(
new ChatServer(chatParams.serverParams),
chatParams.threadParams,
{
ignorectrl: -1,
container: myRealAgent=='safari'?self.frames[0]:$("chatwnd"),
avatar: $("avatarwnd"),
message: $("msgwnd")
}.extend(chatParams.threadUpdaterParams || {})
);
});

View File

@ -26,33 +26,6 @@ function get_user_id()
return (time() + microtime()) . rand(0, 99999999);
}
function prepare_html_message($text, $allow_formating)
{
$escaped_text = htmlspecialchars($text);
$text_w_links = preg_replace('/(https?|ftp):\/\/\S*/', '<a href="$0" target="_blank">$0</a>', $escaped_text);
$multiline = str_replace("\n", "<br/>", $text_w_links);
if (! $allow_formating) {
return $multiline;
}
$formated = preg_replace('/&lt;(span|strong)&gt;(.*)&lt;\/\1&gt;/U', '<$1>$2</$1>', $multiline);
$formated = preg_replace('/&lt;span class=&quot;(.*)&quot;&gt;(.*)&lt;\/span&gt;/U', '<span class="$1">$2</span>', $formated);
return $formated;
}
function message_to_html($msg)
{
if ($msg['ikind'] == Thread::KIND_AVATAR) {
return "";
}
$message = "<span>" . date("H:i:s", $msg['created']) . "</span> ";
$kind = Thread::kindToString($msg['ikind']);
if ($msg['tname'])
$message .= "<span class='n$kind'>" . htmlspecialchars($msg['tname']) . "</span>: ";
$allow_formating = ($msg['ikind'] != Thread::KIND_USER && $msg['ikind'] != Thread::KIND_AGENT);
$message .= "<span class='m$kind'>" . prepare_html_message($msg['tmessage'], $allow_formating) . "</span><br/>";
return $message;
}
function message_to_text($msg)
{
if ($msg['ikind'] == Thread::KIND_AVATAR) {
@ -71,93 +44,6 @@ function message_to_text($msg)
}
}
function get_messages($threadid, $meth, $isuser, &$lastid)
{
global $webim_encoding;
$db = Database::getInstance();
$msgs = $db->query(
"select messageid,ikind,dtmcreated as created,tname,tmessage from {chatmessage} " .
"where threadid = :threadid and messageid > :lastid " .
($isuser ? "and ikind <> ". Thread::KIND_FOR_AGENT : "") .
" order by messageid",
array(
':threadid' => $threadid,
':lastid' => $lastid,
),
array('return_rows' => Database::RETURN_ALL_ROWS)
);
$messages = array();
foreach ($msgs as $msg) {
$message = "";
if ($meth == 'xml') {
switch ($msg['ikind']) {
case Thread::KIND_AVATAR:
$message = "<avatar>" . myiconv($webim_encoding, "utf-8", escape_with_cdata($msg['tmessage'])) . "</avatar>";
break;
default:
$message = "<message>" . myiconv($webim_encoding, "utf-8", escape_with_cdata(message_to_html($msg))) . "</message>\n";
}
} else {
if ($msg['ikind'] != Thread::KIND_AVATAR) {
$message = (($meth == 'text') ? message_to_text($msg) : topage(message_to_html($msg)));
}
}
$messages[] = $message;
if ($msg['messageid'] > $lastid) {
$lastid = $msg['messageid'];
}
}
return $messages;
}
function print_thread_messages($thread, $token, $lastid, $isuser, $format, $agentid = null)
{
global $webim_encoding, $webimroot;
$threadid = $thread->id;
$istyping = abs(time() - $isuser ? $thread->lastPingAgent : $thread->lastPingUser) < Thread::CONNECTION_TIMEOUT
&& (($isuser ? $thread->agentTyping : $thread->userTyping) == "1") ? "1" : "0";
if ($format == "xml") {
$output = get_messages($threadid, "xml", $isuser, $lastid);
start_xml_output();
print("<thread lastid=\"$lastid\" typing=\"" . $istyping . "\" canpost=\"" . (($isuser || $agentid != null && $agentid == $thread->agentId) ? 1 : 0) . "\">");
foreach ($output as $msg) {
print $msg;
}
print("</thread>");
} else if ($format == "html") {
$output = get_messages($threadid, "html", $isuser, $lastid);
start_html_output();
$url = "$webimroot/thread.php?act=refresh&amp;thread=$threadid&amp;token=$token&amp;html=on&amp;user=" . ($isuser ? "true" : "false");
print(
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">" .
"<html>\n<head>\n" .
"<link href=\"$webimroot/styles/default/chat.css\" rel=\"stylesheet\" type=\"text/css\">\n" .
"<meta http-equiv=\"Refresh\" content=\"" . Settings::get('updatefrequency_oldchat') . "; URL=$url&amp;sn=11\">\n" .
"<meta http-equiv=\"Pragma\" content=\"no-cache\">\n" .
"<title>chat</title>\n" .
"</head>\n" .
"<body bgcolor='#FFFFFF' text='#000000' link='#C28400' vlink='#C28400' alink='#C28400' onload=\"if( location.hash != '#aend' ){location.hash='#aend';}\">" .
"<table width='100%' cellspacing='0' cellpadding='0' border='0'><tr><td valign='top' class='message'>");
foreach ($output as $msg) {
print $msg;
}
print(
"</td></tr></table><a name='aend'></a>" .
"</body></html>");
}
}
function get_user_name($username, $addr, $id)
{
return str_replace(

View File

@ -1,89 +0,0 @@
<?php
/*
* Copyright 2005-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function demo_print_message($msg, $format)
{
global $webim_encoding;
if ($format == "xml") {
print "<message>" . myiconv($webim_encoding, "utf-8", escape_with_cdata(message_to_html($msg))) . "</message>\n";
} else {
print topage(message_to_html($msg));
}
}
function demo_process_thread($act, $outformat, $lastid, $isuser, $canpost, $istyping, $postmessage)
{
global $webimroot;
if ($act == "refresh" || $act == "post") {
$lastid++;
if ($outformat == "xml") {
start_xml_output();
print("<thread lastid=\"$lastid\" typing=\"" . ($istyping ? 1 : 0) . "\" canpost=\"" . ($canpost ? 1 : 0) . "\">");
} else {
start_html_output();
$url = "$webimroot/thread.php?act=refresh&amp;thread=0&amp;token=123&amp;html=on&amp;user=" . ($isuser ? "true" : "false");
print(
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">" .
"<html>\n<head>\n" .
"<link href=\"$webimroot/styles/default/chat.css\" rel=\"stylesheet\" type=\"text/css\">\n" .
"<meta http-equiv=\"Refresh\" content=\"" . Settings::get('updatefrequency_oldchat') . "; URL=$url&amp;sn=11\">\n" .
"<meta http-equiv=\"Pragma\" content=\"no-cache\">\n" .
"<title>chat</title>\n" .
"</head>\n" .
"<body bgcolor='#FFFFFF' text='#000000' link='#C28400' vlink='#C28400' alink='#C28400'>" .
"<table width='100%' cellspacing='0' cellpadding='0' border='0'><tr><td valign='top' class='message'>");
}
if ($lastid == 1) {
demo_print_message(
array('ikind' => Thread::KIND_FOR_AGENT, 'created' => time() - 15, 'tname' => '',
'tmessage' => getstring2('chat.came.from', array("http://google.com"))), $outformat);
demo_print_message(
array('ikind' => Thread::KIND_INFO, 'created' => time() - 15, 'tname' => '',
'tmessage' => getstring('chat.wait')), $outformat);
demo_print_message(
array('ikind' => Thread::KIND_EVENTS, 'created' => time() - 10, 'tname' => '',
'tmessage' => getstring2("chat.status.operator.joined", array("Administrator"))), $outformat);
demo_print_message(
array('ikind' => Thread::KIND_AGENT, 'created' => time() - 9, 'tname' => 'Administrator',
'tmessage' => getstring("demo.chat.welcome")), $outformat);
demo_print_message(
array('ikind' => Thread::KIND_USER, 'created' => time() - 5, 'tname' => getstring("chat.default.username"),
'tmessage' => getstring("demo.chat.question")), $outformat);
if ($canpost && $outformat == 'xml') {
demo_print_message(
array('ikind' => Thread::KIND_INFO, 'created' => time() - 5, 'tname' => '',
'tmessage' => 'Hint: type something in message field to see typing notification'), $outformat);
}
}
if ($act == 'post') {
demo_print_message(
array('ikind' => $isuser ? Thread::KIND_USER : Thread::KIND_AGENT, 'created' => time(), 'tmessage' => $postmessage,
'tname' => $isuser ? getstring("chat.default.username") : "Administrator"), $outformat);
}
if ($outformat == "xml") {
print("</thread>");
} else {
print(
"</td></tr></table><a name='aend'></a>" .
"</body></html>");
}
}
}
?>

View File

@ -53,10 +53,10 @@ if( count($errors) > 0 ) {
}
$history = "";
$lastid = -1;
$output = get_messages( $threadid,"text",true,$lastid );
foreach( $output as $msg ) {
$history .= $msg;
$last_id = -1;
$messages = $thread->getMessages(true, $last_id);
foreach ($messages as $msg) {
$history .= message_to_text($msg);
}
$subject = getstring("mail.user.history.subject");

View File

@ -45,10 +45,20 @@ function thread_info($id)
if (isset($_GET['threadid'])) {
// Load thread info
$threadid = verifyparam("threadid", "/^(\d{1,9})?$/", "");
$thread_info = thread_info($threadid);
$page['thread_info'] = $thread_info;
// Build messages list
$lastid = -1;
$page['threadMessages'] = get_messages($threadid, "html", false, $lastid);
$page['thread_info'] = thread_info($threadid);
$messages = $thread_info['thread']->getMessages(false, $lastid);
foreach ($messages as $msg) {
if ($msg['ikind'] == Thread::KIND_AVATAR) {
continue;
}
$page['threadMessages'][] = Thread::themeMessage($msg);
}
}
prepare_menu($operator, false);

View File

@ -5,13 +5,30 @@
<link rel="shortcut icon" href="${webimroot}/images/favicon.ico" type="image/x-icon">
<link rel="stylesheet" type="text/css" href="${tplroot}/chat.css">
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/common.js"></script>
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/mibewapi.js"></script>
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/json2.js"></script>
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/brws.js"></script>
<script type="text/javascript" language="javascript"><!--
var localizedStrings = {closeConfirmation:"${page:chat.close.confirmation}"};
${if:agent}${if:canpost}
var predefinedAnswers = ${page:fullPredefinedAnswers};
${endif:canpost}${endif:agent}
var threadParams = { servl:"${webimroot}/thread.php",wroot:"${webimroot}",frequency:${page:frequency},${if:user}user:"true",${endif:user}threadid:${page:ct.chatThreadId},token:${page:ct.token},cssfile:"${tplroot}/chat.css",ignorectrl:${page:ignorectrl} };
var chatParams = {
cssfile: "${tplroot}/chat.css",
${if:agent}${if:canpost}
predefinedAnswers: ${page:fullPredefinedAnswers},
${endif:canpost}${endif:agent}
threadParams: {
user:${if:user}true${else:user}false${endif:user},
threadid:${page:ct.chatThreadId},
token:${page:ct.token}
},
serverParams: {
servl: "${webimroot}/thread.php",
requestsFrequency: ${page:frequency}
},
threadUpdaterParams: {
webimRoot: "${webimroot}",
ignorectrl:${page:ignorectrl},
localizedStrings: {closeConfirmation:"${page:chat.close.confirmation}"}
}
}
//-->
</script>
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/chat.js"></script>

View File

@ -5,13 +5,30 @@
<link rel="shortcut icon" href="${webimroot}/images/favicon.ico" type="image/x-icon">
<link rel="stylesheet" type="text/css" href="${tplroot}/chat.css">
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/common.js"></script>
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/mibewapi.js"></script>
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/json2.js"></script>
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/brws.js"></script>
<script type="text/javascript" language="javascript"><!--
var localizedStrings = {closeConfirmation:"${page:chat.close.confirmation}"};
${if:agent}${if:canpost}
var predefinedAnswers = ${page:fullPredefinedAnswers};
${endif:canpost}${endif:agent}
var threadParams = { servl:"${webimroot}/thread.php",wroot:"${webimroot}",frequency:${page:frequency},${if:user}user:"true",${endif:user}threadid:${page:ct.chatThreadId},token:${page:ct.token},cssfile:"${tplroot}/chat.css",ignorectrl:${page:ignorectrl} };
var chatParams = {
cssfile: "${tplroot}/chat.css",
${if:agent}${if:canpost}
predefinedAnswers: ${page:fullPredefinedAnswers},
${endif:canpost}${endif:agent}
threadParams: {
user:${if:user}true${else:user}false${endif:user},
threadid:${page:ct.chatThreadId},
token:${page:ct.token}
},
serverParams: {
servl: "${webimroot}/thread.php",
requestsFrequency: ${page:frequency}
},
threadUpdaterParams: {
webimRoot: "${webimroot}",
ignorectrl:${page:ignorectrl},
localizedStrings: {closeConfirmation:"${page:chat.close.confirmation}"}
}
}
//-->
</script>
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/chat.js"></script>

View File

@ -6,16 +6,31 @@
<link rel="shortcut icon" href="${webimroot}/images/favicon.ico" type="image/x-icon" />
<link rel="stylesheet" type="text/css" href="${tplroot}/chat.css" media="all" />
<script type="text/javascript" src="${webimroot}/js/${jsver}/common.js"></script>
<script type="text/javascript" src="${webimroot}/js/${jsver}/mibewapi.js"></script>
<script type="text/javascript" src="${webimroot}/js/${jsver}/json2.js"></script>
<script type="text/javascript" src="${webimroot}/js/${jsver}/brws.js"></script>
<script type="text/javascript">
<!--
var localizedStrings = {closeConfirmation:"${page:chat.close.confirmation}"};
${if:agent}${if:canpost}
var predefinedAnswers = ${page:fullPredefinedAnswers};
${endif:canpost}${endif:agent}
var threadParams = {
servl:"${webimroot}/thread.php",wroot:"${webimroot}",frequency:${page:frequency},${if:user}user:"true",${endif:user}threadid:${page:ct.chatThreadId},token:${page:ct.token},cssfile:"${tplroot}/chat.css",ignorectrl:${page:ignorectrl}
};
var chatParams = {
cssfile: "${tplroot}/chat.css",
${if:agent}${if:canpost}
predefinedAnswers: ${page:fullPredefinedAnswers},
${endif:canpost}${endif:agent}
threadParams: {
user:${if:user}true${else:user}false${endif:user},
threadid:${page:ct.chatThreadId},
token:${page:ct.token}
},
serverParams: {
servl: "${webimroot}/thread.php",
requestsFrequency: ${page:frequency}
},
threadUpdaterParams: {
webimRoot: "${webimroot}",
ignorectrl:${page:ignorectrl},
localizedStrings: {closeConfirmation:"${page:chat.close.confirmation}"}
}
}
var stxt = 10;
function getClientHeight() {
return document.compatMode=='CSS1Compat' || !window.opera?document.documentElement.clientHeight:document.body.clientHeight;

View File

@ -5,13 +5,30 @@
<link rel="shortcut icon" href="${webimroot}/images/favicon.ico" type="image/x-icon">
<link rel="stylesheet" type="text/css" href="${tplroot}/chat.css">
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/common.js"></script>
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/mibewapi.js"></script>
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/json2.js"></script>
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/brws.js"></script>
<script type="text/javascript" language="javascript"><!--
var localizedStrings = {closeConfirmation:"${page:chat.close.confirmation}"};
${if:agent}${if:canpost}
var predefinedAnswers = ${page:fullPredefinedAnswers};
${endif:canpost}${endif:agent}
var threadParams = { servl:"${webimroot}/thread.php",wroot:"${webimroot}",frequency:${page:frequency},${if:user}user:"true",${endif:user}threadid:${page:ct.chatThreadId},token:${page:ct.token},cssfile:"${tplroot}/chat.css",ignorectrl:${page:ignorectrl} };
var chatParams = {
cssfile: "${tplroot}/chat.css",
${if:agent}${if:canpost}
predefinedAnswers: ${page:fullPredefinedAnswers},
${endif:canpost}${endif:agent}
threadParams: {
user:${if:user}true${else:user}false${endif:user},
threadid:${page:ct.chatThreadId},
token:${page:ct.token}
},
serverParams: {
servl: "${webimroot}/thread.php",
requestsFrequency: ${page:frequency}
},
threadUpdaterParams: {
webimRoot: "${webimroot}",
ignorectrl:${page:ignorectrl},
localizedStrings: {closeConfirmation:"${page:chat.close.confirmation}"}
}
}
//-->
</script>
<script type="text/javascript" language="javascript" src="${webimroot}/js/${jsver}/chat.js"></script>

View File

@ -19,97 +19,13 @@ require_once('libs/init.php');
require_once('libs/chat.php');
require_once('libs/operator.php');
require_once('libs/classes/thread.php');
require_once('libs/classes/mibew_api.php');
require_once('libs/classes/mibew_api_interaction.php');
require_once('libs/classes/mibew_api_window_interaction.php');
require_once('libs/classes/mibew_api_execution_context.php');
require_once('libs/classes/thread_processor.php');
$act = verifyparam( "act", "/^(refresh|post|rename|close|ping)$/");
$token = verifyparam( "token", "/^\d{1,9}$/");
$threadid = verifyparam( "thread", "/^\d{1,9}$/");
$isuser = verifyparam( "user", "/^true$/", "false") == 'true';
$outformat = ((verifyparam( "html", "/^on$/", "off") == 'on') ? "html" : "xml");
$istyping = verifyparam( "typed", "/^1$/", "") == '1';
if($threadid == 0 && ($token == 123 || $token == 124)) {
require_once('libs/demothread.php');
$lastid = verifyparam( "lastid", "/^\d{1,9}$/", 0);
demo_process_thread($act,$outformat,$lastid,$isuser,$token == 123,$istyping,$act=="post"?getrawparam('message') : "");
exit;
}
$thread = Thread::load($threadid, $token);
if (! $thread) {
die("wrong thread");
}
function show_ok_result($resid) {
start_xml_output();
echo "<$resid></$resid>";
exit;
}
function show_error($message) {
start_xml_output();
echo "<error><descr>$message</descr></error>";
exit;
}
$thread->ping($isuser, $istyping);
if( !$isuser && $act != "rename" ) {
$operator = check_login();
$thread->checkForReassign($operator);
}
if( $act == "refresh" ) {
$lastid = verifyparam( "lastid", "/^\d{1,9}$/", -1);
print_thread_messages($thread, $token, $lastid, $isuser,$outformat, $isuser ? null : $operator['operatorid']);
exit;
} else if( $act == "post" ) {
$lastid = verifyparam( "lastid", "/^\d{1,9}$/", -1);
$message = getrawparam('message');
$kind = $isuser ? Thread::KIND_USER : Thread::KIND_AGENT;
$from = $isuser ? $thread->userName : $thread->agentName;
if(!$isuser && $operator['operatorid'] != $thread->agentId) {
show_error("cannot send");
}
$postedid = $thread->postMessage(
$kind,
$message,
$from,
$isuser ? null : $operator['operatorid']
);
if($isuser && $thread->shownMessageId == 0) {
$thread->shownMessageId = $postedid;
$thread->save();
}
print_thread_messages($thread, $token, $lastid, $isuser, $outformat, $isuser ? null : $operator['operatorid']);
exit;
} else if( $act == "rename" ) {
if( Settings::get('usercanchangename') != "1" ) {
show_error("server: forbidden to change name");
}
$newname = getrawparam('name');
$thread->renameUser($newname);
$data = strtr(base64_encode(myiconv($webim_encoding,"utf-8",$newname)), '+/=', '-_,');
setcookie($namecookie, $data, time()+60*60*24*365);
show_ok_result("rename");
} else if( $act == "ping" ) {
show_ok_result("ping");
} else if( $act == "close" ) {
if( $isuser || $thread->agentId == $operator['operatorid']) {
$thread->close($isuser);
}
show_ok_result("closed");
}
$processor = ThreadProcessor::getInstance();
$processor->receiveRequest($_POST['data']);
?>