Completely rewrite invitations

This commit is contained in:
Dmitriy Simushev 2013-05-22 14:34:58 +00:00
parent a2db90289a
commit 3994b924de
37 changed files with 659 additions and 346 deletions

View File

@ -334,14 +334,6 @@
</target>
<!-- Compile and concatenate JavaScript files related to invite application -->
<target name="invite_app_js" depends="default_app_js" description="Build JavaScript files related to invite application">
<antcall target="app_js">
<param name="app_name" value="invite" />
</antcall>
<echo>Invite JavaScript application built.</echo>
</target>
<!-- Compile and concatenate JavaScript files related to users application -->
<target name="users_app_js" depends="default_app_js" description="Build JavaScript files related to users application">
@ -430,7 +422,7 @@
<!-- Build all project -->
<target name="all" depends="chat_app_js,invite_app_js,thread_log_app_js,users_app_js,styles_all" description="Build everything">
<target name="all" depends="chat_app_js,thread_log_app_js,users_app_js,styles_all" description="Build everything">
<echo>Mibew Messenger built.</echo>
</target>

View File

@ -47,6 +47,24 @@ if (get_remote_level($_SERVER['HTTP_USER_AGENT']) == 'old') {
exit;
}
if (verifyparam("act", "/^(invitation)$/", "default") == 'invitation') {
// Check if user invited to chat
$invitation_state = invitation_state($_SESSION['visitorid']);
if ($invitation_state['invited'] && $invitation_state['threadid']) {
$thread = Thread::load($invitation_state['threadid']);
// Prepare page
$page = setup_invitation_view($thread);
// Build js application options
$page['invitationOptions'] = json_encode($page['invitation']);
// Expand page
expand("styles/dialogs", getchatstyle(), "chat.tpl");
exit;
}
}
if( !isset($_GET['token']) || !isset($_GET['thread']) ) {
$thread = NULL;
@ -107,8 +125,7 @@ if( !isset($_GET['token']) || !isset($_GET['thread']) ) {
// Get invitation info
if (Settings::get('enabletracking')) {
$invitation_state = invitation_state($_SESSION['visitorid']);
$visitor_is_invited = $invitation_state['invited']
&& !$invitation_state['threadid'];
$visitor_is_invited = $invitation_state['invited'];
} else {
$visitor_is_invited = false;
}

View File

@ -986,17 +986,3 @@ table.awaiting .no-threads, table.awaiting .no-visitors {
float:left;
padding-left:10px;
}
/* invitation wait */
.ajaxWait {
z-index: -1;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url(images/ajax-loader.gif);
background-repeat: no-repeat;
background-position: center;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -42,6 +42,7 @@ $dbtables = array(
"dtmmodified" => "int NOT NULL DEFAULT 0",
"lrevision" => "int NOT NULL DEFAULT 0",
"istate" => "int NOT NULL DEFAULT 0",
"invitationstate" => "int NOT NULL DEFAULT 0",
"ltoken" => "int NOT NULL",
"remote" => "varchar(255)",
"referer" => "text",
@ -167,15 +168,12 @@ $dbtables = array(
"${mysqlprefix}chatsitevisitor" => array(
"visitorid" => "INT NOT NULL auto_increment PRIMARY KEY",
"userid" => "varchar(64) NOT NULL",
"username" => "varchar(255)",
"userid" => "varchar(255) NOT NULL",
"username" => "varchar(64)",
"firsttime" => "int NOT NULL DEFAULT 0",
"lasttime" => "int NOT NULL DEFAULT 0",
"entry" => "text NOT NULL",
"details" => "text NOT NULL",
"invited" => "tinyint(1) NOT NULL DEFAULT 0",
"invitationtime" => "int NOT NULL DEFAULT 0",
"invitedby" => "INT references ${mysqlprefix}chatoperator(operatorid) on delete set null",
"invitations" => "INT NOT NULL DEFAULT 0",
"chats" => "INT NOT NULL DEFAULT 0",
"threadid" => "INT references ${mysqlprefix}chatthread(threadid) on delete set null"
@ -233,7 +231,7 @@ $dbtables_indexes = array(
$memtables = array();
$dbtables_can_update = array(
"${mysqlprefix}chatthread" => array("agentId", "userTyping", "agentTyping", "messageCount", "nextagent", "shownmessageid", "userid", "userAgent", "groupid", "dtmchatstarted"),
"${mysqlprefix}chatthread" => array("agentId", "userTyping", "agentTyping", "messageCount", "nextagent", "shownmessageid", "userid", "userAgent", "groupid", "dtmchatstarted", "invitationstate"),
"${mysqlprefix}chatthreadstatistics" => array(),
"${mysqlprefix}requestbuffer" => array("requestid", "requestkey", "request"),
"${mysqlprefix}chatmessage" => array("agentId"),

View File

@ -128,6 +128,10 @@ if ($act == "silentcreateall") {
runsql("ALTER TABLE ${mysqlprefix}chatthread ADD userid varchar(255) DEFAULT \"\"", $link);
}
if (in_array("${mysqlprefix}chatthread.invitationstate", $absent_columns)) {
runsql("ALTER TABLE ${mysqlprefix}chatthread ADD invitationstate int NOT NULL DEFAULT 0 AFTER istate", $link);
}
if (in_array("${mysqlprefix}chatoperator.iperm", $absent_columns)) {
runsql("ALTER TABLE ${mysqlprefix}chatoperator ADD iperm int DEFAULT 65535", $link);
}

View File

@ -5,5 +5,5 @@
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
(function(a,d){var c=a.Application;c.addRegions({mainRegion:"#main-region"});c.addInitializer(function(b){a.PluginOptions=b.plugins||{};a.Objects.server=new a.Server(d.extend({interactionType:MibewAPIChatInteraction},b.server));a.Objects.Models.page=new a.Models.Page(b.page);switch(b.startFrom){case "chat":c.Chat.start(b.chatOptions);break;case "survey":c.Survey.start(b.surveyOptions);break;case "leaveMessage":c.LeaveMessage.start(b.leaveMessageOptions);break;default:throw Error("Dont know how to start!");
}});c.on("start",function(){a.Objects.server.runUpdater()})})(Mibew,_);
(function(b,d){var c=b.Application;c.addRegions({mainRegion:"#main-region"});c.addInitializer(function(a){b.PluginOptions=a.plugins||{};b.Objects.server=new b.Server(d.extend({interactionType:MibewAPIChatInteraction},a.server));b.Objects.Models.page=new b.Models.Page(a.page);switch(a.startFrom){case "chat":c.Chat.start(a.chatOptions);break;case "survey":c.Survey.start(a.surveyOptions);break;case "leaveMessage":c.LeaveMessage.start(a.leaveMessageOptions);break;case "invitation":c.Invitation.start(a.invitationOptions);
break;default:throw Error("Dont know how to start!");}});c.on("start",function(){b.Objects.server.runUpdater()})})(Mibew,_);

View File

@ -0,0 +1,8 @@
/*
Copyright 2005-2013 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License").
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
(function(a,b){a.Layouts.Invitation=b.Marionette.Layout.extend({template:Handlebars.templates.invitation_layout,regions:{messagesRegion:{selector:"#invitation-messages-region",regionType:a.Regions.Messages}}})})(Mibew,Backbone);

View File

@ -0,0 +1,10 @@
/*
Copyright 2005-2013 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License").
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
(function(a){var d=[],e=a.Application,b=e.module("Invitation",{startWithParent:!1});b.addInitializer(function(f){var c=a.Objects,b=a.Objects.Models;b.thread=new a.Models.Thread(f.thread);b.user=new a.Models.ChatUser(f.user);c.invitationLayout=new a.Layouts.Invitation;e.mainRegion.show(c.invitationLayout);c.Collections.messages=new a.Collections.Messages;c.invitationLayout.messagesRegion.show(new a.Views.MessagesCollection({collection:c.Collections.messages}));d.push(c.server.callFunctionsPeriodically(function(){var b=
a.Objects.Models.thread;return[{"function":"update",arguments:{"return":{},references:{},threadId:b.get("id"),token:b.get("token"),lastId:b.get("lastId"),typed:!1,user:!0}}]},function(){}))});b.on("start",function(){a.Objects.server.restartUpdater()});b.addFinalizer(function(){a.Objects.invitationLayout.close();for(var b=0;b<d.length;b++)a.Objects.server.stopCallFunctionsPeriodically(d[b]);a.Objects.Collections.messages.finalize();delete a.Objects.invitationLayout;delete a.Objects.Models.thread;delete a.Objects.Models.user;
delete a.Objects.Collections.messages})})(Mibew);

View File

@ -341,6 +341,13 @@ disableInput:function(){this.ui.message.attr("disabled","disabled")},clearInput:
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
(function(a,b){a.Layouts.Invitation=b.Marionette.Layout.extend({template:Handlebars.templates.invitation_layout,regions:{messagesRegion:{selector:"#invitation-messages-region",regionType:a.Regions.Messages}}})})(Mibew,Backbone);
/*
Copyright 2005-2013 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License").
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
(function(a,b){a.Layouts.LeaveMessage=b.Marionette.Layout.extend({template:Handlebars.templates.leave_message_layout,regions:{leaveMessageFormRegion:"#content-wrapper",descriptionRegion:"#description-region"},serializeData:function(){return{page:a.Objects.Models.page.toJSON()}}})})(Mibew,Backbone);
/*
Copyright 2005-2013 the original author or authors.
@ -368,6 +375,15 @@ delete a.Objects.Collections.messages;delete a.Objects.Collections.controls;dele
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
(function(a){var d=[],e=a.Application,b=e.module("Invitation",{startWithParent:!1});b.addInitializer(function(f){var c=a.Objects,b=a.Objects.Models;b.thread=new a.Models.Thread(f.thread);b.user=new a.Models.ChatUser(f.user);c.invitationLayout=new a.Layouts.Invitation;e.mainRegion.show(c.invitationLayout);c.Collections.messages=new a.Collections.Messages;c.invitationLayout.messagesRegion.show(new a.Views.MessagesCollection({collection:c.Collections.messages}));d.push(c.server.callFunctionsPeriodically(function(){var b=
a.Objects.Models.thread;return[{"function":"update",arguments:{"return":{},references:{},threadId:b.get("id"),token:b.get("token"),lastId:b.get("lastId"),typed:!1,user:!0}}]},function(){}))});b.on("start",function(){a.Objects.server.restartUpdater()});b.addFinalizer(function(){a.Objects.invitationLayout.close();for(var b=0;b<d.length;b++)a.Objects.server.stopCallFunctionsPeriodically(d[b]);a.Objects.Collections.messages.finalize();delete a.Objects.invitationLayout;delete a.Objects.Models.thread;delete a.Objects.Models.user;
delete a.Objects.Collections.messages})})(Mibew);
/*
Copyright 2005-2013 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License").
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
(function(a){var e=a.Application,f=e.module("LeaveMessage",{startWithParent:!1});f.addInitializer(function(d){var b=a.Objects,c=a.Objects.Models;d.page&&c.page.set(d.page);b.leaveMessageLayout=new a.Layouts.LeaveMessage;e.mainRegion.show(b.leaveMessageLayout);c.leaveMessageForm=new a.Models.LeaveMessageForm(d.leaveMessageForm);b.leaveMessageLayout.leaveMessageFormRegion.show(new a.Views.LeaveMessageForm({model:c.leaveMessageForm}));b.leaveMessageLayout.descriptionRegion.show(new a.Views.LeaveMessageDescription);
c.leaveMessageForm.on("submit:complete",function(){b.leaveMessageLayout.leaveMessageFormRegion.close();b.leaveMessageLayout.descriptionRegion.close();b.leaveMessageLayout.descriptionRegion.show(new a.Views.LeaveMessageSentDescription)})});f.addFinalizer(function(){a.Objects.leaveMessageLayout.close();delete a.Objects.Models.leaveMessageForm})})(Mibew);
/*
@ -383,5 +399,5 @@ c.leaveMessageForm.on("submit:complete",function(){b.leaveMessageLayout.leaveMes
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
(function(a,d){var c=a.Application;c.addRegions({mainRegion:"#main-region"});c.addInitializer(function(b){a.PluginOptions=b.plugins||{};a.Objects.server=new a.Server(d.extend({interactionType:MibewAPIChatInteraction},b.server));a.Objects.Models.page=new a.Models.Page(b.page);switch(b.startFrom){case "chat":c.Chat.start(b.chatOptions);break;case "survey":c.Survey.start(b.surveyOptions);break;case "leaveMessage":c.LeaveMessage.start(b.leaveMessageOptions);break;default:throw Error("Dont know how to start!");
}});c.on("start",function(){a.Objects.server.runUpdater()})})(Mibew,_);
(function(b,d){var c=b.Application;c.addRegions({mainRegion:"#main-region"});c.addInitializer(function(a){b.PluginOptions=a.plugins||{};b.Objects.server=new b.Server(d.extend({interactionType:MibewAPIChatInteraction},a.server));b.Objects.Models.page=new b.Models.Page(a.page);switch(a.startFrom){case "chat":c.Chat.start(a.chatOptions);break;case "survey":c.Survey.start(a.surveyOptions);break;case "leaveMessage":c.LeaveMessage.start(a.leaveMessageOptions);break;case "invitation":c.Invitation.start(a.invitationOptions);
break;default:throw Error("Dont know how to start!");}});c.on("start",function(){b.Objects.server.runUpdater()})})(Mibew,_);

View File

@ -5,4 +5,4 @@
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
(function(a){a.Models.Thread=a.Models.Base.extend({defaults:{id:0,token:0,lastId:0,state:null},STATE_QUEUE:0,STATE_WAITING:1,STATE_CHATTING:2,STATE_CLOSED:3,STATE_LOADING:4,STATE_LEFT:5})})(Mibew);
(function(a){a.Models.Thread=a.Models.Base.extend({defaults:{id:0,token:0,lastId:0,state:null},STATE_QUEUE:0,STATE_WAITING:1,STATE_CHATTING:2,STATE_CLOSED:3,STATE_LOADING:4,STATE_LEFT:5,STATE_INVITED:6})})(Mibew);

View File

@ -87,7 +87,7 @@ b.Utils.playSound=function(a){c("body").append('<audio autoplay style="display:
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
(function(a){a.Models.Thread=a.Models.Base.extend({defaults:{id:0,token:0,lastId:0,state:null},STATE_QUEUE:0,STATE_WAITING:1,STATE_CHATTING:2,STATE_CLOSED:3,STATE_LOADING:4,STATE_LEFT:5})})(Mibew);
(function(a){a.Models.Thread=a.Models.Base.extend({defaults:{id:0,token:0,lastId:0,state:null},STATE_QUEUE:0,STATE_WAITING:1,STATE_CHATTING:2,STATE_CLOSED:3,STATE_LOADING:4,STATE_LEFT:5,STATE_INVITED:6})})(Mibew);
/*
Copyright 2005-2013 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License").

View File

@ -1,9 +0,0 @@
/*
Copyright 2005-2013 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License").
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
(function(c,a,e){a=new a.Marionette.Application;a.addInitializer(function(a){var d=new c.Server(e.extend({interactionType:MibewAPIInviteInteraction},a.server));d.callFunctionsPeriodically(function(){return[{"function":"invitationState",arguments:{"return":{invited:"invited",threadId:"threadId"},references:{},visitorId:a.visitorId}}]},function(b){0==b.errorCode&&(b.invited||window.close(),b.threadId&&(window.name="ImCenter"+b.threadId,window.location=a.chatLink+"?thread="+b.threadId))});d.runUpdater();
c.Objects.server=d});c.Application=a})(Mibew,Backbone,_);

View File

@ -1,8 +0,0 @@
/*
Copyright 2005-2013 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License").
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
MibewAPIInviteInteraction=function(){this.obligatoryArguments={"*":{"return":{},references:{},visitorId:null},result:{errorCode:0}};this.reservedFunctionNames=["result"]};MibewAPIInviteInteraction.prototype=new MibewAPIInteraction;

View File

@ -1,15 +0,0 @@
/*
Copyright 2005-2013 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License").
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
MibewAPIInviteInteraction=function(){this.obligatoryArguments={"*":{"return":{},references:{},visitorId:null},result:{errorCode:0}};this.reservedFunctionNames=["result"]};MibewAPIInviteInteraction.prototype=new MibewAPIInteraction;
/*
Copyright 2005-2013 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License").
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
(function(c,a,e){a=new a.Marionette.Application;a.addInitializer(function(a){var d=new c.Server(e.extend({interactionType:MibewAPIInviteInteraction},a.server));d.callFunctionsPeriodically(function(){return[{"function":"invitationState",arguments:{"return":{invited:"invited",threadId:"threadId"},references:{},visitorId:a.visitorId}}]},function(b){0==b.errorCode&&(b.invited||window.close(),b.threadId&&(window.name="ImCenter"+b.threadId,window.location=a.chatLink+"?thread="+b.threadId))});d.runUpdater();
c.Objects.server=d});c.Application=a})(Mibew,Backbone,_);

View File

@ -12,6 +12,6 @@ return b.join("&")};a.Widget.prototype.sendToServer=function(b){for(var c in b)i
this.loadScript(f));for(var h in b)b.hasOwnProperty(h)&&(h in this.handlersDependences||(this.handlersDependences[h]=b[h]));for(c=0;c<d.length;c++){var g=d[c];if(this.canRunHandler(g))a.APIFunctions[g](e);else g in this.handlers||(this.handlers[g]=function(){a.APIFunctions[g](e)})}this.cleanUpAfterRequest();window.setTimeout(function(){j.makeRequest()},this.requestTimeout)};a.Widget.prototype.cleanUpAfterRequest=function(){document.getElementsByTagName("head")[0].removeChild(document.getElementById("responseScript"))};
a.Widget.prototype.loadScript=function(b){var c=this,a=this.doLoadScript(this.requestedScripts[b].url,b);a.onload=function(){c.scriptReady(b)};a.onreadystatechange=function(){("complete"==this.readyState||"loaded"==this.readyState)&&c.scriptReady(b)}};a.Widget.prototype.doLoadScript=function(b,c){var a=document.createElement("script");a.setAttribute("type","text/javascript");a.setAttribute("src",b);a.setAttribute("id",c);document.getElementsByTagName("head")[0].appendChild(a);return a};a.Widget.prototype.scriptReady=
function(b){this.requestedScripts[b].status="ready";for(var a in this.handlers)this.handlers.hasOwnProperty(a)&&this.canRunHandler(a)&&(this.handlers[a](),delete this.handlers[a])};a.Widget.prototype.canRunHandler=function(b){b=this.handlersDependences[b];for(var a=0;a<b.length;a++)if("ready"!=this.requestedScripts[b[a]].status)return!1;return!0};a.Widget.init=function(b){a.Objects.widget=new a.Widget(b);a.Objects.widget.makeRequest()};a.Utils={};a.Utils.createCookie=function(b,a){var d=/([^\.]+\.[^\.]+)$/.exec(document.location.hostname)[1];
document.cookie=""+b+"="+a+"; path=/; "+(d?"domain="+d+";":"")};a.Utils.readCookie=function(b){var a=document.cookie.split("; ");b+="=";for(var d=!1,e=0;e<a.length;e++)if(-1!=a[e].indexOf(b)){d=a[e].substr(b.length);break}return d};a.APIFunctions={};a.APIFunctions.updateUserId=function(b){a.Utils.createCookie(a.Objects.widget.visitorCookieName,b.user.id)};a.APIFunctions.inviteOnResponse=function(b){var a=b.invitation.message,d=b.invitation.operator,e=b.invitation.avatar;b='<div id="mibewinvitationpopup"><div id="mibewinvitationclose"><a href="javascript:void(0);" onclick="Mibew.Invitation.reject();">&times;</a></div>';
d&&(b+='<h1 onclick="Mibew.Invitation.accept();">'+d+"</h1>");e&&(b+='<img id="mibewinvitationavatar" src="'+e+'" title="'+d+'" alt="'+d+'" onclick="Mibew.Invitation.accept();" />');b=b+('<p onclick="Mibew.Invitation.accept();">'+a+"</p>")+'<div style="clear: both;"></div></div>';if(a=document.getElementById("mibewinvitation"))a.innerHTML=b};a.Invitation={};a.Invitation.hide=function(){var a=document.getElementById("mibewinvitationpopup");a&&(a.style.display="none")};a.Invitation.accept=function(){document.getElementById("mibewAgentButton")&&
(document.getElementById("mibewAgentButton").onclick(),a.Invitation.hide())};a.Invitation.reject=function(){a.Objects.widget.sendToServer({invitation_rejected:1});a.Invitation.hide()}})(Mibew);
document.cookie=""+b+"="+a+"; path=/; "+(d?"domain="+d+";":"")};a.Utils.readCookie=function(b){var a=document.cookie.split("; ");b+="=";for(var d=!1,e=0;e<a.length;e++)if(-1!=a[e].indexOf(b)){d=a[e].substr(b.length);break}return d};a.APIFunctions={};a.APIFunctions.updateUserId=function(b){a.Utils.createCookie(a.Objects.widget.visitorCookieName,b.user.id)};a.APIFunctions.inviteOnResponse=function(b){var a=b.invitation.operator,d=b.invitation.avatar,e=b.invitation.url;b='<div id="mibewinvitationpopup" style="display: none;"><div id="mibewinvitationclose"><a href="javascript:void(0);" onclick="Mibew.Invitation.reject();">&times;</a></div>';
a&&(b+='<h1 onclick="Mibew.Invitation.accept();">'+a+"</h1>");d&&(b+='<img id="mibewinvitationavatar" src="'+d+'" title="'+a+'" alt="'+a+'" onclick="Mibew.Invitation.accept();" />');e&&(b+='<iframe id="mibewinvitationframe" src="'+e+'" onload="Mibew.Invitation.show();"></iframe>');b+='<div style="clear: both;"></div></div>';if(a=document.getElementById("mibewinvitation"))a.innerHTML=b};a.Invitation={};a.Invitation.hide=function(){var a=document.getElementById("mibewinvitationpopup");a&&a.parentNode.removeChild(a)};
a.Invitation.show=function(){var a=document.getElementById("mibewinvitationpopup");a&&(a.style.display="block")};a.Invitation.accept=function(){document.getElementById("mibewAgentButton")&&(document.getElementById("mibewAgentButton").onclick(),a.Invitation.hide())};a.Invitation.reject=function(){a.Objects.widget.sendToServer({invitation_rejected:1});a.Invitation.hide()}})(Mibew);

View File

@ -40,6 +40,9 @@
case 'leaveMessage':
app.LeaveMessage.start(options.leaveMessageOptions);
break;
case 'invitation':
app.Invitation.start(options.invitationOptions);
break;
default:
throw new Error('Dont know how to start!');
break;

View File

@ -0,0 +1,36 @@
/**
* @preserve Copyright 2005-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*/
(function(Mibew, Backbone){
/**
* Represents invitation layout
*/
Mibew.Layouts.Invitation = Backbone.Marionette.Layout.extend(
/** @lends Mibew.Layouts.Invitation.prototype */
{
/**
* Template function
* @type Function
*/
template: Handlebars.templates.invitation_layout,
/**
* Regions list
* @type Object
*/
regions: {
messagesRegion: {
selector: '#invitation-messages-region',
regionType: Mibew.Regions.Messages
}
}
}
);
})(Mibew, Backbone);

View File

@ -0,0 +1,114 @@
/**
* @preserve Copyright 2005-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*/
(function(Mibew){
/**
* Contains ids of periodically called functions
* @type Array
*/
var periodicallyCalled = [];
// Create shortcut for Application object
var app = Mibew.Application;
// Create Invite module
var invitation = app.module('Invitation', {startWithParent: false});
// Add module initializer
invitation.addInitializer(function(options) {
// Create some shortcuts
var objs = Mibew.Objects;
var models = Mibew.Objects.Models;
// Initialize Thread and User
models.thread = new Mibew.Models.Thread(options.thread);
models.user = new Mibew.Models.ChatUser(options.user);
// Create instance of the Invitation layout
objs.invitationLayout = new Mibew.Layouts.Invitation();
// Show layout at page
app.mainRegion.show(objs.invitationLayout);
// Initialize Messages area
// Create messages collection and store it
objs.Collections.messages = new Mibew.Collections.Messages();
// Display messages
objs.invitationLayout.messagesRegion.show(new Mibew.Views.MessagesCollection({
collection: objs.Collections.messages
}));
// Periodically call update function at the server side
periodicallyCalled.push(
objs.server.callFunctionsPeriodically(
function() {
// Get thread object
var thread = Mibew.Objects.Models.thread;
// Build functions list
return [
{
"function": "update",
"arguments": {
"return": {},
"references": {},
"threadId": thread.get('id'),
"token": thread.get('token'),
"lastId": thread.get('lastId'),
"typed": false,
"user": true
}
}
]
},
function() {}
)
);
});
invitation.on('start', function() {
// Restart server updater because module just started and there are no
// reasons to wait refresh time to get results of previous request
Mibew.Objects.server.restartUpdater();
})
// Add module finalizer
invitation.addFinalizer(function() {
// Close layout
Mibew.Objects.invitationLayout.close();
// Stop call functions periodically
for (var i = 0; i < periodicallyCalled.length; i++) {
Mibew.Objects.server.stopCallFunctionsPeriodically(
periodicallyCalled[i]
);
}
// Run collections finalizers
// Finalize messages collection
Mibew.Objects.Collections.messages.finalize();
// Clean up memory
// Delete layout
delete Mibew.Objects.invitationLayout;
// Delete models
delete Mibew.Objects.Models.thread;
delete Mibew.Objects.Models.user;
// Delete collections
delete Mibew.Objects.Collections.messages;
});
})(Mibew);

View File

@ -70,7 +70,11 @@
/**
* User left message without starting a conversation
*/
STATE_LEFT: 5
STATE_LEFT: 5,
/**
* Visitor was invited to chat by operator
*/
STATE_INVITED: 6
/** End of thread state constants */
}

View File

@ -1,67 +0,0 @@
/**
* @preserve Copyright 2005-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*/
(function(Mibew, Backbone, _){
// Create application instance
var App = new Backbone.Marionette.Application();
// Initialize application
App.addInitializer(function(options){
// Initialize Server
var server = new Mibew.Server(_.extend(
{
'interactionType': MibewAPIInviteInteraction
},
options.server
));
// Periodically call update function at the server side
server.callFunctionsPeriodically(
function() {
// Build functions list
return [
{
"function": "invitationState",
"arguments": {
"return": {
'invited': 'invited',
'threadId': 'threadId'
},
"references": {},
"visitorId": options.visitorId
}
}
];
},
function(args) {
if (args.errorCode == 0) {
if (!args.invited) {
// Visitor not invited any more.
// Invitation vindow should be closed.
window.close();
}
if (args.threadId) {
// Invitation accepted.
// Redirect agent to chat page
window.name = 'ImCenter' + args.threadId;
window.location= options.chatLink
+ '?thread=' + args.threadId;
}
}
}
);
server.runUpdater();
Mibew.Objects.server = server;
});
Mibew.Application = App;
})(Mibew, Backbone, _);

View File

@ -1,30 +0,0 @@
/**
* @preserve Copyright 2005-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*/
/**
* Represents Invitation waiting Window to core interaction type
*
* @constructor
*/
MibewAPIInviteInteraction = function() {
this.obligatoryArguments = {
'*': {
'return': {},
'references': {},
'visitorId': null
},
'result': {
'errorCode': 0
}
};
this.reservedFunctionNames = [
'result'
];
}
MibewAPIInviteInteraction.prototype = new MibewAPIInteraction();

View File

@ -380,28 +380,36 @@ var Mibew = {};
* @param {Object} response Data passed from server
*/
Mibew.APIFunctions.inviteOnResponse = function(response) {
var message = response.invitation.message;
var operator = response.invitation.operator;
var avatar = response.invitation.avatar;
var url = response.invitation.url;
var popuptext = '<div id="mibewinvitationpopup">';
var popuptext = '<div id="mibewinvitationpopup" style="display: none;">';
popuptext += '<div id="mibewinvitationclose">'
+ '<a href="javascript:void(0);" onclick="Mibew.Invitation.reject();">'
+ '&times;</a></div>';
// Add operator name
if (operator) {
popuptext += '<h1 onclick="Mibew.Invitation.accept();">'
+ operator
+ '</h1>';
}
// Add operator avatar
if (avatar) {
popuptext += '<img id="mibewinvitationavatar" src="' + avatar
+ '" title="' + operator
+ '" alt="' + operator
+ '" onclick="Mibew.Invitation.accept();" />';
}
popuptext += '<p onclick="Mibew.Invitation.accept();">'
+ message
+ '</p>';
// Translate message from the thread related with invitation into iframe
if (url) {
popuptext += '<iframe id="mibewinvitationframe" src="' + url
+ '" onload="Mibew.Invitation.show();"></iframe>';
}
popuptext += '<div style="clear: both;"></div></div>';
var invitationdiv = document.getElementById("mibewinvitation");
@ -416,12 +424,22 @@ var Mibew = {};
Mibew.Invitation = {};
/**
* Hide invitation popup
* Hide invitation popup and remove it from DOM
*/
Mibew.Invitation.hide = function() {
var invitationPopup = document.getElementById('mibewinvitationpopup');
if (invitationPopup) {
invitationPopup.style.display = 'none';
invitationPopup.parentNode.removeChild(invitationPopup);
}
}
/**
* Display invitation popup
*/
Mibew.Invitation.show = function() {
var invitationPopup = document.getElementById('mibewinvitationpopup');
if (invitationPopup) {
invitationPopup.style.display = 'block';
}
}

View File

@ -642,8 +642,30 @@ function chat_start_for_user($group_id, $visitor_id, $visitor_name, $referrer, $
die("number of connections from your IP is exceeded, try again later");
}
// Create thread
$thread = Thread::create();
// Check if visitor was invited to chat
$is_invited = false;
if (Settings::get('enabletracking')) {
$invitation_state = invitation_state($_SESSION['visitorid']);
if ($invitation_state['invited']) {
$is_invited = true;
}
}
// Get thread object
if ($is_invited) {
// Get thread from invitation
$thread = invitation_accept($_SESSION['visitorid']);
if (! $thread) {
die("Cannot start thread");
}
$thread->state = Thread::STATE_CHATTING;
} else {
// Create thread
$thread = Thread::create();
$thread->state = Thread::STATE_LOADING;
}
// Update thread fields
$thread->groupId = $group_id;
$thread->userName = $visitor_name;
$thread->remote = $remote_host;
@ -651,36 +673,31 @@ function chat_start_for_user($group_id, $visitor_id, $visitor_name, $referrer, $
$thread->locale = $current_locale;
$thread->userId = $visitor_id;
$thread->userAgent = $user_browser;
$thread->state = Thread::STATE_LOADING;
$thread->save();
$_SESSION['threadid'] = $thread->id;
// Check if invitation accept
if (Settings::get('enabletracking')) {
$operator = invitation_accept($_SESSION['visitorid'], $thread->id);
if ($operator) {
$operator = operator_by_id($operator);
$operator_name = get_operator_name($operator);
$thread->postMessage(
Thread::KIND_FOR_AGENT,
getstring2(
'chat.visitor.invitation.accepted',
array($operator_name)
)
);
}
}
// Send some messages
if ($referrer) {
// Send several messages
if ($is_invited) {
$operator = operator_by_id($thread->agentId);
$operator_name = get_operator_name($operator);
$thread->postMessage(
Thread::KIND_FOR_AGENT,
getstring2('chat.came.from',array($referrer))
getstring2(
'chat.visitor.invitation.accepted',
array($operator_name)
)
);
}
} else {
if ($referrer) {
$thread->postMessage(
Thread::KIND_FOR_AGENT,
getstring2('chat.came.from',array($referrer))
);
}
$thread->postMessage(Thread::KIND_INFO, getstring('chat.wait'));
$thread->postMessage(Thread::KIND_INFO, getstring('chat.wait'));
}
// TODO: May be move sending this message somewhere else?
if ($info) {

View File

@ -61,6 +61,31 @@ Class Thread {
* User left message without starting a conversation
*/
const STATE_LEFT = 5;
/**
* Visitor was invited to chat by operator
*/
const STATE_INVITED = 6;
/**
* Visitor was not invited to chat
*/
const INVITATION_NOT_INVITED = 0;
/**
* Operator invited visitor and wait for reaction.
*/
const INVITATION_WAIT = 1;
/**
* Invitation was accepted by visitor
*/
const INVITATION_ACCEPTED = 2;
/**
* Invitation was rejected by visitor
*/
const INVITATION_REJECTED = 3;
/**
* Invitation was ignored by visitor. Invitation was automatically closed.
*/
const INVITATION_IGNORED = 4;
/**
* Message sent by user
@ -106,6 +131,8 @@ Class Thread {
* - 'id': id of the thread
* - 'lastRevision': last revision number
* - 'state': state of the thread. See Thread::STATE_*
* - 'invitationState': state of invitation. See INVITATION_* constants,
* defined in libs/invitation.php
* - 'lastToken': last chat token
* - 'nextAgent': id of the next agent(agent that change current agent in the chat)
* - 'groupId': id of the group related to the thread
@ -138,6 +165,7 @@ Class Thread {
'lastRevision' => 'lrevision',
'state' => 'istate',
'invitationState' => 'invitationstate',
'lastToken' => 'ltoken',
'nextAgent' => 'nextagent',

View File

@ -183,6 +183,7 @@ class UsersProcessor extends ClientSideProcessor {
" from {chatthread} t left outer join {chatgroup} g on " .
" t.groupid = g.groupid " .
" where t.lrevision > :since " .
" AND t.istate <> " . Thread::STATE_INVITED .
($since == 0
// Select only active threads at first time when lrevision = 0
? " AND t.istate <> " . Thread::STATE_CLOSED .
@ -377,19 +378,35 @@ class UsersProcessor extends ClientSideProcessor {
// Load visitors list
$db = Database::getInstance();
// Load visitors
$query = "SELECT visitorid, userid, username, firsttime, " .
"lasttime,entry, details, invited, invitationtime, " .
"invitedby, invitations, chats " .
"FROM {chatsitevisitor} " .
"WHERE threadid IS NULL " .
"ORDER BY invited, lasttime DESC, invitations";
$query = "SELECT v.visitorid, " .
"v.userid, " .
"v.username, " .
"v.firsttime, " .
"v.lasttime, " .
"v.entry, " .
"v.details, " .
"t.invitationstate, " .
"t.dtmcreated AS invitationtime, " .
"t.agentId AS invitedby, " .
"v.invitations, " .
"v.chats " .
"FROM {chatsitevisitor} v " .
"LEFT OUTER JOIN {chatthread} t " .
"ON t.threadid = v.threadid " .
"WHERE v.threadid IS NULL " .
"OR (t.istate = :state_invited " .
"AND t.invitationstate = :invitation_wait)" .
"ORDER BY t.invitationstate, v.lasttime DESC, v.invitations";
$query .= (Settings::get('visitors_limit') == '0')
? ""
: " LIMIT " . Settings::get('visitors_limit');
$rows = $db->query(
$query,
NULL,
array(
':state_invited' => Thread::STATE_INVITED,
':invitation_wait' => Thread::INVITATION_WAIT
),
array('return_rows' => Database::RETURN_ALL_ROWS)
);
@ -410,6 +427,7 @@ class UsersProcessor extends ClientSideProcessor {
}
// Get invitation info
$row['invited'] = ($row['invitationstate'] == Thread::INVITATION_WAIT);
if ($row['invited']) {
$agent_name = get_operator_name(
operator_by_id($row['invitedby'])

View File

@ -15,71 +15,193 @@
* limitations under the License.
*/
function invitation_state($visitorid)
{
/**
* Check invitation state for specified visitior
*
* @param int $visitor_id ID of the visitor to check
* @return array Array of invitation info. It contains following items:
* - 'invited': boolean, indicates if the visitor was invited by an operator;
* - 'threadid': int, ID of the thread, related with visitor or boolean false
* if visitor with specfied ID does not exist.
*/
function invitation_state($visitor_id) {
$db = Database::getInstance();
$result = $db->query(
"select invited, threadid from {chatsitevisitor} where visitorid = ?",
array($visitorid),
$db_result = $db->query(
"SELECT t.threadid, t.invitationstate, t.istate " .
"FROM {chatsitevisitor} v, {chatthread} t " .
"WHERE visitorid = ? " .
"AND t.threadid = v.threadid",
array($visitor_id),
array('return_rows' => Database::RETURN_ONE_ROW)
);
if (!$result) {
$result['invited'] = 0;
$result['threadid'] = 0;
}
return $result;
}
function invitation_invite($visitorid, $operatorid)
{
if (!invitation_check($visitorid)) {
$db = Database::getInstance();
$db->query(
"update {chatsitevisitor} set invited = 1, invitedby = :operatorid, " .
"invitationtime = :now, invitations = invitations + 1 where visitorid = :visitorid",
array(
':operatorid' => $operatorid,
':visitorid' => $visitorid,
':now' => time()
)
);
return invitation_check($visitorid);
$ret = array();
if (!$db_result) {
$ret['invited'] = false;
$ret['threadid'] = false;
} else {
return FALSE;
$ret['invited'] = ($db_result['istate'] == Thread::STATE_INVITED)
&& ($db_result['invitationstate'] == Thread::INVITATION_WAIT);
$ret['threadid'] = $db_result['threadid'];
}
return $ret;
}
function invitation_check($visitorid)
{
$db = Database::getInstance();
$result = $db->query(
"select invitedby from {chatsitevisitor} where invited and visitorid = ? " .
" and lasttime < invitationtime and threadid is null",
array($visitorid),
array('return_rows' => Database::RETURN_ONE_ROW)
);
return ($result && isset($result['invitedby']) && $result['invitedby']) ? $result['invitedby'] : FALSE;
}
/**
* Invite visitor by operator
*
* @global string $current_locale Current locale code
* @param int $visitor_id ID of the visitor, who must be invited.
* @param array $operator Info for operator who invite the visitor
* @return Thread|boolean Thread object related with invitation or boolean
* false on failure
*/
function invitation_invite($visitor_id, $operator) {
global $current_locale;
// The visitor already invited
if (invitation_check($visitor_id)) {
return false;
}
// Get visitor info
$visitor = track_get_visitor_by_id($visitor_id);
// Get last page visited by the visitor
$visitor_path = track_get_path($visitor);
ksort($visitor_path);
$last_visited_page = array_pop($visitor_path);
// Get visitor details
$visitor_details = track_retrieve_details($visitor);
// Get some operator's info
$operator_name = get_operator_name($operator);
// Create thread for invitation
$thread = Thread::create();
if (! $thread) {
// Something went wrong
return false;
}
// Populate thread and save it
$thread->agentId = $operator['operatorid'];
$thread->agentName = $operator_name;
$thread->userName = $visitor['username'];
$thread->remote = $visitor_details['remote_host'];
$thread->referer = $last_visited_page;
// User's locale is unknown, set operator locale to the thread
$thread->locale = $current_locale;
$thread->userId = $visitor['userid'];
$thread->userAgent = $visitor_details['user_agent'];
$thread->state = Thread::STATE_INVITED;
$thread->invitationState = Thread::INVITATION_WAIT;
$thread->save();
function invitation_accept($visitorid, $threadid)
{
$db = Database::getInstance();
$db->query(
"update {chatsitevisitor} set threadid = ?, chats = chats + 1 where visitorid = ?",
array($threadid, $visitorid)
"UPDATE {chatsitevisitor} set " .
"invitations = invitations + 1, " .
"threadid = :thread_id " .
"WHERE visitorid = :visitor_id",
array(
':thread_id' => $thread->id,
':visitor_id' => $visitor_id
)
);
// Send some messages
$thread->postMessage(
Thread::KIND_FOR_AGENT,
getlocal2(
'chat.visitor.invitation.sent',
array($operator_name, $last_visited_page)
)
);
$thread->postMessage(
Thread::KIND_AGENT,
getlocal("invitation.message"),
$operator_name
);
return $thread;
}
/**
* Check if visitor already invited
*
* @param int $visitor_id ID of the visitor to check
* @return int|boolean ID of the operator who invite the visitor or boolean
* false if the visitor was not invited.
*/
function invitation_check($visitor_id) {
$db = Database::getInstance();
$result = $db->query(
"select invitedby from {chatsitevisitor} where visitorid = ?",
array($visitorid),
"SELECT t.agentId AS invitedby " .
"FROM {chatsitevisitor} v, {chatthread} t " .
"WHERE v.threadid = t.threadid " .
"AND v.visitorid = :visitor_id " .
// Check only for new invitations. Refactor this place later
"AND v.lasttime < t.dtmcreated " .
"AND t.istate = :state_invited " .
"AND t.invitationstate = :invitation_wait ",
array(
':visitor_id' => $visitor_id,
':state_invited' => Thread::STATE_INVITED,
':invitation_wait' => Thread::INVITATION_WAIT
),
array('return_rows' => Database::RETURN_ONE_ROW)
);
return empty($result['invitedby']) ? false : $result['invitedby'];
}
/**
* Invitation was accepted by visitor
*
* @param int $visitor_id ID of the visitor who accept invitation
* @return Thread|boolean Thread object or boolean false on failure
*/
function invitation_accept($visitor_id) {
// Check if visitor was invited
$invitation_state = invitation_state($visitor_id);
if (! $invitation_state['invited']) {
// Visitor was not invited
return false;
}
// Get thread related with the visitor
$db = Database::getInstance();
$result = $db->query(
"SELECT threadid FROM {chatsitevisitor} WHERE visitorid = :visitor_id",
array(':visitor_id' => $visitor_id),
array('return_rows' => Database::RETURN_ONE_ROW)
);
if ($result && isset($result['invitedby']) && $result['invitedby']) {
return $result['invitedby'];
} else {
return FALSE;
if (empty($result['threadid'])) {
// Something went wrong. There is no thread related with the visitor.
return false;
}
$thread = Thread::load($result['threadid']);
if (! $thread) {
// Something went wrong. Cannot load thread.
return false;
}
// Update thread info
$thread->state = Thread::STATE_LOADING;
$thread->invitationState = Thread::INVITATION_ACCEPTED;
$thread->save();
// Update visitor info
$db->query(
"UPDATE {chatsitevisitor} SET chats = chats + 1 " .
"WHERE visitorid = :visitor_id",
array(':visitor_id' => $visitor_id)
);
return $thread;
}
/**
@ -88,12 +210,30 @@ function invitation_accept($visitorid, $threadid)
* @param int $visitor_id ID of the visitor
*/
function invitation_reject($visitor_id) {
$visitor = track_get_visitor_by_id($visitor_id);
// Send message to operator
$thread = Thread::load($visitor['threadid']);
if ($thread) {
$thread->postMessage(
Thread::KIND_FOR_AGENT,
getlocal('chat.visitor.invitation.rejected')
);
}
$db = Database::getInstance();
$db->query(
"UPDATE {chatsitevisitor} SET invited = 0, " .
"invitationtime = NULL, invitedby = NULL " .
"WHERE visitorid = ?",
array($visitor_id)
"UPDATE {chatsitevisitor} v, {chatthread} t SET " .
"v.threadid = NULL, " .
"t.invitationstate = :invitation_rejected, " .
"t.istate = :state_closed " .
"WHERE t.threadid = v.threadid " .
"AND visitorid = :visitor_id",
array(
':invitation_rejected' => Thread::INVITATION_REJECTED,
':state_closed' => Thread::STATE_CLOSED,
':visitor_id' => $visitor_id
)
);
}
@ -105,14 +245,56 @@ function invitation_close_old() {
// Remove old invitations
$db->query(
"UPDATE {chatsitevisitor} SET invited = 0, " .
"invitationtime = NULL, invitedby = NULL".
" WHERE threadid IS NULL AND (:now - invitationtime) > :lifetime",
"UPDATE {chatsitevisitor} v, {chatthread} t SET " .
"t.invitationstate = :invitation_ignored, " .
"t.istate = :state_closed, " .
"v.threadid = NULL " .
"WHERE t.istate = :state_invited " .
"AND t.invitationstate = :invitation_wait " .
"AND (:now - t.dtmcreated) > :lifetime",
array(
':invitation_ignored' => Thread::INVITATION_IGNORED,
':invitation_wait' => Thread::INVITATION_WAIT,
':state_closed' => Thread::STATE_CLOSED,
':state_invited' => Thread::STATE_INVITED,
':lifetime' => Settings::get('invitation_lifetime'),
':now' => time()
)
);
}
/**
* Prepare data to dispaly invitation
*
* @param Thread $thread Thread object related with invitation
* @return array Array of invitation data
*/
function setup_invitation_view(Thread $thread) {
$data = array();
// Set refresh frequency
$data['frequency'] = Settings::get('updatefrequency_chat');
// Load JavaScript plugins and JavaScripts, CSS files required by them
$data = array_merge_recursive($data, get_plugins_data('client_chat_window'));
// Create some empty arrays
$data['invitation'] = array();
$data['invitation']['thread'] = array(
'id' => $thread->id,
'token' => $thread->lastToken
);
$data['invitation']['user'] = array(
'name' => htmlspecialchars(topage($thread->userName)),
'canChangeName' => false,
'isAgent' => false
);
$data['startFrom'] = 'invitation';
return $data;
}
?>

View File

@ -81,6 +81,8 @@ chat.thread.state_wait_for_another_agent=Waiting for operator
chat.visitor.email=E-Mail: {0}
chat.visitor.info=Info: {0}
chat.visitor.invitation.accepted=Visitor accepted invitation from operator {0}
chat.visitor.invitation.rejected=Visitor rejected invitation
chat.visitor.invitation.sent=Operator {0} invites visitor at {1} page
chat.wait=Thank you for contacting us. An operator will be with you shortly.
chat.window.chatting_with=You are chatting with:
chat.window.close_title=Close chat

View File

@ -81,6 +81,8 @@ chat.thread.state_wait_for_another_agent=
chat.visitor.email=E-Mail: {0}
chat.visitor.info=Î Ïîñåòèòåëå: {0}
chat.visitor.invitation.accepted=Ïîñåòèòåëü ïðèíÿë ïðèãëàøåíèå îò îïåðàòîðà {0}
chat.visitor.invitation.rejected=Ïîñåòèòåëü îòêëîíèë ïðèãëàøåíèå
chat.visitor.invitation.sent=Îïåðàòîð {0} ïðèãëàñèë ïîñåòèòåëÿ íà ñòðàíèöå {1}
chat.wait=Ïîæàëóéñòà, ïîäîæäèòå íåìíîãî, ê Âàì ïðèñîåäèíèòñÿ îïåðàòîð..
chat.window.chatting_with=Âû îáùàåòåñü ñ:
chat.window.close_title=Çàêðûòü äèàëîã

View File

@ -17,20 +17,23 @@
require_once('../libs/init.php');
require_once('../libs/invitation.php');
require_once('../libs/track.php');
require_once('../libs/operator.php');
require_once('../libs/classes/thread.php');
$operator = check_login();
$visitorid = verifyparam("visitor", "/^\d{1,8}$/");
if (!invitation_invite($visitorid, $operator['operatorid'])) {
$thread = invitation_invite($visitorid, $operator);
if (!$thread) {
die("Invitation failed!");
}
$page = array();
$page['visitor'] = $visitorid;
$page['frequency'] = Settings::get('updatefrequency_operator');
// Open chat window for operator
$redirect_to = $webimroot .
'/operator/agent.php?thread=' . $thread->id .
'&token=' . $thread->lastToken;
header('Location: ' . $redirect_to);
start_html_output();
require('../view/invite.php');
?>

View File

@ -459,4 +459,41 @@ ul li {
top: 200px;
left: 250px;
}
/* ----- */
/* ----- */
/* Invitations */
#invitation-messages-region {
height: 150px;
min-height: 150px;
width: 398px;
overflow-y: auto;
padding: 0px;
font: normal 10px Tahoma;
line-height: 1.8em;
color: #999;
background-color: #FFFFFF;
}
#invitation-messages-region .minf {
font-size: 1.1em;
color: #7BAA0F;
}
#invitation-messages-region .mhidden,
#invitation-messages-region .mevent {
font-size: 1.1em;
}
#invitation-messages-region .nagent {
font-size: 1.1em;
font-weight: bold;
color: #000;
}
#invitation-messages-region .nuser {
font-size: 1.1em;
font-weight: bold;
color: #DA251D;
}
#invitation-messages-region .magent,
#invitation-messages-region .muser {
font-size: 1.1em;
color: #000;
}
/* ----- */

View File

@ -0,0 +1 @@
<div id="invitation-messages-region"></div>

View File

@ -27,13 +27,13 @@ d;d={hash:{},data:c};return q((b=f.apply,b?b.call(a,a.message,"urlReplace, nl2br
a.helpers;c=c||{};var p,r="function",h=this.escapeExpression,l=f.helperMissing,k=this;a='<div id="message">\n';if((b=f["if"].call(d,(p=d.user,null==p||!1===p?p:p.canPost),{hash:{},inverse:k.noop,fn:k.program(1,function(){return'\n <div class="bgc"><div class="bgl"><div class="bgr">\n <textarea id="message-input" class="message" tabindex="0" rows="4" cols="10"></textarea>\n </div></div></div>\n'},c),data:c}))||0===b)a+=b;a+='\n</div>\n\n<div id="send">\n';if((b=f["if"].call(d,(p=d.user,
null==p||!1===p?p:p.canPost),{hash:{},inverse:k.noop,fn:k.program(3,function(a,c){var j,e,b;j='\n <div id="postmessage">\n <div id="predefined-wrapper">\n ';if((b=f["if"].call(a,(e=a.user,null==e||!1===e?e:e.isAgent),{hash:{},inverse:k.noop,fn:k.program(4,g,c),data:c}))||0===b)j+=b;b={hash:{},data:c};j=j+'\n </div>\n <a href="javascript:void(0)" id="send-message" title="'+(h((e=f.L10n,e?e.call(a,"chat.window.send_message",b):l.call(a,"L10n","chat.window.send_message",
b)))+'">');b={hash:{},data:c};return j+=h((e=f.L10n,e?e.call(a,"chat.window.send_message_short_and_shortcut",b):l.call(a,"L10n","chat.window.send_message_short_and_shortcut",b)))+"</a>\n </div>\n"},c),data:c}))||0===b)a+=b;return a+'\n</div>\n<div class="clear"></div>'});s.chat_status_base=r(function(a,d,f,b,c){this.compilerInfo=[3,">= 1.0.0-rc.4"];f=f||a.helpers;c=c||{};a=this.escapeExpression;(f=f.title)?f=f.call(d,{hash:{},data:c}):(f=d.title,f="function"===typeof f?f.apply(d):f);return a(f)});
s.chat_status_message=r(function(a,d,f,b,c){this.compilerInfo=[3,">= 1.0.0-rc.4"];f=f||a.helpers;c=c||{};a=this.escapeExpression;(f=f.message)?f=f.call(d,{hash:{},data:c}):(f=d.message,f="function"===typeof f?f.apply(d):f);return a(f)});s.chat_status_typing=r(function(a,d,f,b,c){this.compilerInfo=[3,">= 1.0.0-rc.4"];f=f||a.helpers;c=c||{};var g;a=f.helperMissing;b=this.escapeExpression;c={hash:{},data:c};return b((g=f.L10n,g?g.call(d,"typing.remote",c):a.call(d,"L10n","typing.remote",c)))});s.leave_message_description=
r(function(a,d,f,b,c){this.compilerInfo=[3,">= 1.0.0-rc.4"];f=f||a.helpers;c=c||{};var g,q=f.helperMissing,p=this.escapeExpression;b={hash:{},data:c};a='<div class="buttons">\n <a href="javascript:window.close();" title="'+(p((g=f.L10n,g?g.call(d,"leavemessage.close",b):q.call(d,"L10n","leavemessage.close",b)))+'">\n <img class="tpl-image iclosewin" src="'+p((g=(g=d.page,null==g||!1===g?g:g.webimRoot),"function"===typeof g?g.apply(d):g))+'/images/free.gif" alt="');b={hash:{},data:c};a+=
p((g=f.L10n,g?g.call(d,"leavemessage.close",b):q.call(d,"L10n","leavemessage.close",b)))+'" />\n </a>\n</div>\n<div class="messagetxt">';b={hash:{},data:c};return a+=p((g=f.L10n,g?g.call(d,"leavemessage.descr",b):q.call(d,"L10n","leavemessage.descr",b)))+"</div>"});s.leave_message_form=r(function(a,d,f,b,c){function g(e,a){var c,b;c='<input type="hidden" name="group" value="';(b=f.groupId)?b=b.call(e,{hash:{},data:a}):(b=e.groupId,b=typeof b===k?b.apply(e):b);return c+=m(b)+'"/>'}function q(e,
a){var c,b,j;c=""+('\n <option value="'+m((b=e.id,typeof b===k?b.apply(e):b))+'" ');if((j=f["if"].call(e,e.selected,{hash:{},inverse:n.noop,fn:n.program(6,p,a),data:a}))||0===j)c+=j;return c+=">"+m((b=e.name,typeof b===k?b.apply(e):b))+"</option>\n "}function p(){return'selected="selected"'}function r(e,a){var b;return(b=f["if"].call(e,e.selected,{hash:{},inverse:n.noop,fn:n.program(9,h,a),data:a}))||0===b?b:""}function h(e){var a;return m((a=e.description,
typeof a===k?a.apply(e):a))}this.compilerInfo=[3,">= 1.0.0-rc.4"];f=f||a.helpers;c=c||{};var l,k="function",m=this.escapeExpression,n=this,j=f.helperMissing;a=""+('<form name="leaveMessageForm" method="post" action="">\n <input type="hidden" name="style" value="'+m((l=(l=d.page,null==l||!1===l?l:l.style),typeof l===k?l.apply(d):l))+'"/>\n <input type="hidden" name="info" value="');(b=f.info)?b=b.call(d,{hash:{},data:c}):(b=d.info,b=typeof b===k?b.apply(d):b);a+=m(b)+'"/>\n <input type="hidden" name="referrer" value="';
(b=f.referrer)?b=b.call(d,{hash:{},data:c}):(b=d.referrer,b=typeof b===k?b.apply(d):b);a+=m(b)+'"/>\n ';if((b=f.unless.call(d,d.groups,{hash:{},inverse:n.noop,fn:n.program(1,function(e,a){var b;return(b=f["if"].call(e,e.groupId,{hash:{},inverse:n.noop,fn:n.program(2,g,a),data:a}))||0===b?b:""},c),data:c}))||0===b)a+=b;b={hash:{},data:c};a=a+'\n\n <div class="errors"></div>\n\n <table cellspacing="1" cellpadding="5" border="0" class="form">\n <tr>\n <td><strong>'+(m((l=f.L10n,
l?l.call(d,"form.field.email",b):j.call(d,"L10n","form.field.email",b)))+':</strong></td>\n <td><input type="text" name="email" size="50" value="');(b=f.email)?b=b.call(d,{hash:{},data:c}):(b=d.email,b=typeof b===k?b.apply(d):b);a+=m(b)+'" class="username"/></td>\n </tr>\n <tr>\n <td><strong>';b={hash:{},data:c};a+=m((l=f.L10n,l?l.call(d,"form.field.name",b):j.call(d,"L10n","form.field.name",b)))+':</strong></td>\n <td><input type="text" name="name" size="50" value="';
s.chat_status_message=r(function(a,d,f,b,c){this.compilerInfo=[3,">= 1.0.0-rc.4"];f=f||a.helpers;c=c||{};a=this.escapeExpression;(f=f.message)?f=f.call(d,{hash:{},data:c}):(f=d.message,f="function"===typeof f?f.apply(d):f);return a(f)});s.chat_status_typing=r(function(a,d,f,b,c){this.compilerInfo=[3,">= 1.0.0-rc.4"];f=f||a.helpers;c=c||{};var g;a=f.helperMissing;b=this.escapeExpression;c={hash:{},data:c};return b((g=f.L10n,g?g.call(d,"typing.remote",c):a.call(d,"L10n","typing.remote",c)))});s.invitation_layout=
r(function(){this.compilerInfo=[3,">= 1.0.0-rc.4"];return'<div id="invitation-messages-region"></div>'});s.leave_message_description=r(function(a,d,f,b,c){this.compilerInfo=[3,">= 1.0.0-rc.4"];f=f||a.helpers;c=c||{};var g,q=f.helperMissing,p=this.escapeExpression;b={hash:{},data:c};a='<div class="buttons">\n <a href="javascript:window.close();" title="'+(p((g=f.L10n,g?g.call(d,"leavemessage.close",b):q.call(d,"L10n","leavemessage.close",b)))+'">\n <img class="tpl-image iclosewin" src="'+
p((g=(g=d.page,null==g||!1===g?g:g.webimRoot),"function"===typeof g?g.apply(d):g))+'/images/free.gif" alt="');b={hash:{},data:c};a+=p((g=f.L10n,g?g.call(d,"leavemessage.close",b):q.call(d,"L10n","leavemessage.close",b)))+'" />\n </a>\n</div>\n<div class="messagetxt">';b={hash:{},data:c};return a+=p((g=f.L10n,g?g.call(d,"leavemessage.descr",b):q.call(d,"L10n","leavemessage.descr",b)))+"</div>"});s.leave_message_form=r(function(a,d,f,b,c){function g(e,a){var c,b;c='<input type="hidden" name="group" value="';
(b=f.groupId)?b=b.call(e,{hash:{},data:a}):(b=e.groupId,b=typeof b===k?b.apply(e):b);return c+=m(b)+'"/>'}function q(e,a){var c,b,j;c=""+('\n <option value="'+m((b=e.id,typeof b===k?b.apply(e):b))+'" ');if((j=f["if"].call(e,e.selected,{hash:{},inverse:n.noop,fn:n.program(6,p,a),data:a}))||0===j)c+=j;return c+=">"+m((b=e.name,typeof b===k?b.apply(e):b))+"</option>\n "}function p(){return'selected="selected"'}function r(e,a){var b;return(b=f["if"].call(e,e.selected,
{hash:{},inverse:n.noop,fn:n.program(9,h,a),data:a}))||0===b?b:""}function h(e){var a;return m((a=e.description,typeof a===k?a.apply(e):a))}this.compilerInfo=[3,">= 1.0.0-rc.4"];f=f||a.helpers;c=c||{};var l,k="function",m=this.escapeExpression,n=this,j=f.helperMissing;a=""+('<form name="leaveMessageForm" method="post" action="">\n <input type="hidden" name="style" value="'+m((l=(l=d.page,null==l||!1===l?l:l.style),typeof l===k?l.apply(d):l))+'"/>\n <input type="hidden" name="info" value="');
(b=f.info)?b=b.call(d,{hash:{},data:c}):(b=d.info,b=typeof b===k?b.apply(d):b);a+=m(b)+'"/>\n <input type="hidden" name="referrer" value="';(b=f.referrer)?b=b.call(d,{hash:{},data:c}):(b=d.referrer,b=typeof b===k?b.apply(d):b);a+=m(b)+'"/>\n ';if((b=f.unless.call(d,d.groups,{hash:{},inverse:n.noop,fn:n.program(1,function(e,a){var b;return(b=f["if"].call(e,e.groupId,{hash:{},inverse:n.noop,fn:n.program(2,g,a),data:a}))||0===b?b:""},c),data:c}))||0===b)a+=b;b={hash:{},data:c};a=a+'\n\n <div class="errors"></div>\n\n <table cellspacing="1" cellpadding="5" border="0" class="form">\n <tr>\n <td><strong>'+
(m((l=f.L10n,l?l.call(d,"form.field.email",b):j.call(d,"L10n","form.field.email",b)))+':</strong></td>\n <td><input type="text" name="email" size="50" value="');(b=f.email)?b=b.call(d,{hash:{},data:c}):(b=d.email,b=typeof b===k?b.apply(d):b);a+=m(b)+'" class="username"/></td>\n </tr>\n <tr>\n <td><strong>';b={hash:{},data:c};a+=m((l=f.L10n,l?l.call(d,"form.field.name",b):j.call(d,"L10n","form.field.name",b)))+':</strong></td>\n <td><input type="text" name="name" size="50" value="';
(b=f.name)?b=b.call(d,{hash:{},data:c}):(b=d.name,b=typeof b===k?b.apply(d):b);a+=m(b)+'" class="username"/></td>\n </tr>\n ';if((b=f["if"].call(d,d.groups,{hash:{},inverse:n.noop,fn:n.program(4,function(e,a){var b,c,d;d={hash:{},data:a};b='\n <tr>\n <td class="text"><strong>'+(m((c=f.L10n,c?c.call(e,"form.field.department",d):j.call(e,"L10n","form.field.department",d)))+'</strong></td>\n <td>\n <select name="group" style="min-width:200px;">\n ');
if((d=f.each.call(e,e.groups,{hash:{},inverse:n.noop,fn:n.program(5,q,a),data:a}))||0===d)b+=d;b+='\n </select>\n </td>\n </tr>\n <tr>\n <td class="text"><strong>';d={hash:{},data:a};b+=m((c=f.L10n,c?c.call(e,"form.field.department.description",d):j.call(e,"L10n","form.field.department.description",d)))+'</strong></td>\n <td class="text" id="groupDescription">\n ';if((d=f.each.call(e,e.groups,{hash:{},inverse:n.noop,fn:n.program(8,
r,a),data:a}))||0===d)b+=d;return b+="\n </td>\n </tr>\n "},c),data:c}))||0===b)a+=b;a+="\n <tr>\n <td><strong>";b={hash:{},data:c};a+=m((l=f.L10n,l?l.call(d,"form.field.message",b):j.call(d,"L10n","form.field.message",b)))+':</strong></td>\n <td valign="top">\n <textarea name="message" tabindex="0" cols="40" rows="5">';(b=f.message)?b=b.call(d,{hash:{},data:c}):(b=d.message,b=typeof b===k?b.apply(d):b);a+=m(b)+"</textarea>\n </td>\n </tr>\n ";

View File

@ -103,6 +103,9 @@
${if:leaveMessageOptions}
leaveMessageOptions: ${page:leaveMessageOptions},
${endif:leaveMessageOptions}
${if:invitationOptions}
invitationOptions: ${page:invitationOptions},
${endif:invitationOptions}
startFrom: "${page:startFrom}",
plugins: ${page:js_plugin_options}
});

View File

@ -80,3 +80,10 @@
cursor: pointer !important;
float: left !important;
}
#mibewinvitationframe {
width: 398px;
height: 150px;
overflow: hidden;
border: 1px solid #000;
}

View File

@ -1,67 +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.
*/
$page['title'] = getlocal("invitation.title");
function tpl_header() { global $page, $webimroot;
?>
<!-- External libs -->
<script type="text/javascript" src="<?php echo $webimroot ?>/js/libs/jquery.min.js"></script>
<script type="text/javascript" src="<?php echo $webimroot ?>/js/libs/json2.js"></script>
<script type="text/javascript" src="<?php echo $webimroot ?>/js/libs/underscore-min.js"></script>
<script type="text/javascript" src="<?php echo $webimroot ?>/js/libs/backbone-min.js"></script>
<script type="text/javascript" src="<?php echo $webimroot ?>/js/libs/backbone.marionette.min.js"></script>
<script type="text/javascript" src="<?php echo $webimroot ?>/js/libs/handlebars.js"></script>
<!-- Application files -->
<script type="text/javascript" src="<?php echo $webimroot ?>/js/compiled/mibewapi.js"></script>
<script type="text/javascript" src="<?php echo $webimroot ?>/js/compiled/default_app.js"></script>
<script type="text/javascript" src="<?php echo $webimroot ?>/js/compiled/invite_app.js"></script>
<script type="text/javascript"><!--
jQuery(document).ready(function(){
Mibew.Application.start({
server: {
url: "<?php echo $webimroot ?>/operator/invitationstate.php",
requestsFrequency: <?php echo $page['frequency'] ?>
},
visitorId: "<?php echo $page['visitor'] ?>",
chatLink: "<?php echo $webimroot ?>/operator/agent.php"
});
});
//--></script>
<?php
}
function tpl_content() { global $page, $webimroot;
?>
<?php echo getlocal("invitation.sent"); ?>
<div class="ajaxWait"></div>
<?php
} /* content */
require_once('inc_main.php');
?>

View File

@ -71,9 +71,12 @@ if (Settings::get('enabletracking') == '1') {
$operatorName = ($locale == $home_locale)
? $operator['vclocalename']
: $operator['vccommonname'];
$response['data']['invitation']['operator'] = htmlspecialchars($operatorName);
$response['data']['invitation']['message'] = getlocal("invitation.message");
$response['data']['invitation']['avatar'] = htmlspecialchars($operator['vcavatar']);
$response['data']['invitation'] = array(
'operator' => htmlspecialchars($operatorName),
'avatar' => htmlspecialchars($operator['vcavatar']),
'url' => get_app_location(true, is_secure_request())
. '/client.php?act=invitation'
);
}
// Check if visitor reject invitation