Completely rewrite User's JavaScript application

This commit is contained in:
Dmitriy Simushev 2013-02-01 15:09:05 +00:00
parent d2531a526d
commit 6b9f2bd789
65 changed files with 3136 additions and 986 deletions

View File

@ -16,6 +16,7 @@
default_app_js - Build JavaScript files related to default application
chat_app_js - Build JavaScript files related to chat application
users_app_js - Build JavaScript files related to users application
core_handlebars - Compile Handlebars templates of the Core
@ -263,6 +264,14 @@
<echo>Chat JavaScript application built.</echo>
</target>
<!-- Compile and concatenate JavaScript files related to users application -->
<target name="users_app_js" depends="default_app_js">
<antcall target="app_js">
<param name="app_name" value="users" />
</antcall>
<echo>Users JavaScript application built.</echo>
</target>
<!-- Compile Handlebars templates of the Core -->
<target name="core_handlebars">
<echo>Compile Handlebars templates of the Core</echo>
@ -292,7 +301,7 @@
</target>
<!-- Build all project -->
<target name="all" depends="core_handlebars,chat_app_js,styles_all">
<target name="all" depends="chat_app_js,users_app_js,styles_all">
<echo>Mibew Messenger built.</echo>
</target>

View File

@ -86,6 +86,10 @@ a {
height:40px;
}
.inline-block {
display: inline-block;
}
#footer {
background: white url(images/footer.gif) bottom repeat-x;
font-size:11px;
@ -557,8 +561,11 @@ table.awaiting td.visitor {
border-bottom: 1px solid #ccc;
padding: 10px 8px;
margin: 0px;
text-align: center;
}
table.awaiting .no-threads, table.awaiting .no-visitors {
height: 30px;
}
.awaiting .visitor a { color: #296685; }
.awaiting tr:hover .visitor, .awaiting tr:hover .visitor a { color: #1D485E; }
@ -571,27 +578,26 @@ table.awaiting td.visitor {
.awaiting tr.inchat:hover .visitor, .awaiting tr.inchat:hover .visitor a { color: #444; }
.awaiting tr.inchat a { text-decoration: none; }
.firstmessage {
.first-message {
text-align: right;
font-size: 0.8em;
padding-right: 10px;
}
.firstmessage a {
.first-message a {
text-decoration: none;
}
.firstmessage a:hover {
.first-message a:hover {
text-decoration: underline;
}
#status-panel-region {
margin: 10px;
}
#connstatus {
float:right;
margin: 10px 10px;
}
#connlinks {
margin: 10px 10px;
}
#connlinks a {
@ -603,12 +609,64 @@ table.awaiting td.visitor {
text-decoration: underline;
}
.default-thread-controls {
width: 100px;
}
.default-visitor-controls {
width: 20px;
}
.default-thread-controls .control,
.default-visitor-controls .control {
height: 15px;
width: 15px;
margin: 0 2px;
border: none;
cursor: pointer;
}
.open-control {
background: no-repeat top left url('images/tbliclspeak.gif');
}
.view-control {
background: no-repeat top left url('images/tbliclread.gif');
}
.track-control {
background: no-repeat top left url('images/tblictrack.gif');
}
.ban-control {
background: no-repeat top left url('images/ban.gif');
}
#sound-region {
display: none;
}
/* online operators */
#onlineoperators {
#agents-region {
padding-right: 10px;
float: right;
}
.agent-status-away, .agent-status-online {
display: inline-block;
height: 12px;
width: 12px;
border: none;
background-repeat: no-repeat;
margin-left: 5px;
margin-right: 2px;
}
.agent-status-away {
background-image: url("images/opaway.gif");
}
.agent-status-online {
background-image: url("images/oponline.gif");
}
/* search */

View File

@ -0,0 +1,21 @@
/*
This file is part of Mibew Messenger project.
Copyright (c) 2005-2011 Mibew Messenger Community
All rights reserved. The contents of this file are subject to the terms of
the Eclipse Public License v1.0 which accompanies this distribution, and
is available at http://www.eclipse.org/legal/epl-v10.html
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 2 or later (the "GPL"), in which case
the provisions of the GPL are applicable instead of those above. If you wish
to allow use of your version of this file only under the terms of the GPL, and
not to allow others to use your version of this file under the terms of the
EPL, indicate your decision by deleting the provisions above and replace them
with the notice and other provisions required by the GPL.
*/
.inline-block {
display: inline;
zoom: 1;
}

View File

@ -1,35 +0,0 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
Ajax.PeriodicalUpdater=Class.create();
Class.inherit(Ajax.PeriodicalUpdater,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.frequency=this._options.frequency||2;this.updater={};this.update()},handleException:function(){this._options.handleError&&this._options.handleError("offline, reconnecting");this.stopUpdate();this.timer=setTimeout(this.update.bind(this),
1E3)},handleTimeout:function(){this._options.handleError&&this._options.handleError("timeout, reconnecting");this.stopUpdate();this.timer=setTimeout(this.update.bind(this),1E3)},stopUpdate:function(){this.updater._options&&(this.updater._options.onComplete=void 0);clearTimeout(this.timer)},update:function(){this._options.updateParams&&(this._options.parameters=this._options.updateParams());this.updater=new Ajax.Request(this._options.url,this._options)},requestComplete:function(a){try{var b=Ajax.getXml(a);
b?(this._options.updateContent||Ajax.emptyFunction)(b):this._options.handleError&&this._options.handleError("reconnecting")}catch(c){}this.timer=setTimeout(this.update.bind(this),1E3*this.frequency)}});
var HtmlGenerationUtils={popupLink:function(a,b,c,d,h,f,e){return'<a href="'+a+'"'+(null!=e?' class="'+e+'"':"")+' target="_blank" title="'+b+'" onclick="this.newWindow = window.open(\''+a+"', '"+c+"', 'toolbar=0,scrollbars=0,location=0,status=1,menubar=0,width="+h+",height="+f+",resizable=1');this.newWindow.focus();this.newWindow.opener=window;return false;\">"+d+"</a>"},generateOneRowTable:function(a){return'<table class="inner"><tr>'+a+"</tr></table>"},viewOpenCell:function(a,b,c,d,h,f,e,m){b=
b+"?thread="+c;f="<td>";f=h||d?f+HtmlGenerationUtils.popupLink(m||!d?b:b+"&viewonly=true",localized[h?0:1],"ImCenter"+c,a,640,480,null):f+('<a href="#">'+a+"</a>");f+="</td>";""!=e&&(f=f+('</tr><tr><td class="firstmessage" colspan="2"><a href="javascript:void(0)" title="'+e+'" onclick="alert(this.title);return false;">')+(30<e.length?e.substring(0,30)+"...":e),f+="</a></td>");return HtmlGenerationUtils.generateOneRowTable(f)},viewActionsCell:function(a,b,c,d,h,f){a=a+"?thread="+b;var e="";d&&(e=e+
'<td class="icon">'+HtmlGenerationUtils.popupLink(a,localized[0],"ImCenter"+b,'<img src="'+webimRoot+'/images/tbliclspeak.gif" width="15" height="15" border="0" alt="'+localized[0]+'">',640,480,null),e+="</td>");c&&(e+='<td class="icon">',e+=HtmlGenerationUtils.popupLink(a+"&viewonly=true",localized[1],"ImCenter"+b,'<img src="'+webimRoot+'/images/tbliclread.gif" width="15" height="15" border="0" alt="'+localized[1]+'">',640,480,null),e+="</td>");h&&(e+='<td class="icon">',e+=HtmlGenerationUtils.popupLink(f+
"?thread="+b,localized[6],"ImTracked"+b,'<img src="'+webimRoot+'/images/tblictrack.gif" width="15" height="15" border="0" alt="'+localized[6]+'">',640,480,null),e+="</td>");return e},banCell:function(a,b){return'<td class="icon">'+HtmlGenerationUtils.popupLink(webimRoot+"/operator/ban.php?"+(b?"id="+b:"thread="+a),localized[2],"ban"+a,'<img src="'+webimRoot+'/images/ban.gif" width="15" height="15" border="0" alt="'+localized[2]+'">',720,480,null)+"</td>"},viewVisOpenCell:function(a,b,c,d,h){var f=
"<td>",f=h?f+HtmlGenerationUtils.popupLink(b+"?visitor="+c,localized[7],"ImCenter"+c,a,640,480,null):f+('<a href="#">'+a+"</a>"),f=f+'</td><td class="icon">';a=HtmlGenerationUtils.popupLink(d+"?visitor="+c,localized[6],"ImTracked"+c,'<img src="'+webimRoot+'/images/tblictrack.gif" width="15" height="15" border="0" alt="'+localized[6]+'">',640,480,null);a=a.replace("scrollbars=0","scrollbars=1");f+=a;f+="</td>";return HtmlGenerationUtils.generateOneRowTable(f)}};Ajax.ThreadListUpdater=Class.create();
Class.inherit(Ajax.ThreadListUpdater,Ajax.Base,{initialize:function(a){this.setOptions(a);this._options.updateParams=this.updateParams.bind(this);this._options.handleError=this.handleError.bind(this);this._options.updateContent=this.updateContent.bind(this);this._options.lastrevision=0;this.threadTimers={};this.delta=0;this.t=this._options.table;this.t2=this._options.visitors_table;this.periodicalUpdater=new Ajax.PeriodicalUpdater(this._options);this.old_visitors={};this.visitors={};this.visitorTimers=
{}},updateParams:function(){return"since="+this._options.lastrevision+"&status="+this._options.istatus+(this._options.showonline?"&showonline=1":"")+(this._options.showvisitors?"&showvisitors=1":"")},setStatus:function(a){this._options.status.innerHTML=a},handleError:function(a){this.setStatus(a)},updateThread:function(a){function b(a,b,d,c){if(a=CommonUtils.getCell(d,b,a))a.innerHTML=c}for(var c,d,h,f=!1,e=!1,m=!1,n=null,p=null,g=0;g<a.attributes.length;g++){var j=a.attributes[g];"id"==j.nodeName?
c=j.nodeValue:"stateid"==j.nodeName?d=j.nodeValue:"state"==j.nodeName?h=j.nodeValue:"canopen"==j.nodeName?e=!0:"canview"==j.nodeName?f=!0:"canban"==j.nodeName?m=!0:"ban"==j.nodeName?n=j.nodeValue:"banid"==j.nodeName&&(p=j.nodeValue)}g=CommonUtils.getRow("thr"+c,this.t);if("closed"==d)g&&this.t.deleteRow(g.rowIndex),this.threadTimers[c]=null;else{var j=NodeUtils.getNodeValue(a,"name"),k=HtmlGenerationUtils.viewActionsCell(this._options.agentservl,c,f,e,this._options.showvisitors,this._options.trackedservl),
l=NodeUtils.getNodeValue(a,"addr"),r=NodeUtils.getNodeValue(a,"time"),t=NodeUtils.getNodeValue(a,"agent"),s=NodeUtils.getNodeValue(a,"modified"),u=NodeUtils.getNodeValue(a,"message"),q="<td>"+NodeUtils.getNodeValue(a,"useragent")+"</td>";null!=n&&(q="<td>"+NodeUtils.getNodeValue(a,"reason")+"</td>");m&&(k+=HtmlGenerationUtils.banCell(c,p));k=HtmlGenerationUtils.generateOneRowTable(k);q=HtmlGenerationUtils.generateOneRowTable(q);a=CommonUtils.getRow("t"+d,this.t);m=CommonUtils.getRow("t"+d+"end",this.t);
if(null!=g&&(g.rowIndex<=a.rowIndex||g.rowIndex>=m.rowIndex))this.t.deleteRow(g.rowIndex),g=this.threadTimers[c]=null;if(null==g){if(g=this.t.insertRow(a.rowIndex+1),g.className="blocked"==n&&"chat"!=d?"ban":"in"+d,g.id="thr"+c,this.threadTimers[c]=[r,s,d],CommonUtils.insertCell(g,"name","visitor",null,null,HtmlGenerationUtils.viewOpenCell(j,this._options.agentservl,c,f,e,n,u,"chat"!=d,this._options.showvisitors,this._options.trackedservl)),CommonUtils.insertCell(g,"actions","visitor","center",null,
k),CommonUtils.insertCell(g,"contid","visitor","center",null,l),CommonUtils.insertCell(g,"state","visitor","center",null,h),CommonUtils.insertCell(g,"op","visitor","center",null,t),CommonUtils.insertCell(g,"time","visitor","center",null,this.getTimeSince(r)),CommonUtils.insertCell(g,"wait","visitor","center",null,"chat"!=d?this.getTimeSince(s):"-"),CommonUtils.insertCell(g,"etc","visitor","center",null,q),"wait"==d||"prio"==d)return!0}else this.threadTimers[c]=[r,s,d],g.className="blocked"==n&&"chat"!=
d?"ban":"in"+d,b(this.t,g,"name",HtmlGenerationUtils.viewOpenCell(j,this._options.agentservl,c,f,e,n,u,"chat"!=d,this._options.showvisitors,this._options.trackedservl)),b(this.t,g,"actions",k),b(this.t,g,"contid",l),b(this.t,g,"state",h),b(this.t,g,"op",t),b(this.t,g,"time",this.getTimeSince(r)),b(this.t,g,"wait","chat"!=d?this.getTimeSince(s):"-"),b(this.t,g,"etc",q);return!1}},updateQueueMessages:function(){function a(a,b){var c=$(b),e=$(b+"end");return null==c||null==e?!1:c.rowIndex+1<e.rowIndex}
var b=$("statustd");if(b){var c=a(this.t,"twait")||a(this.t,"tprio")||a(this.t,"tchat");b.innerHTML=c?"":this._options.noclients;b.height=c?5:30}},getTimeSince:function(a){a=Math.floor(((new Date).getTime()-a-this.delta)/1E3);var b=Math.floor(a/60),c="";a%=60;10>a&&(a="0"+a);60<=b&&(c=Math.floor(b/60),b%=60,10>b&&(b="0"+b),c+=":");return c+b+":"+a},updateTimers:function(){for(var a in this.threadTimers)if(null!=this.threadTimers[a]){var b=this.threadTimers[a],c=CommonUtils.getRow("thr"+a,this.t);
if(null!=c){var d=this.getTimeSince(b[0]),h=CommonUtils.getCell("time",c,this.t);h&&(h.innerHTML=d);b="chat"!=b[2]?this.getTimeSince(b[1]):"-";if(c=CommonUtils.getCell("wait",c,this.t))c.innerHTML=b}}},updateThreads:function(a){var b=!1,c=NodeUtils.getAttrValue(a,"time"),d=NodeUtils.getAttrValue(a,"revision");c&&(this.delta=(new Date).getTime()-c);d&&(this._options.lastrevision=d);for(c=0;c<a.childNodes.length;c++)d=a.childNodes[c],"thread"==d.tagName&&this.updateThread(d)&&(b=!0);this.updateQueueMessages();
this.updateTimers();this.setStatus(this._options.istatus?localized[8]:localized[9]);b&&(playSound(webimRoot+"/sounds/new_user.wav"),window.focus(),updaterOptions.showpopup&&alert(localized[5]))},updateOperators:function(a){var b=$("onlineoperators");if(b){for(var c=[],d=0;d<a.childNodes.length;d++){var h=a.childNodes[d];if("operator"==h.tagName){var f=NodeUtils.getAttrValue(h,"name"),h=null!=NodeUtils.getAttrValue(h,"away");c[c.length]='<img src="'+webimRoot+"/images/op"+(h?"away":"online")+'.gif" width="12" height="12" border="0" alt="'+
localized[1]+'"> '+f}}b.innerHTML=c.join(", ")}},updateVisitorsTimers:function(){for(var a in this.visitorTimers)if(null!=this.visitorTimers[a]){var b=this.visitorTimers[a],c=CommonUtils.getRow("vis"+a,this.t2);if(null!=c){var d=function(a,b,c,d){if(a=CommonUtils.getCell(c,b,a))a.innerHTML=d};d(this.t2,c,"time",this.getTimeSince(b[0]));d(this.t2,c,"modified",this.getTimeSince(b[1]));null!=b[2]&&d(this.t2,c,"invitationtime",this.getTimeSince(b[2]))}}},updateVisitor:function(a){function b(a,b,c,d){if(a=
CommonUtils.getCell(c,b,a))a.innerHTML=d}for(var c,d=0;d<a.attributes.length;d++){var h=a.attributes[d];"id"==h.nodeName&&(c=h.nodeValue)}var h=NodeUtils.getNodeValue(a,"addr"),f=NodeUtils.getNodeValue(a,"username"),e=NodeUtils.getNodeValue(a,"useragent"),m=NodeUtils.getNodeValue(a,"time"),n=NodeUtils.getNodeValue(a,"modified"),p=NodeUtils.getNodeValue(a,"invitations"),g=NodeUtils.getNodeValue(a,"chats"),j=null,k=null;a=a.getElementsByTagName("invitation")[0];for(d=0;d<a.childNodes.length;d++){var l=
a.childNodes[d];"operator"==l.tagName?j=l.firstChild.nodeValue:"invitationtime"==l.tagName&&(k=l.firstChild.nodeValue)}l=null==j?"free":"invited";d=CommonUtils.getRow("vis"+c,this.t2);a=CommonUtils.getRow("vis"+l,this.t2);l=CommonUtils.getRow("vis"+l+"end",this.t2);if(null!=d&&(d.rowIndex<=a.rowIndex||d.rowIndex>=l.rowIndex))this.t2.deleteRow(d.rowIndex),d=this.visitorTimers[c]=null;null==d?(d=this.t2.insertRow(a.rowIndex+1),d.id="vis"+c,this.visitorTimers[c]=[m,n,k],CommonUtils.insertCell(d,"username",
"visitor",null,null,HtmlGenerationUtils.viewVisOpenCell(f,this._options.inviteservl,c,this._options.trackedservl,null==j)),CommonUtils.insertCell(d,"addr","visitor","center",null,h),CommonUtils.insertCell(d,"time","visitor","center",null,this.getTimeSince(m)),CommonUtils.insertCell(d,"modified","visitor","center",null,this.getTimeSince(n)),CommonUtils.insertCell(d,"operator","visitor","center",null,null!=j?j:"-"),CommonUtils.insertCell(d,"invitationtime","visitor","center",null,null!=j?this.getTimeSince(k):
"-"),CommonUtils.insertCell(d,"invitations","visitor","center",null,p+" / "+g),CommonUtils.insertCell(d,"useragent","visitor","center",null,e)):(this.visitorTimers[c]=[m,n,k],b(this.t2,d,"username",HtmlGenerationUtils.viewVisOpenCell(f,this._options.inviteservl,c,this._options.trackedservl,null==j)),b(this.t2,d,"addr",h),b(this.t2,d,"operator",null!=j?j:"-"),b(this.t2,d,"time",this.getTimeSince(m)),b(this.t2,d,"modified",this.getTimeSince(n)),b(this.t2,d,"invitationtime",null!=j?this.getTimeSince(k):
"-"),b(this.t2,d,"invitations",p+" / "+g),b(this.t2,d,"useragent",e));this.visitors[c]=1;return!1},removeOldVisitors:function(){for(id in this.old_visitors)if(void 0===this.visitors[id]){var a=CommonUtils.getRow("vis"+id,this.t2);a&&this.t2.deleteRow(a.rowIndex);this.visitorTimers[id]=null}},updateVisitorsList:function(a){var b=$("visstatustd");b&&(b.innerHTML=0<a?"":this._options.novisitors,b.height=0<a?5:30)},updateVisitors:function(a){this.old_visitors=this.visitors;this.visitors={};for(var b=
0,c=0;c<a.childNodes.length;c++){var d=a.childNodes[c];"visitor"==d.tagName&&(b++,this.updateVisitor(d))}this.updateVisitorsTimers();this.removeOldVisitors();this.updateVisitorsList(b)},updateContent:function(a){if("update"==a.tagName)for(var b=0;b<a.childNodes.length;b++){var c=a.childNodes[b];"threads"==c.tagName?this.updateThreads(c):"operators"==c.tagName?this.updateOperators(c):"visitors"==c.tagName&&this.updateVisitors(c)}else"error"==a.tagName?this.setStatus(NodeUtils.getNodeValue(a,"descr")):
this.setStatus("reconnecting")}});function togglemenu(){$("sidebar")&&($("wcontent")&&$("togglemenu"))&&("contentnomenu"==$("wcontent").className?($("sidebar").style.display="block",$("wcontent").className="contentinner",$("togglemenu").innerHTML=localized[4]):($("sidebar").style.display="none",$("wcontent").className="contentnomenu",$("togglemenu").innerHTML=localized[3]))}var webimRoot="";Behaviour.register({"#togglemenu":function(a){a.onclick=function(){togglemenu()}}});
EventHelper.register(window,"onload",function(){webimRoot=updaterOptions.wroot;new Ajax.ThreadListUpdater({table:$("threadlist"),status:$("connstatus"),istatus:0,visitors_table:$("visitorslist")}.extend(updaterOptions||{}));updaterOptions.havemenu||togglemenu()});

View File

@ -0,0 +1,10 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(a,g,h){var b=new g.Marionette.Application;b.addRegions({agentsRegion:"#agents-region",statusPanelRegion:"#status-panel-region",threadsRegion:"#threads-region",visitorsRegion:"#visitors-region",soundRegion:"#sound-region"});b.addInitializer(function(e){var f=a.Objects,c=a.Objects.Models,d=a.Objects.Collections;f.server=new a.Server(h.extend({interactionType:MibewAPIUsersInteraction},e.server));c.page=new a.Models.Page(e.page);c.agent=new a.Models.Agent(e.agent);d.threads=new a.Collections.Threads;
b.threadsRegion.show(new a.Views.ThreadsCollection({collection:d.threads}));e.page.showOnlineOperators&&(d.visitors=new a.Collections.Visitors,b.visitorsRegion.show(new a.Views.VisitorsCollection({collection:d.visitors})));c.statusPanel=new a.Models.StatusPanel;b.statusPanelRegion.show(new a.Views.StatusPanel({model:c.statusPanel}));e.page.showOnlineOperators&&(d.agents=new a.Collections.Agents,b.agentsRegion.show(new a.Views.AgentsCollection({collection:d.agents})));c.sound=new a.Models.Sound;b.soundRegion.show(new a.Views.Sound({model:c.sound}));
f.server.callFunctionsPeriodically(function(){return[{"function":"update",arguments:{"return":{},references:{},agentId:c.agent.id}}]},function(){})});b.on("start",function(){a.Objects.server.runUpdater()});a.Application=b})(Mibew,Backbone,_);

View File

@ -0,0 +1,8 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(a){a.Views.AgentsCollection=a.Views.CollectionBase.extend({itemView:a.Views.Agent,className:"agents-collection",collectionEvents:{"sort add remove reset":"render"},initialize:function(){this.on("itemview:before:render",this.updateIndexes,this)},updateIndexes:function(a){var b=this.collection,c=a.model;c&&(a.isModelFirst=0==b.indexOf(c),a.isModelLast=b.indexOf(c)==b.length-1)}})})(Mibew);

View File

@ -0,0 +1,11 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(d,h,j,k){d.Views.ThreadsCollection=h.Marionette.CompositeView.extend({template:j.templates.threads_collection,itemView:d.Views.QueuedThread,itemViewContainer:"#threads-container",emptyView:d.Views.NoThreads,className:"threads-collection",collectionEvents:{sort:"renderCollection","sort:field":"createSortField",add:"threadAdded"},itemViewOptions:function(a){return{tagName:d.Objects.Models.page.get("threadTag"),collection:a.get("controls")}},initialize:function(){window.setInterval(k.bind(this.renderCollection,
this),2E3);this.on("itemview:before:render",this.updateStyles,this)},updateStyles:function(a){var b=this.collection,c=a.model,d=this;if(c.id){var e=this.getQueueCode(c),f=!1,g=!1,b=b.filter(function(a){return d.getQueueCode(a)==e});0<b.length&&(g=b[0].id==c.id,f=b[b.length-1].id==c.id);if(0<a.lastStyles.length){c=0;for(b=a.lastStyles.length;c<b;c++)a.$el.removeClass(a.lastStyles[c]);a.lastStyles=[]}c=(e!=this.QUEUE_BAN?"in":"")+this.queueCodeToString(e);a.lastStyles.push(c);g&&a.lastStyles.push(c+
"-first");f&&a.lastStyles.push(c+"-last");c=0;for(b=a.lastStyles.length;c<b;c++)a.$el.addClass(a.lastStyles[c])}},createSortField:function(a,b){var c=this.getQueueCode(a)||"Z";b.field=c.toString()+"_"+a.get("waitingTime").toString()},threadAdded:function(){var a=d.Objects.Models.page.get("webimRoot");a&&d.Objects.Models.sound.play(a+"/sounds/new_user.wav")},getQueueCode:function(a){var b=a.get("state");return!1!=a.get("ban")&&b!=a.STATE_CHATTING?this.QUEUE_BAN:b==a.STATE_QUEUE||b==a.STATE_LOADING?
this.QUEUE_WAITING:b==a.STATE_CLOSED||b==a.STATE_LEFT?this.QUEUE_CLOSED:b==a.STATE_WAITING?this.QUEUE_PRIO:b==a.STATE_CHATTING?this.QUEUE_CHATTING:!1},queueCodeToString:function(a){return a==this.QUEUE_PRIO?"prio":a==this.QUEUE_WAITING?"wait":a==this.QUEUE_CHATTING?"chat":a==this.QUEUE_BAN?"ban":a==this.QUEUE_CLOSED?"closed":""},QUEUE_PRIO:1,QUEUE_WAITING:2,QUEUE_CHATTING:3,QUEUE_BAN:4,QUEUE_CLOSED:5})})(Mibew,Backbone,Handlebars,_);

View File

@ -0,0 +1,9 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(a,b,c,d){a.Views.VisitorsCollection=b.Marionette.CompositeView.extend({template:c.templates.visitors_collection,itemView:a.Views.Visitor,itemViewContainer:"#visitors-container",emptyView:a.Views.NoVisitors,className:"visitors-collection",collectionEvents:{sort:"renderCollection"},itemViewOptions:function(b){return{tagName:a.Objects.Models.page.get("visitorTag"),collection:b.get("controls")}},initialize:function(){window.setInterval(d.bind(this.renderCollection,this),2E3);this.on("itemview:before:render",
this.updateStyles,this)}})})(Mibew,Backbone,Handlebars,_);

View File

@ -0,0 +1,8 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(b,c,d){b.Collections.Agents=c.Collection.extend({model:b.Models.Agent,comparator:function(a){return a.get("name")},initialize:function(){var a=b.Objects.Models.agent;b.Objects.server.callFunctionsPeriodically(function(){return[{"function":"updateOperators",arguments:{agentId:a.id,"return":{operators:"operators"},references:{}}}]},d.bind(this.updateOperators,this))},updateOperators:function(a){this.update(a.operators)}})})(Mibew,Backbone,_);

View File

@ -0,0 +1,10 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(c,e,f){c.Collections.Threads=e.Collection.extend({model:c.Models.QueuedThread,initialize:function(){this.revision=0;var a=this,b=c.Objects.Models.agent;c.Objects.server.callFunctionsPeriodically(function(){return[{"function":"currentTime",arguments:{agentId:b.id,"return":{time:"currentTime"},references:{}}},{"function":"updateThreads",arguments:{agentId:b.id,revision:a.revision,"return":{threads:"threads",lastRevision:"lastRevision"},references:{}}}]},f.bind(this.updateThreads,this))},comparator:function(a){var b=
{field:a.get("waitingTime").toString()};this.trigger("sort:field",a,b);return b.field},updateThreads:function(a){if(0==a.errorCode){if(0<a.threads.length){var b;b=a.currentTime?Math.round((new Date).getTime()/1E3)-a.currentTime:0;for(var d=0,e=a.threads.length;d<e;d++)a.threads[d].totalTime=parseInt(a.threads[d].totalTime)+b,a.threads[d].waitingTime=parseInt(a.threads[d].waitingTime)+b;this.trigger("before:update:threads",a.threads);var f=c.Models.Thread.prototype.STATE_CLOSED,g=c.Models.Thread.prototype.STATE_LEFT;
b=[];this.update(a.threads,{remove:!1,sort:!1});b=this.filter(function(a){return a.get("state")==f||a.get("state")==g});0<b.length&&this.remove(b);this.sort();this.trigger("after:update:threads")}this.revision=a.lastRevision}}})})(Mibew,Backbone,_);

View File

@ -0,0 +1,9 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(b,e,f){b.Collections.Visitors=e.Collection.extend({model:b.Models.Visitor,initialize:function(){var a=b.Objects.Models.agent;b.Objects.server.callFunctionsPeriodically(function(){return[{"function":"currentTime",arguments:{agentId:a.id,"return":{time:"currentTime"},references:{}}},{"function":"updateVisitors",arguments:{agentId:a.id,"return":{visitors:"visitors"},references:{}}}]},f.bind(this.updateVisitors,this))},comparator:function(a){var c={field:a.get("firstTime").toString()};this.trigger("sort:field",
a,c);return c.field},updateVisitors:function(a){if(0==a.errorCode){var c;c=a.currentTime?Math.round((new Date).getTime()/1E3)-a.currentTime:0;for(var d=0,b=a.visitors.length;d<b;d++)a.visitors[d].lastTime=parseInt(a.visitors[d].lastTime)+c,a.visitors[d].firstTime=parseInt(a.visitors[d].firstTime)+c;this.trigger("before:update:visitors",a.visitors);this.update(a.visitors);this.trigger("after:update:visitors")}}})})(Mibew,Backbone,_);

View File

@ -0,0 +1,8 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(e){e.registerHelper("formatTimeSince",function(b){var a=Math.round((new Date).getTime()/1E3)-b;b=a%60;var d=Math.floor(a/60)%60,a=Math.floor(a/3600),c=[];0<a&&c.push(a);c.push(10>d?"0"+d:d);c.push(10>b?"0"+b:b);return c.join(":")})})(Handlebars);

View File

@ -0,0 +1,8 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(a){a.Regions={};a.Popup={};a.Popup.open=function(b,a,c){b=window.open(b,a,c);b.focus();b.opener=window}})(Mibew);

View File

@ -0,0 +1,8 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
MibewAPIUsersInteraction=function(){this.obligatoryArguments={"*":{agentId:null,"return":{},references:{}},result:{errorCode:0}};this.reservedFunctionNames=["result"]};MibewAPIUsersInteraction.prototype=new MibewAPIInteraction;

View File

@ -0,0 +1,8 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(b,c,d){b.Views.Agent=c.Marionette.ItemView.extend({template:d.templates.agent,tagName:"span",className:"agent",modelEvents:{change:"render"},initialize:function(){this.isModelLast=this.isModelFirst=!1},serializeData:function(){var a=this.model.toJSON();a.isFirst=this.isModelFirst;a.isLast=this.isModelLast;return a}})})(Mibew,Backbone,Handlebars);

View File

@ -0,0 +1,8 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(a,b,c){a.Views.NoThreads=b.Marionette.ItemView.extend({template:c.templates.no_threads,initialize:function(a){this.tagName=a.tagName}})})(Mibew,Backbone,Handlebars);

View File

@ -0,0 +1,8 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(a,b,c){a.Views.NoVisitors=b.Marionette.ItemView.extend({template:c.templates.no_visitors,initialize:function(a){this.tagName=a.tagName}})})(Mibew,Backbone,Handlebars);

View File

@ -0,0 +1,12 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(d,e){d.Views.QueuedThread=d.Views.CompositeBase.extend({template:e.templates.queued_thread,itemView:d.Views.Control,itemViewContainer:".thread-controls",className:"thread",modelEvents:{change:"render"},events:{"click .open-dialog":"openDialog","click .view-control":"viewDialog","click .track-control":"showTrack","click .ban-control":"showBan","click .geo-link":"showGeoInfo","click .first-message a":"showFirstMessage"},initialize:function(){this.lastStyles=[]},serializeData:function(){var a=
this.model,b=d.Objects.Models.page,c=a.toJSON();c.stateDesc=this.stateToDesc(a.get("state"));c.chatting=a.get("state")==a.STATE_CHATTING;c.tracked=b.get("showVisitors");c.firstMessage&&(c.firstMessagePreview=30<c.firstMessage.length?c.firstMessage.substring(0,30)+"...":c.firstMessage);return c},stateToDesc:function(a){var b=d.Localization;return a==this.model.STATE_QUEUE?b.get("chat.thread.state_wait"):a==this.model.STATE_WAITING?b.get("chat.thread.state_wait_for_another_agent"):a==this.model.STATE_CHATTING?
b.get("chat.thread.state_chatting_with_agent"):a==this.model.STATE_CLOSED?b.get("chat.thread.state_closed"):a==this.model.STATE_LOADING?b.get("chat.thread.state_loading"):""},showGeoInfo:function(){var a=this.model.get("userIp");if(a){var b=d.Objects.Models.page,c=b.get("geoLink").replace("{ip}",a);d.Popup.open(c,"ip"+a,b.get("geoWindowParams"))}},openDialog:function(){var a=this.model,a=a.get("state")==a.STATE_CHATTING&&a.get("canView");this.showDialogWindow(a)},viewDialog:function(){this.showDialogWindow(!0)},
showDialogWindow:function(a){var b=this.model.id,c=d.Objects.Models.page;d.Popup.open(c.get("agentLink")+"?thread="+b+(a?"&viewonly=true":""),"ImCenter"+b,c.get("chatWindowParams"))},showTrack:function(){var a=this.model.id,b=d.Objects.Models.page;d.Popup.open(b.get("trackedLink")+"?thread="+a,"ImTracked"+a,b.get("trackedUserWindowParams"))},showBan:function(){var a=this.model,b=a.get("ban"),c=d.Objects.Models.page;d.Popup.open(c.get("banLink")+"?"+(!1!==b?"id="+b.id:"thread="+a.id),"ImBan"+b.id,
c.get("banWindowParams"))},showFirstMessage:function(){var a=this.model.get("firstMessage");a&&alert(a)}})})(Mibew,Handlebars);

View File

@ -0,0 +1,8 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(a,c,d){a.Views.StatusPanel=c.Marionette.ItemView.extend({template:d.templates.status_panel,modelEvents:{change:"render"},ui:{changeStatus:"#change-status"},events:{"click #change-status":"changeAgentStatus"},initialize:function(){a.Objects.Models.agent.on("change",this.render,this)},changeAgentStatus:function(){this.model.changeAgentStatus()},serializeData:function(){var b=this.model.toJSON();b.agent=a.Objects.Models.agent.toJSON();return b}})})(Mibew,Backbone,Handlebars);

View File

@ -0,0 +1,9 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(a,d){a.Views.Visitor=a.Views.CompositeBase.extend({template:d.templates.visitor,itemView:a.Views.Control,itemViewContainer:".visitor-controls",className:"visitor",modelEvents:{change:"render"},events:{"click .invite-link":"inviteUser","click .geo-link":"showGeoInfo","click .track-control":"showTrack"},inviteUser:function(){if(!this.model.get("invitationInfo")){var b=this.model.id,c=a.Objects.Models.page;a.Popup.open(c.get("inviteLink")+"?visitor="+b,"ImCenter"+b,c.get("inviteWindowParams"))}},
showTrack:function(){var b=this.model.id,c=a.Objects.Models.page;a.Popup.open(c.get("trackedLink")+"?visitor="+b,"ImTracked"+b,c.get("trackedVisitorWindowParams"))},showGeoInfo:function(){var b=this.model.get("userIp");if(b){var c=a.Objects.Models.page,d=c.get("geoLink").replace("{ip}",b);a.Popup.open(d,"ip"+b,c.get("geoWindowParams"))}}})})(Mibew,Handlebars);

View File

@ -0,0 +1,8 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(a,b){a.Models.Agent=a.Models.User.extend({defaults:b.extend({},a.Models.User.prototype.defaults,{id:null,isAgent:!0,away:!1}),away:function(){this.setAvailability(!1)},available:function(){this.setAvailability(!0)},setAvailability:function(c){var b=this;a.Objects.server.callFunctions([{"function":c?"available":"away",arguments:{agentId:this.id,references:{},"return":{}}}],function(a){0==a.errorCode&&b.set({away:!c})},!0)}})})(Mibew,_);

View File

@ -0,0 +1,9 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(a,c){var b=[],f=a.Models.QueuedThread=a.Models.Thread.extend({defaults:c.extend({},a.Models.Thread.prototype.defaults,{controls:null,userName:"",userIp:"",remote:"",userAgent:"",agentName:"",canOpen:!1,canView:!1,canBan:!1,ban:!1,totalTime:0,waitingTime:0,firstMessage:null}),initialize:function(){for(var e=[],b=f.getControls(),d=0,c=b.length;d<c;d++)e.push(new b[d]({thread:this}));this.set({controls:new a.Collections.Controls(e)})}},{addControl:function(a){b.push(a)},getControls:function(){return b}})})(Mibew,
_);

View File

@ -0,0 +1,8 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(b){b.Models.StatusPanel=b.Models.Base.extend({defaults:{message:""},setStatus:function(a){this.set({message:a})},changeAgentStatus:function(){var a=b.Objects.Models.agent;a.get("away")?a.available():a.away()}})})(Mibew);

View File

@ -0,0 +1,8 @@
/*
This file is part of Mibew Messenger project.
http://mibew.org
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
(function(a,c){var b=[],f=a.Models.Visitor=a.Models.User.extend({defaults:c.extend({},a.Models.User.prototype.defaults,{controls:null,userName:"",userIp:"",remote:"",userAgent:"",firstTime:0,lastTime:0,invitations:0,chats:0,invitationInfo:!1}),initialize:function(){for(var e=[],b=f.getControls(),d=0,c=b.length;d<c;d++)e.push(new b[d]({visitor:this}));this.set({controls:new a.Collections.Controls(e)})}},{addControl:function(a){b.push(a)},getControls:function(){return b}})})(Mibew,_);

File diff suppressed because one or more lines are too long

View File

@ -1,556 +0,0 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
Ajax.PeriodicalUpdater = Class.create();
Class.inherit( Ajax.PeriodicalUpdater, Ajax.Base, {
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.frequency = (this._options.frequency || 2);
this.updater = {};
this.update();
},
handleException: function(_request, ex) {
if( this._options.handleError )
this._options.handleError("offline, reconnecting");
this.stopUpdate();
this.timer = setTimeout(this.update.bind(this), 1000);
},
handleTimeout: function(_request) {
if( this._options.handleError )
this._options.handleError("timeout, reconnecting");
this.stopUpdate();
this.timer = setTimeout(this.update.bind(this), 1000);
},
stopUpdate: function() {
if( this.updater._options )
this.updater._options.onComplete = undefined;
clearTimeout(this.timer);
},
update: function() {
if( this._options.updateParams )
this._options.parameters = (this._options.updateParams)();
this.updater = new Ajax.Request(this._options.url, this._options);
},
requestComplete: function(presponse) {
try {
var xmlRoot = Ajax.getXml(presponse);
if( xmlRoot ) {
(this._options.updateContent || Ajax.emptyFunction)( xmlRoot );
} else {
if( this._options.handleError )
this._options.handleError("reconnecting");
}
} catch(e) {
}
this.timer = setTimeout(this.update.bind(this), this.frequency * 1000);
}
});
var HtmlGenerationUtils = {
popupLink: function(link, title, wndid, inner, width, height,linkclass) {
return '<a href="'+link+'"'+(linkclass != null ? ' class="'+linkclass+'"' : '')+' target="_blank" title="'+title+'" onclick="this.newWindow = window.open(\''+link+'\', \''+
wndid+'\', \'toolbar=0,scrollbars=0,location=0,status=1,menubar=0,width='+width+',height='+height+',resizable=1\');this.newWindow.focus();this.newWindow.opener=window;return false;">'+
inner+'</a>';
},
generateOneRowTable: function(content) {
return '<table class="inner"><tr>' + content + '</tr></table>';
},
viewOpenCell: function(username,servlet,id,canview,canopen,ban,message,cantakenow,tracked,trackedlink) {
var link = servlet+"?thread="+id;
var gen = '<td>';
if(canopen || canview ) {
gen += HtmlGenerationUtils.popupLink( (cantakenow||!canview) ? link : link+"&viewonly=true", localized[canopen ? 0 : 1], "ImCenter"+id, username, 640, 480, null);
} else {
gen += '<a href="#">' + username + '</a>';
}
gen += '</td>';
if( message != "" ) {
gen += '</tr><tr><td class="firstmessage" colspan="2"><a href="javascript:void(0)" title="'+message+'" onclick="alert(this.title);return false;">';
gen += message.length > 30 ? message.substring(0,30) + '...' : message;
gen += '</a></td>';
}
return HtmlGenerationUtils.generateOneRowTable(gen);
},
viewActionsCell: function(servlet,id,canview,canopen,tracked,trackedlink) {
var link = servlet+"?thread="+id;
var gen = '';
if( canopen ) {
gen += '<td class="icon">';
gen += HtmlGenerationUtils.popupLink( link, localized[0], "ImCenter"+id, '<img src="'+webimRoot+'/images/tbliclspeak.gif" width="15" height="15" border="0" alt="'+localized[0]+'">', 640, 480, null);
gen += '</td>';
}
if( canview ) {
gen += '<td class="icon">';
gen += HtmlGenerationUtils.popupLink( link+"&viewonly=true", localized[1], "ImCenter"+id, '<img src="'+webimRoot+'/images/tbliclread.gif" width="15" height="15" border="0" alt="'+localized[1]+'">', 640, 480, null);
gen += '</td>';
}
if ( tracked ) {
gen += '<td class="icon">';
gen += HtmlGenerationUtils.popupLink( trackedlink+"?thread="+id, localized[6], "ImTracked"+id, '<img src="'+webimRoot+'/images/tblictrack.gif" width="15" height="15" border="0" alt="'+localized[6]+'">', 640, 480, null);
gen += '</td>';
}
return gen;
},
banCell: function(id,banid){
return '<td class="icon">'+
HtmlGenerationUtils.popupLink( webimRoot+'/operator/ban.php?'+(banid ? 'id='+banid : 'thread='+id), localized[2], "ban"+id, '<img src="'+webimRoot+'/images/ban.gif" width="15" height="15" border="0" alt="'+localized[2]+'">', 720, 480, null)+
'</td>';
},
viewVisOpenCell: function(username, inviteservlet, userid, trackedservlet, caninvite) {
var cellsCount = 2;
var gen = '<td>';
if(caninvite) {
gen += HtmlGenerationUtils.popupLink( inviteservlet+"?visitor="+userid, localized[7], "ImCenter"+userid, username, 640, 480, null);
} else {
gen += '<a href="#">' + username + '</a>';
}
gen += '</td>';
gen += '<td class="icon">';
var tr_link = HtmlGenerationUtils.popupLink( trackedservlet+"?visitor="+userid, localized[6], "ImTracked"+userid, '<img src="'+webimRoot+'/images/tblictrack.gif" width="15" height="15" border="0" alt="'+localized[6]+'">', 640, 480, null);
tr_link = tr_link.replace("scrollbars=0","scrollbars=1");
gen += tr_link;
gen += '</td>';
return HtmlGenerationUtils.generateOneRowTable(gen);
}
};
Ajax.ThreadListUpdater = Class.create();
Class.inherit( Ajax.ThreadListUpdater, Ajax.Base, {
initialize: function(_options) {
this.setOptions(_options);
this._options.updateParams = this.updateParams.bind(this);
this._options.handleError = this.handleError.bind(this);
this._options.updateContent = this.updateContent.bind(this);
this._options.lastrevision = 0;
this.threadTimers = new Object();
this.delta = 0;
this.t = this._options.table;
this.t2 = this._options.visitors_table;
this.periodicalUpdater = new Ajax.PeriodicalUpdater(this._options);
this.old_visitors = new Object();
this.visitors = new Object();
this.visitorTimers = new Object();
},
updateParams: function() {
return "since=" + this._options.lastrevision + "&status=" + this._options.istatus + (this._options.showonline ? "&showonline=1" : "") + (this._options.showvisitors ? "&showvisitors=1" : "");
},
setStatus: function(msg) {
this._options.status.innerHTML = msg;
},
handleError: function(s) {
this.setStatus( s );
},
updateThread: function(node) {
var id, stateid, vstate, canview = false, canopen = false, canban = false, ban = null, banid = null;
for( var i = 0; i < node.attributes.length; i++ ) {
var attr = node.attributes[i];
if( attr.nodeName == "id" )
id = attr.nodeValue;
else if( attr.nodeName == "stateid" )
stateid = attr.nodeValue;
else if( attr.nodeName == "state" )
vstate = attr.nodeValue;
else if( attr.nodeName == "canopen" )
canopen = true;
else if( attr.nodeName == "canview" )
canview = true;
else if( attr.nodeName == "canban" )
canban = true;
else if( attr.nodeName == "ban" )
ban = attr.nodeValue;
else if( attr.nodeName == "banid" )
banid = attr.nodeValue;
}
function setcell(_table, row,id,pcontent) {
var cell = CommonUtils.getCell( id, row, _table );
if( cell )
cell.innerHTML = pcontent;
}
var row = CommonUtils.getRow("thr"+id, this.t);
if( stateid == "closed" ) {
if( row ) {
this.t.deleteRow(row.rowIndex);
}
this.threadTimers[id] = null;
return;
}
var vname = NodeUtils.getNodeValue(node,"name");
var actions = HtmlGenerationUtils.viewActionsCell(this._options.agentservl,id,canview,canopen,this._options.showvisitors, this._options.trackedservl);
var vaddr = NodeUtils.getNodeValue(node,"addr");
var vtime = NodeUtils.getNodeValue(node,"time");
var agent = NodeUtils.getNodeValue(node,"agent");
var modified = NodeUtils.getNodeValue(node,"modified");
var message = NodeUtils.getNodeValue(node,"message");
var etc = '<td>'+NodeUtils.getNodeValue(node,"useragent")+'</td>';
if(ban != null) {
etc = '<td>'+NodeUtils.getNodeValue(node,"reason")+'</td>';
}
if(canban) {
actions += HtmlGenerationUtils.banCell(id,banid);
}
actions = HtmlGenerationUtils.generateOneRowTable(actions);
etc = HtmlGenerationUtils.generateOneRowTable(etc);
var startRow = CommonUtils.getRow("t"+stateid, this.t);
var endRow = CommonUtils.getRow("t"+stateid+"end", this.t);
if( row != null && (row.rowIndex <= startRow.rowIndex || row.rowIndex >= endRow.rowIndex ) ) {
this.t.deleteRow(row.rowIndex);
this.threadTimers[id] = null;
row = null;
}
if( row == null ) {
row = this.t.insertRow(startRow.rowIndex+1);
row.className = (ban == "blocked" && stateid != "chat") ? "ban" : "in"+stateid;
row.id = "thr"+id;
this.threadTimers[id] = new Array(vtime,modified,stateid);
CommonUtils.insertCell(row, "name", "visitor", null, null, HtmlGenerationUtils.viewOpenCell(vname,this._options.agentservl,id,canview,canopen,ban,message,stateid!='chat',this._options.showvisitors, this._options.trackedservl));
CommonUtils.insertCell(row, "actions", "visitor", "center", null, actions);
CommonUtils.insertCell(row, "contid", "visitor", "center", null, vaddr );
CommonUtils.insertCell(row, "state", "visitor", "center", null, vstate );
CommonUtils.insertCell(row, "op", "visitor", "center", null, agent );
CommonUtils.insertCell(row, "time", "visitor", "center", null, this.getTimeSince(vtime) );
CommonUtils.insertCell(row, "wait", "visitor", "center", null, (stateid!='chat' ? this.getTimeSince(modified) : '-') );
CommonUtils.insertCell(row, "etc", "visitor", "center", null, etc );
if( stateid == 'wait' || stateid == 'prio' )
return true;
} else {
this.threadTimers[id] = new Array(vtime,modified,stateid);
row.className = (ban == "blocked" && stateid != "chat") ? "ban" : "in"+stateid;
setcell(this.t, row,"name",HtmlGenerationUtils.viewOpenCell(vname,this._options.agentservl,id,canview,canopen,ban,message,stateid!='chat',this._options.showvisitors, this._options.trackedservl));
setcell(this.t, row, "actions", actions);
setcell(this.t, row,"contid",vaddr);
setcell(this.t, row,"state",vstate);
setcell(this.t, row,"op",agent);
setcell(this.t, row,"time",this.getTimeSince(vtime));
setcell(this.t, row,"wait",(stateid!='chat' ? this.getTimeSince(modified) : '-'));
setcell(this.t, row,"etc",etc);
}
return false;
},
updateQueueMessages: function() {
function queueNotEmpty(t,id) {
var startRow = $(id);
var endRow = $(id+"end");
if( startRow == null || endRow == null ) {
return false;
}
return startRow.rowIndex+1 < endRow.rowIndex;
}
var _status = $("statustd");
if( _status) {
var notempty = queueNotEmpty(this.t, "twait") || queueNotEmpty(this.t, "tprio") || queueNotEmpty(this.t, "tchat");
_status.innerHTML = notempty ? "" : this._options.noclients;
_status.height = notempty ? 5 : 30;
}
},
getTimeSince: function(srvtime) {
var secs = Math.floor(((new Date()).getTime()-srvtime-this.delta)/1000);
var minutes = Math.floor(secs/60);
var prefix = "";
secs = secs % 60;
if( secs < 10 )
secs = "0" + secs;
if( minutes >= 60 ) {
var hours = Math.floor(minutes/60);
minutes = minutes % 60;
if( minutes < 10 )
minutes = "0" + minutes;
prefix = hours + ":";
}
return prefix + minutes+":"+secs;
},
updateTimers: function() {
for (var i in this.threadTimers) {
if (this.threadTimers[i] != null) {
var value = this.threadTimers[i];
var row = CommonUtils.getRow("thr"+i, this.t);
if( row != null ) {
function setcell(_table, row,id,pcontent) {
var cell = CommonUtils.getCell( id, row, _table );
if( cell )
cell.innerHTML = pcontent;
}
setcell(this.t, row,"time",this.getTimeSince(value[0]));
setcell(this.t, row,"wait",(value[2]!='chat' ? this.getTimeSince(value[1]) : '-'));
}
}
}
},
updateThreads: function(root) {
var newAdded = false;
var _time = NodeUtils.getAttrValue(root, "time");
var _revision = NodeUtils.getAttrValue(root, "revision" );
if( _time )
this.delta = (new Date()).getTime() - _time;
if( _revision )
this._options.lastrevision = _revision;
for( var i = 0; i < root.childNodes.length; i++ ) {
var node = root.childNodes[i];
if( node.tagName == 'thread' )
if( this.updateThread(node) )
newAdded = true;
}
this.updateQueueMessages();
this.updateTimers();
this.setStatus(this._options.istatus ? localized[8] : localized[9]);
if( newAdded ) {
playSound(webimRoot+'/sounds/new_user.wav');
window.focus();
if(updaterOptions.showpopup) {
alert(localized[5]);
}
}
},
updateOperators: function(root) {
var div = $('onlineoperators');
if (!div)
return;
var names = [];
for( var i = 0; i < root.childNodes.length; i++ ) {
var node = root.childNodes[i];
if(node.tagName != 'operator')
continue;
var name = NodeUtils.getAttrValue(node, 'name');
var isAway = NodeUtils.getAttrValue(node, 'away') != null;
names[names.length] =
'<img src="'+webimRoot+'/images/op'+(isAway ? 'away' : 'online')+
'.gif" width="12" height="12" border="0" alt="'+localized[1]+'"> '+ name;
}
div.innerHTML = names.join(', ');
},
updateVisitorsTimers: function() {
for (var i in this.visitorTimers) {
if (this.visitorTimers[i] != null) {
var value = this.visitorTimers[i];
var row = CommonUtils.getRow("vis"+i, this.t2);
if( row != null ) {
function setcell(_table, row,id,pcontent) {
var cell = CommonUtils.getCell( id, row, _table );
if( cell )
cell.innerHTML = pcontent;
}
setcell(this.t2, row,"time",this.getTimeSince(value[0]));
setcell(this.t2, row,"modified",this.getTimeSince(value[1]));
if (value[2] != null)
setcell(this.t2, row,"invitationtime",this.getTimeSince(value[2]));
}
}
}
},
updateVisitor: function(node) {
var id, invited = false;
for( var i = 0; i < node.attributes.length; i++ ) {
var attr = node.attributes[i];
if( attr.nodeName == "id" )
id = attr.nodeValue;
}
function setcell(_table, row,id,pcontent) {
var cell = CommonUtils.getCell( id, row, _table );
if( cell )
cell.innerHTML = pcontent;
}
var addr = NodeUtils.getNodeValue(node,"addr");
var username = NodeUtils.getNodeValue(node,"username");
var useragent = NodeUtils.getNodeValue(node,"useragent");
var time = NodeUtils.getNodeValue(node,"time");
var modified = NodeUtils.getNodeValue(node,"modified");
var invitations = NodeUtils.getNodeValue(node,"invitations");
var chats = NodeUtils.getNodeValue(node,"chats");
var operator = null;
var invitationtime = null;
var invitation = node.getElementsByTagName("invitation")[0];
for( var i = 0; i < invitation.childNodes.length; i++ ) {
var childnode = invitation.childNodes[i];
if( childnode.tagName == 'operator' ) {
operator = childnode.firstChild.nodeValue;
}
else if ( childnode.tagName == 'invitationtime' ) {
invitationtime = childnode.firstChild.nodeValue;
}
}
var state = (operator == null) ? 'free' : 'invited';
var row = CommonUtils.getRow("vis"+id, this.t2);
var startRow = CommonUtils.getRow("vis" + state, this.t2);
var endRow = CommonUtils.getRow("vis" + state + "end", this.t2);
if( row != null && (row.rowIndex <= startRow.rowIndex || row.rowIndex >= endRow.rowIndex ) ) {
this.t2.deleteRow(row.rowIndex);
this.visitorTimers[id] = null;
row = null;
}
if (row == null) {
row = this.t2.insertRow(startRow.rowIndex+1);
row.id = "vis"+id;
this.visitorTimers[id] = new Array(time, modified, invitationtime);
CommonUtils.insertCell(row, "username", "visitor", null, null, HtmlGenerationUtils.viewVisOpenCell(username, this._options.inviteservl, id, this._options.trackedservl, operator==null));
CommonUtils.insertCell(row, "addr", "visitor", "center", null, addr);
CommonUtils.insertCell(row, "time", "visitor", "center", null, this.getTimeSince(time) );
CommonUtils.insertCell(row, "modified", "visitor", "center", null, this.getTimeSince(modified) );
CommonUtils.insertCell(row, "operator", "visitor", "center", null, (operator != null) ? operator : '-');
CommonUtils.insertCell(row, "invitationtime", "visitor", "center", null, (operator != null ? this.getTimeSince(invitationtime) : '-') );
CommonUtils.insertCell(row, "invitations", "visitor", "center", null, invitations + ' / ' + chats);
CommonUtils.insertCell(row, "useragent", "visitor", "center", null, useragent);
}
else {
this.visitorTimers[id] = new Array(time, modified, invitationtime);
setcell(this.t2, row, "username",HtmlGenerationUtils.viewVisOpenCell(username, this._options.inviteservl, id, this._options.trackedservl, operator==null));
setcell(this.t2, row, "addr", addr);
setcell(this.t2, row, "operator", (operator != null) ? operator : '-');
setcell(this.t2, row, "time", this.getTimeSince(time) );
setcell(this.t2, row, "modified", this.getTimeSince(modified) );
setcell(this.t2, row, "invitationtime", (operator != null ? this.getTimeSince(invitationtime) : '-') );
setcell(this.t2, row, "invitations", invitations + ' / ' + chats);
setcell(this.t2, row, "useragent", useragent);
}
this.visitors[id] = 1;
return false;
},
removeOldVisitors: function() {
for (id in this.old_visitors) {
if (this.visitors[id] === undefined) {
var row = CommonUtils.getRow("vis"+id, this.t2);
if( row ) {
this.t2.deleteRow(row.rowIndex);
}
this.visitorTimers[id] = null;
}
}
},
updateVisitorsList: function(visitors) {
var _status = $("visstatustd");
if( _status) {
_status.innerHTML = (visitors > 0) ? "" : this._options.novisitors;
_status.height = (visitors > 0) ? 5 : 30;
}
},
updateVisitors: function(root) {
this.old_visitors = this.visitors;
this.visitors = new Object();
var visitors_cnt = 0;
for( var i = 0; i < root.childNodes.length; i++ ) {
var node = root.childNodes[i];
if( node.tagName == 'visitor' ) {
visitors_cnt++;
this.updateVisitor(node);
}
}
this.updateVisitorsTimers();
this.removeOldVisitors();
this.updateVisitorsList(visitors_cnt);
},
updateContent: function(root) {
if( root.tagName == 'update' ) {
for( var i = 0; i < root.childNodes.length; i++ ) {
var node = root.childNodes[i];
if (node.tagName == 'threads') {
this.updateThreads(node);
} else if (node.tagName == 'operators') {
this.updateOperators(node);
} else if (node.tagName == 'visitors') {
this.updateVisitors(node);
}
}
} else if( root.tagName == 'error' ) {
this.setStatus(NodeUtils.getNodeValue(root,"descr") );
} else {
this.setStatus( "reconnecting" );
}
}
});
function togglemenu() {
if($("sidebar") && $("wcontent") && $("togglemenu")) {
if($("wcontent").className == "contentnomenu") {
$("sidebar").style.display = "block";
$("wcontent").className = "contentinner";
$("togglemenu").innerHTML = localized[4];
} else {
$("sidebar").style.display = "none";
$("wcontent").className = "contentnomenu";
$("togglemenu").innerHTML = localized[3];
}
}
}
var webimRoot = "";
Behaviour.register({
'#togglemenu' : function(el) {
el.onclick = function() {
togglemenu();
};
}
});
EventHelper.register(window, 'onload', function(){
webimRoot = updaterOptions.wroot;
new Ajax.ThreadListUpdater(({table:$("threadlist"),status:$("connstatus"),istatus:0,visitors_table:$("visitorslist")}).extend(updaterOptions || {}));
if(!updaterOptions.havemenu) {
togglemenu();
}
});

View File

@ -0,0 +1,104 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, Backbone, _){
// Create application instance
var App = new Backbone.Marionette.Application();
// Define regions
App.addRegions({
agentsRegion: '#agents-region',
statusPanelRegion: '#status-panel-region',
threadsRegion: '#threads-region',
visitorsRegion: '#visitors-region',
soundRegion: '#sound-region'
});
// Initialize application
App.addInitializer(function(options){
// Create some shortcuts
var objs = Mibew.Objects;
var models = Mibew.Objects.Models;
var colls = Mibew.Objects.Collections;
// Initialize Server, Thread and User
objs.server = new Mibew.Server(_.extend(
{'interactionType': MibewAPIUsersInteraction},
options.server
));
// Initialize Page
models.page = new Mibew.Models.Page(options.page);
// Initialize Agent
models.agent = new Mibew.Models.Agent(options.agent);
// Initialize threads collection
colls.threads = new Mibew.Collections.Threads();
App.threadsRegion.show(new Mibew.Views.ThreadsCollection({
collection: colls.threads
}));
// Initialize visitors collection
if (options.page.showOnlineOperators) {
colls.visitors = new Mibew.Collections.Visitors();
App.visitorsRegion.show(new Mibew.Views.VisitorsCollection({
collection: colls.visitors
}));
}
// Initialize status panel
models.statusPanel = new Mibew.Models.StatusPanel();
App.statusPanelRegion.show(new Mibew.Views.StatusPanel({
model: models.statusPanel
}));
// Initialize agents collection and show it
if (options.page.showOnlineOperators) {
colls.agents = new Mibew.Collections.Agents();
App.agentsRegion.show(new Mibew.Views.AgentsCollection({
collection: colls.agents
}));
}
// Initialize sounds
models.sound = new Mibew.Models.Sound();
App.soundRegion.show(new Mibew.Views.Sound({
model: models.sound
}));
// Periodically call update function at the server side
objs.server.callFunctionsPeriodically(
function() {
// Build functions list
return [
{
"function": "update",
"arguments": {
"return": {},
"references": {},
"agentId": models.agent.id
}
}
];
},
function(args) {}
);
});
App.on('start', function() {
// Run Server updater
Mibew.Objects.server.runUpdater();
});
Mibew.Application = App;
})(Mibew, Backbone, _);

View File

@ -0,0 +1,66 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew) {
/**
* @class Represents online agents bar
*/
Mibew.Views.AgentsCollection = Mibew.Views.CollectionBase.extend(
/** @lends Mibew.Views.AgentsCollection.prototype */
{
/**
* Default item view constructor.
* @type Function
*/
itemView: Mibew.Views.Agent,
/**
* Class name for view's DOM element
* @type String
*/
className: 'agents-collection',
/**
* Map collection events to the view methods
* @type Object
*/
collectionEvents: {
'sort add remove reset': 'render'
},
/**
* View initializer
*/
initialize: function() {
// Register events
this.on('itemview:before:render', this.updateIndexes, this);
},
/**
* Update 'isModelFirst' and 'isModelLast' child views fields on
* collection 'sort', 'add', 'remove' and 'reset' events.Indexies
*/
updateIndexes: function(childView) {
// Create some shortcuts
var collection = this.collection;
var model = childView.model;
if (model) {
// Update isModelFirst and isModelLast properties
childView.isModelFirst = (collection.indexOf(model) == 0);
childView.isModelLast = (
collection.indexOf(model) == (collection.length - 1)
);
}
}
}
);
})(Mibew);

View File

@ -0,0 +1,244 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, Backbone, Handlebars, _) {
/**
* @class Represents threads list
*/
Mibew.Views.ThreadsCollection = Backbone.Marionette.CompositeView.extend(
/** @lends Mibew.Views.ThreadsCollection.prototype */
{
template: Handlebars.templates.threads_collection,
/**
* Default item view constructor.
* @type Function
*/
itemView: Mibew.Views.QueuedThread,
/**
* DOM element for collection items
* @type String
*/
itemViewContainer: '#threads-container',
/**
* Empty view constructor.
* @type Function
*/
emptyView: Mibew.Views.NoThreads,
/**
* Class name for view's DOM element
* @type String
*/
className: 'threads-collection',
/**
* Map collection events to the view methods
* @type Object
*/
collectionEvents: {
'sort': 'renderCollection',
'sort:field': 'createSortField',
'add': 'threadAdded'
},
/**
* Pass some options to item view
* @returns {Object} Options object
*/
itemViewOptions: function(model) {
var page = Mibew.Objects.Models.page;
return {
tagName: page.get('threadTag'),
collection: model.get('controls')
}
},
/**
* View initializer.
* @todo Do something with timer. Do not render whole view!
*/
initialize: function() {
// Rerender view to keep timers in items views working
window.setInterval(_.bind(this.renderCollection, this), 2 * 1000);
// Register events
this.on('itemview:before:render', this.updateStyles, this);
},
/**
* Update thread DOM element classes depending on thread params.
* @param {Mibew.Views.QueuedThread} childView View instance for
* thread in the queue
*/
updateStyles: function(childView) {
// Create some shortcuts
var collection = this.collection;
var thread = childView.model;
var self = this;
if (thread.id) {
var queueCode = this.getQueueCode(thread);
var isLast = false, isFirst = false;
// Filter collection by queue type
var filteredThreads = collection.filter(function(model) {
return self.getQueueCode(model) == queueCode;
});
// Get isFirst and isLast flags
if (filteredThreads.length > 0) {
isFirst = (filteredThreads[0].id == thread.id);
isLast = (
filteredThreads[filteredThreads.length-1].id == thread.id
);
}
// Remove all old styles
if (childView.lastStyles.length > 0) {
for(var i = 0, l = childView.lastStyles.length; i < l; i++) {
childView.$el.removeClass(childView.lastStyles[i]);
}
childView.lastStyles = [];
}
// Create new style name
var style = ((queueCode != this.QUEUE_BAN)?'in':'')
+ this.queueCodeToString(queueCode);
// Store new styles
childView.lastStyles.push(style);
if (isFirst) {
childView.lastStyles.push(style + "-first");
}
if (isLast) {
childView.lastStyles.push(style + "-last");
}
// Add styles names to DOM element
for(var i = 0, l = childView.lastStyles.length; i < l; i++) {
childView.$el.addClass(childView.lastStyles[i]);
}
}
},
/**
* This is the 'sort:field' event handler.
* Make threads sort by queue code and waiting time.
* @param {Mibew.Models.QueuedThread} thread Thread model
* @param {Object} sort Sorting object that contains property
* 'field' - a string by which threads will be sorted
*/
createSortField: function(thread, sort) {
var queueCode = this.getQueueCode(thread) || 'Z';
sort.field = queueCode.toString()
+ '_'
+ thread.get('waitingTime').toString()
},
/**
* Play sound when new thread add to collection
*/
threadAdded: function() {
// Build sound path
var path = Mibew.Objects.Models.page.get('webimRoot');
if (path) {
path += '/sounds/new_user.wav';
// Play sound
Mibew.Objects.Models.sound.play(path);
}
},
/**
* Calculate queue code for thread
* @returns {Boolean|Number} Queue code or false if code is unknown
*/
getQueueCode: function(thread) {
var state = thread.get('state');
if (thread.get('ban') != false
&& state != thread.STATE_CHATTING) {
return this.QUEUE_BAN;
}
if (state == thread.STATE_QUEUE
|| state == thread.STATE_LOADING) {
return this.QUEUE_WAITING;
}
if (state == thread.STATE_CLOSED
|| state == thread.STATE_LEFT) {
return this.QUEUE_CLOSED;
}
if (state == thread.STATE_WAITING) {
return this.QUEUE_PRIO;
}
if (state == thread.STATE_CHATTING) {
return this.QUEUE_CHATTING;
}
return false;
},
/**
* Convert numeric queue code to string one
* @returns {String}
*/
queueCodeToString: function(code) {
if (code == this.QUEUE_PRIO) {
return "prio";
}
if (code == this.QUEUE_WAITING) {
return "wait";
}
if (code == this.QUEUE_CHATTING) {
return "chat";
}
if (code == this.QUEUE_BAN) {
return "ban";
}
if (code == this.QUEUE_CLOSED) {
return "closed";
}
return "";
},
/** Queues codes */
/**
* Priority queue. Includes threads with STATE_WAITING state
*/
QUEUE_PRIO: 1,
/**
* Waiting queue. Includes threads with STATE_LOADING and
* STATE_WAITING states.
*/
QUEUE_WAITING: 2,
/**
* Chatting queue. Includes threads with STATE_CHATTING state
*/
QUEUE_CHATTING: 3,
/**
* Ban queue. Includes all blocked threads.
*/
QUEUE_BAN: 4,
/**
* Closed queue. Includes all threads with STATE_CLOSED and
* STATE_LEFT states
*/
QUEUE_CLOSED: 5
/** End of queues codes */
}
);
})(Mibew, Backbone, Handlebars, _);

View File

@ -0,0 +1,76 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, Backbone, Handlebars, _) {
/**
* @class Represents visitors list
*/
Mibew.Views.VisitorsCollection = Backbone.Marionette.CompositeView.extend(
/** @lends Mibew.Views.VisitorsCollection.prototype */
{
template: Handlebars.templates.visitors_collection,
/**
* Default item view constructor.
* @type Function
*/
itemView: Mibew.Views.Visitor,
/**
* DOM element for collection items
* @type String
*/
itemViewContainer: '#visitors-container',
/**
* Empty view constructor.
* @type Function
*/
emptyView: Mibew.Views.NoVisitors,
/**
* Class name for view's DOM element
* @type String
*/
className: 'visitors-collection',
/**
* Map collection events to the view methods
* @type Object
*/
collectionEvents: {
'sort': 'renderCollection'
},
/**
* Pass some options to item view
* @returns {Object} Options object
*/
itemViewOptions: function(model) {
var page = Mibew.Objects.Models.page;
return {
tagName: page.get('visitorTag'),
collection: model.get('controls')
}
},
/**
* View initializer.
* @todo Do something with timer. Do not render whole view!
*/
initialize: function() {
// Rerender view to keep timers in items views working
window.setInterval(_.bind(this.renderCollection, this), 2 * 1000);
// Register events
this.on('itemview:before:render', this.updateStyles, this);
}
}
);
})(Mibew, Backbone, Handlebars, _);

View File

@ -0,0 +1,69 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, Backbone, _){
/**
* @class Represents collection of agents
*/
Mibew.Collections.Agents = Backbone.Collection.extend(
/** @lends Mibew.Collections.Agents.prototype */
{
/**
* Model type of the collection items
*/
model: Mibew.Models.Agent,
/**
* Use for sort controls in collection
* @param {Backbone.Model} model Agent model
*/
comparator: function(model) {
return model.get('name');
},
/**
* Collection initializer
*/
initialize: function() {
// Register some shortcuts
var agent = Mibew.Objects.Models.agent;
// Call updateOperators periodically at the server
Mibew.Objects.server.callFunctionsPeriodically(
function(){
return [
{
'function': 'updateOperators',
'arguments': {
'agentId': agent.id,
'return': {
'operators': 'operators'
},
'references': {}
}
}
];
},
_.bind(this.updateOperators, this)
);
},
/**
* Update available agents.
* @param {Object} args Arguments from the server
*/
updateOperators: function(args) {
this.update(args.operators);
}
}
);
})(Mibew, Backbone, _);

View File

@ -0,0 +1,156 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, Backbone, _){
/**
* @class Represents threads collection
*/
Mibew.Collections.Threads = Backbone.Collection.extend(
/** @lends Mibew.Collections.Threads.prototype */
{
/**
* Model type of the collection items
* @type Function
*/
model: Mibew.Models.QueuedThread,
/**
* Collection initializer
*/
initialize: function() {
// Initialize fields and methods
/**
* Last threads revision number. Prevent transfering not
* modified threads.
* @type Number
* @fieldOf Mibew.Collections.Threads
*/
this.revision = 0;
// Register some shortcuts
var self = this;
var agent = Mibew.Objects.Models.agent;
// Call updateThreads periodically at the server
Mibew.Objects.server.callFunctionsPeriodically(
function(){
return [
{
'function': 'currentTime',
'arguments': {
'agentId': agent.id,
'return': {
'time': 'currentTime'
},
'references': {}
}
},
{
'function': 'updateThreads',
'arguments': {
'agentId': agent.id,
'revision': self.revision,
'return': {
'threads': 'threads',
'lastRevision': 'lastRevision'
},
'references': {}
}
}
];
},
_.bind(this.updateThreads, this)
);
},
/**
* Use for sort threads in collection.
* By default threads sort by state and waiting time.
* Triggers 'sort:field' event after sort field generated.
* @param {Mibew.Models.QueuedThread} thread Thread model
*/
comparator: function(thread) {
// Create default sort field
var sort = {
field: thread.get('waitingTime').toString()
}
// Trigger event to provide an ability to change sorting order
this.trigger('sort:field', thread, sort);
// Return sort field
return sort.field;
},
/**
* Update threads list.
* Trigger 'before:update:threads' event and pass array of raw
* threads data as argument to event handler.
* Also trigger 'after:update:threads' event.
* @param {Object} args Arguments returned from server
*/
updateThreads: function(args) {
if (args.errorCode == 0) {
if (args.threads.length > 0) {
// Fix time difference between server and client
var delta;
if (args.currentTime) {
delta = Math.round((new Date()).getTime() / 1000)
- args.currentTime;
} else {
delta = 0;
}
for(var i = 0, l = args.threads.length; i < l; i++) {
args.threads[i].totalTime
= parseInt(args.threads[i].totalTime) + delta;
args.threads[i].waitingTime
= parseInt(args.threads[i].waitingTime) + delta;
}
// Trigger event. Event handlers can change threads info
this.trigger('before:update:threads', args.threads);
// Create shortcuts for thread states
var stateClosed = Mibew.Models.Thread.prototype.STATE_CLOSED;
var stateLeft = Mibew.Models.Thread.prototype.STATE_LEFT;
// Define empty array for threads that should be remove
var remove = [];
// Update threads list
this.update(args.threads, {remove: false, sort: false});
// Get closed and left thread. Collect them into
// remove array
remove = this.filter(function(thread) {
return (thread.get('state') == stateClosed
|| thread.get('state') == stateLeft);
});
// Remove closed and left threads
if (remove.length > 0) {
this.remove(remove);
}
// Sort residual collection
this.sort();
// Trigger event
this.trigger('after:update:threads');
}
this.revision = args.lastRevision;
}
}
}
);
})(Mibew, Backbone, _);

View File

@ -0,0 +1,117 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, Backbone, _){
/**
* @class Represents visitors collection
*/
Mibew.Collections.Visitors = Backbone.Collection.extend(
/** @lends Mibew.Collections.Visitors.prototype */
{
/**
* Model type of the collection items
* @type Function
*/
model: Mibew.Models.Visitor,
/**
* Collection initializer
*/
initialize: function() {
// Register some shortcuts
var agent = Mibew.Objects.Models.agent;
// Call updateThreads periodically at the server
Mibew.Objects.server.callFunctionsPeriodically(
function(){
return [
{
'function': 'currentTime',
'arguments': {
'agentId': agent.id,
'return': {
'time': 'currentTime'
},
'references': {}
}
},
{
'function': 'updateVisitors',
'arguments': {
'agentId': agent.id,
'return': {
'visitors': 'visitors'
},
'references': {}
}
}
];
},
_.bind(this.updateVisitors, this)
);
},
/**
* Use for sort visitors in collection.
* By default visitors sort by firstTime field.
* Triggers 'sort:field' event after sort field generated.
* @param {Mibew.Models.Visitor} visitor Visitor model
*/
comparator: function(visitor) {
// Create default sort field
var sort = {
field: visitor.get('firstTime').toString()
}
// Trigger event to provide an ability to change sorting order
this.trigger('sort:field', visitor, sort);
// Return sort field
return sort.field;
},
/**
* Update visitors list.
* Trigger 'before:update:visitors' event and pass array of raw
* visitors data as argument to event handler.
* Also trigger 'after:update:visitors' event.
* @param {Object} args Arguments returned from server
*/
updateVisitors: function(args) {
if (args.errorCode == 0) {
// Fix time difference between server and client
var delta;
if (args.currentTime) {
delta = Math.round((new Date()).getTime() / 1000)
- args.currentTime;
} else {
delta = 0;
}
for(var i = 0, l = args.visitors.length; i < l; i++) {
args.visitors[i].lastTime = parseInt(args.visitors[i].lastTime) + delta;
args.visitors[i].firstTime = parseInt(args.visitors[i].firstTime) + delta;
}
// Trigger event. Event handlers can change visitors info
this.trigger('before:update:visitors', args.visitors);
// Update collection
this.update(args.visitors);
// Trigger event
this.trigger('after:update:visitors');
}
}
}
);
})(Mibew, Backbone, _);

View File

@ -0,0 +1,33 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Handlebars){
/**
* Register 'formatTimeToNow' Handlebars helper.
*
* This helper takes unix timestamp as argument and return difference
* between current timestamp and passed one in "HH:MM:SS" format.
*/
Handlebars.registerHelper('formatTimeSince', function(unixTimestamp){
// Get time diff
var diff = Math.round((new Date()).getTime() / 1000) - unixTimestamp;
// Get time parts
var seconds = diff % 60;
var minutes = Math.floor(diff / 60) % 60;
var hours = Math.floor(diff / (60 * 60));
// Get result parts
var result = [];
if (hours > 0) {
result.push(hours);
}
result.push(minutes < 10 ? '0' + minutes : minutes);
result.push(seconds < 10 ? '0' + seconds : seconds);
// Build result string
return result.join(':');
});
})(Handlebars);

View File

@ -0,0 +1,33 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew){
/**
* @namespace Holds application region constructors
*/
Mibew.Regions = {};
/**
* @namespace Holds popup windows control
*/
Mibew.Popup = {};
/**
* Open new window
* @param {String} link URL address of page to open
* @param {String} id Id of new window
* @param {String} params Window params passed to window.open method
*/
Mibew.Popup.open = function(link, id, params) {
var newWindow = window.open(link, id, params);
newWindow.focus();
newWindow.opener = window;
}
})(Mibew);

View File

@ -0,0 +1,30 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
/**
* Represents User list Window to core interaction type
*
* @constructor
*/
MibewAPIUsersInteraction = function() {
this.obligatoryArguments = {
'*': {
'agentId': null,
'return': {},
'references': {}
},
'result': {
'errorCode': 0
}
};
this.reservedFunctionNames = [
'result'
];
}
MibewAPIUsersInteraction.prototype = new MibewAPIInteraction();

View File

@ -0,0 +1,81 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, Backbone, Handlebars) {
/**
* @class Represents agent view.
*/
Mibew.Views.Agent = Backbone.Marionette.ItemView.extend(
/** @lends Mibew.Views.Agent.prototype */
{
/**
* Template function
* @type Function
*/
template: Handlebars.templates.agent,
/**
* Name of wrapper tag for an agent view
* @type String
*/
tagName: 'span',
/**
* CSS class name for view's DOM element
* @type String
*/
className: 'agent',
/**
* Map model events to the view methods
* @type Object
*/
modelEvents: {
'change': 'render'
},
/**
* View initializer
*/
initialize: function() {
// Initialize fields and methods of the instance
/**
* Indicates if model related to the view is first in collection
* @type Boolean
* @fieldOf Mibew.Views.Agent
*/
this.isModelFirst = false;
/**
* Indicates if model related to the view is last in collection
* @type Boolean
* @fieldOf Mibew.Views.Agent
*/
this.isModelLast = false;
},
/**
* Override Backbone.Marionette.ItemView.serializeData to pass some
* extra fields to template. Add 'isFirst' and 'isLast' values.
* Following additional values available in template:
* - 'isFirst': indicates if model is first in collection
* - 'isLast': indicates if model is last in collection
* @returns {Object} Template data
*/
serializeData: function() {
var data = this.model.toJSON();
data.isFirst = this.isModelFirst;
data.isLast = this.isModelLast;
return data;
}
}
);
})(Mibew, Backbone, Handlebars);

View File

@ -0,0 +1,34 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, Backbone, Handlebars) {
/**
* @class Represents empty thread view.
*/
Mibew.Views.NoThreads = Backbone.Marionette.ItemView.extend(
/** @lends Mibew.Views.NoThreads.prototype */
{
/**
* Template function
* @type Function
*/
template: Handlebars.templates.no_threads,
/**
* View initializer
* @param {Object} options Options object passed from
* {@link Mibew.Views.ThreadsCollection.prototype.itemViewOptions}
*/
initialize: function(options) {
this.tagName = options.tagName;
}
}
);
})(Mibew, Backbone, Handlebars);

View File

@ -0,0 +1,34 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, Backbone, Handlebars) {
/**
* @class Represents empty visitor view.
*/
Mibew.Views.NoVisitors = Backbone.Marionette.ItemView.extend(
/** @lends Mibew.Views.NoVisitors.prototype */
{
/**
* Template function
* @type Function
*/
template: Handlebars.templates.no_visitors,
/**
* View initializer
* @param {Object} options Options object passed from
* {@link Mibew.Views.VisitorsCollection.prototype.itemViewOptions}
*/
initialize: function(options) {
this.tagName = options.tagName;
}
}
);
})(Mibew, Backbone, Handlebars);

View File

@ -0,0 +1,240 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, Handlebars) {
/**
* @class Represents thread view.
*/
Mibew.Views.QueuedThread = Mibew.Views.CompositeBase.extend(
/** @lends Mibew.Views.QueuedThread.prototype */
{
/**
* Template function
* @type Function
*/
template: Handlebars.templates.queued_thread,
/**
* Default item view constructor.
* @type Function
*/
itemView: Mibew.Views.Control,
/**
* DOM element for collection items
* @type String
*/
itemViewContainer: '.thread-controls',
/**
* CSS class name for view's DOM element
* @type String
*/
className: 'thread',
/**
* Map model events to the view methods
* @type Object
*/
modelEvents: {
'change': 'render'
},
/**
* UI events hash.
* Map UI events on the view methods.
* @type Object
*/
events: {
'click .open-dialog': 'openDialog',
'click .view-control': 'viewDialog',
'click .track-control': 'showTrack',
'click .ban-control': 'showBan',
'click .geo-link': 'showGeoInfo',
'click .first-message a': 'showFirstMessage'
},
/**
* View initializer
*/
initialize: function() {
// Initialize fields and methods of the instance
/**
* Contain list of last styles added to the thread DOM element.
* Used by {@link Mibew.Views.ThreadsCollection} view.
* @type Array
* @fieldOf Mibew.Views.Thread
*/
this.lastStyles = [];
},
/**
* Override Backbone.Marionette.ItemView.serializeData to pass some
* extra fields to template.
* Following additional values available in template:
* - 'stateDesc': thread state description
* - 'chatting': indicates if thread have STATE_CHATTING
* - 'tracked': indicates if tracked system is enabled
* - 'firstMessagePreview': first message limited by 30 characters
* @returns {Object} Template data
*/
serializeData: function() {
var thread = this.model
var page = Mibew.Objects.Models.page;
var data = thread.toJSON();
data.stateDesc = this.stateToDesc(thread.get('state'));
data.chatting = (thread.get('state') == thread.STATE_CHATTING);
data.tracked = page.get('showVisitors');
if (data.firstMessage) {
data.firstMessagePreview = data.firstMessage.length > 30
? data.firstMessage.substring(0,30) + '...'
: data.firstMessage
}
return data;
},
/**
* Convert numeric thread state code to string description of a
* state
* @param {Number} state Thread state code
* @returns {String} Description of the thread state
*/
stateToDesc: function(state) {
var l = Mibew.Localization;
if (state == this.model.STATE_QUEUE) {
return l.get('chat.thread.state_wait');
}
if (state == this.model.STATE_WAITING) {
return l.get('chat.thread.state_wait_for_another_agent');
}
if (state == this.model.STATE_CHATTING) {
return l.get('chat.thread.state_chatting_with_agent');
}
if (state == this.model.STATE_CLOSED) {
return l.get('chat.thread.state_closed');
}
if (state == this.model.STATE_LOADING) {
return l.get('chat.thread.state_loading');
}
return "";
},
/**
* Open window with geo information
*/
showGeoInfo: function() {
var ip = this.model.get('userIp');
if (ip) {
var page = Mibew.Objects.Models.page;
var geoLink = page.get('geoLink')
.replace("{ip}", ip);
Mibew.Popup.open(
geoLink,
'ip' + ip,
page.get('geoWindowParams')
);
}
},
/**
* Open chat window in dialog mode
*/
openDialog: function() {
// Create some shortcuts
var thread = this.model;
var viewOnly = (thread.get('state') == thread.STATE_CHATTING)
&& thread.get('canView');
// Show dialog window
this.showDialogWindow(viewOnly);
},
/**
* Open chat window in view mode
*/
viewDialog: function() {
this.showDialogWindow(true);
},
/**
* Open chat window
* @param {Boolean} viewOnly Indicates if chat window should be open
* in view mode
*/
showDialogWindow: function(viewOnly) {
// Create some shortcuts
var thread = this.model;
var threadId = thread.id;
var page = Mibew.Objects.Models.page;
// Open chat window
Mibew.Popup.open(
page.get('agentLink')
+ '?thread='
+ threadId
+ (viewOnly ? '&viewonly=true': ''),
'ImCenter' + threadId,
page.get('chatWindowParams')
);
},
/**
* Open tracked window
*/
showTrack: function() {
// Create some shortcuts
var threadId = this.model.id;
var page = Mibew.Objects.Models.page;
// Open tracked window
Mibew.Popup.open(
page.get('trackedLink')
+ '?thread='
+ threadId,
'ImTracked' + threadId,
page.get('trackedUserWindowParams')
);
},
/**
* Open ban window
*/
showBan: function() {
// Create some shortcuts
var thread = this.model;
var ban = thread.get('ban');
var page = Mibew.Objects.Models.page;
// Open ban window
Mibew.Popup.open(
page.get('banLink')
+ '?'
+ (ban !== false
? 'id='+ban.id
: 'thread='+ thread.id),
'ImBan' + ban.id,
page.get('banWindowParams')
);
},
/**
* Show first message from user to agent
*/
showFirstMessage: function() {
var message = this.model.get('firstMessage');
if (message) {
alert(message);
}
}
}
);
})(Mibew, Handlebars);

View File

@ -0,0 +1,74 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, Backbone, Handlebars) {
/**
* @class Represents status panel view.
*/
Mibew.Views.StatusPanel = Backbone.Marionette.ItemView.extend(
/** @lends Mibew.Views.StatusPanel.prototype */
{
/**
* Template function
* @type Function
*/
template: Handlebars.templates.status_panel,
/**
* Map model events to the view methods
* @type Object
*/
modelEvents: {
'change': 'render'
},
/**
* Shortcuts for ui elements
* @type Object
*/
ui: {
changeStatus: '#change-status'
},
/**
* Map ui events to view methods
* @type Object
*/
events: {
'click #change-status': 'changeAgentStatus'
},
/**
* View initializer
*/
initialize: function() {
Mibew.Objects.Models.agent.on('change', this.render, this);
},
/**
* Changes users status
*/
changeAgentStatus: function() {
this.model.changeAgentStatus();
},
/**
* Override Backbone.Marionette.ItemView.serializeData to pass some
* extra fields to template.
* @returns {Object} Template data
*/
serializeData: function() {
var data = this.model.toJSON();
data.agent = Mibew.Objects.Models.agent.toJSON();
return data;
}
}
);
})(Mibew, Backbone, Handlebars);

View File

@ -0,0 +1,117 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, Handlebars) {
/**
* @class Represents visitor view.
*/
Mibew.Views.Visitor = Mibew.Views.CompositeBase.extend(
/** @lends Mibew.Views.Visitor.prototype */
{
/**
* Template function
* @type Function
*/
template: Handlebars.templates.visitor,
/**
* Default item view constructor.
* @type Function
*/
itemView: Mibew.Views.Control,
/**
* DOM element for collection items
* @type String
*/
itemViewContainer: '.visitor-controls',
/**
* CSS class name for view's DOM element
* @type String
*/
className: 'visitor',
/**
* Map model events to the view methods
* @type Object
*/
modelEvents: {
'change': 'render'
},
/**
* UI events hash.
* Map UI events on the view methods.
* @type Object
*/
events: {
'click .invite-link': 'inviteUser',
'click .geo-link': 'showGeoInfo',
'click .track-control': 'showTrack'
},
/**
* Invite user to chat
*/
inviteUser: function() {
if (! this.model.get('invitationInfo')) {
// Create some shortcuts
var visitorId = this.model.id;
var page = Mibew.Objects.Models.page;
// Open invite window
Mibew.Popup.open(
page.get('inviteLink')
+ '?visitor='
+ visitorId,
'ImCenter' + visitorId,
page.get('inviteWindowParams')
);
}
},
/**
* Open tracked window
*/
showTrack: function() {
// Create some shortcuts
var visitorId = this.model.id;
var page = Mibew.Objects.Models.page;
// Open tracked window
Mibew.Popup.open(
page.get('trackedLink')
+ '?visitor='
+ visitorId,
'ImTracked' + visitorId,
page.get('trackedVisitorWindowParams')
);
},
/**
* Open window with geo information
*/
showGeoInfo: function() {
var ip = this.model.get('userIp');
if (ip) {
var page = Mibew.Objects.Models.page;
var geoLink = page.get('geoLink')
.replace("{ip}", ip);
Mibew.Popup.open(
geoLink,
'ip' + ip,
page.get('geoWindowParams')
);
}
}
}
);
})(Mibew, Handlebars);

View File

@ -0,0 +1,95 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, _){
/**
* @class Represents an agent
*/
Mibew.Models.Agent = Mibew.Models.User.extend(
/** @lends Mibew.Models.Agent.prototype */
{
/**
* A list of default model values.
* Inherits values from Mibew.Models.User
* @type Object
*/
defaults: _.extend(
{},
Mibew.Models.User.prototype.defaults,
{
/**
* Agent id on the server
* @type Number
*/
id: null,
/**
* Indicates that user is agent.
* Left only for compatibility with Mibew.Models.User
* @type Boolean
*/
isAgent: true,
/**
* Indicates if agent away or available at the moment
* @type Boolean
*/
away: false
}
),
/**
* Set user status to 'away'
* This is a shortcut for setAvailability method
*/
away: function() {
this.setAvailability(false);
},
/**
* Set user status to 'available'
* This is a shortcut for setAvailability method
*/
available: function() {
this.setAvailability(true);
},
/**
* Set agent status: 'away' or 'available'
* @param {Boolean} available true set agent's status to 'available'
* and false set agent's status to 'away'
*/
setAvailability: function(available) {
var funcName = available?'available':'away';
var self = this;
Mibew.Objects.server.callFunctions(
[
{
'function': funcName,
'arguments': {
'agentId': this.id,
'references': {},
'return': {}
}
}
],
function(args){
if (args.errorCode == 0) {
self.set({'away': !available});
}
},
true
);
}
}
);
})(Mibew, _);

View File

@ -0,0 +1,154 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, _){
/**
* Holds thread controls constructors
* @type Array
*/
var controlsConstructors = [];
/**
* Prepresent thread in users queue
* @class
*/
var QueuedThread = Mibew.Models.QueuedThread = Mibew.Models.Thread.extend(
/** @lends Mibew.Models.QueuedThread.prototype */
{
/**
* A list of default model values.
* Inherits values from Mibew.Models.Thread
* @type Object
*/
defaults: _.extend(
{},
Mibew.Models.Thread.prototype.defaults,
{
/**
* Collection of thread controls
* @type Mibew.Collections.Controls
*/
controls: null,
/**
* Name of the user
* @type String
*/
userName: '',
/**
* Ip address of the user
* @type String
*/
userIp: '',
/**
* Full remote address returned by web server. Generally
* equals to userIp.
* @type String
*/
remote: '',
/**
* User agent
* @type String
*/
userAgent: '',
/**
* Agent name
* @type String
*/
agentName: '',
/**
* Indicates if agent can open thread
* @type Boolean
*/
canOpen: false,
/**
* Indicates if agent can view thread
* @type Boolean
*/
canView: false,
/**
* Indicates if agent can ban the user
* @type Boolean
*/
canBan: false,
/**
* Contains ban info if user already blocked or boolean
* false otherwise.
* @type Boolean|Object
*/
ban: false,
/**
* Unix timestamp when thread was started
* @type Number
*/
totalTime: 0,
/**
* Unix timestamp when user begin wait for agent
* @type Number
*/
waitingTime: 0,
/**
* First message from user to operator
* @type String
*/
firstMessage: null
}
),
/**
* Model initializer.
* Create controls collection and store it in the model field.
*/
initialize: function() {
var self = this;
var controls = [];
var constructors = QueuedThread.getControls();
for (var i = 0, l = constructors.length; i < l; i++) {
controls.push(new constructors[i]({thread: self}));
}
this.set({
controls: new Mibew.Collections.Controls(controls)
});
}
},
/** @lends Mibew.Models.QueuedThread */
{
/**
* Add thread control constructor
* @static
* @param {Function} Mibew.Models.Control or inherited constructor
*/
addControl: function(control) {
controlsConstructors.push(control)
},
/**
* Returns list of thread controls constructors
* @static
* @returns {Array} List of controls constructors
*/
getControls: function() {
return controlsConstructors;
}
}
);
})(Mibew, _);

View File

@ -0,0 +1,53 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew){
/**
* @class Represents a status panel
*/
Mibew.Models.StatusPanel = Mibew.Models.Base.extend(
/** @lends Mibew.Models.StatusPanel.prototype */
{
/**
* A list of default model values.
* @type Object
*/
defaults: {
/**
* Status message
* @type String
*/
message: ''
},
/**
* Set status message
* @param {String} message New status message
*/
setStatus: function(message) {
this.set({'message': message});
},
/**
* Changes agent status
*/
changeAgentStatus: function() {
var agent = Mibew.Objects.Models.agent;
if (agent.get('away')) {
agent.available();
} else {
agent.away();
}
}
}
);
})(Mibew);

View File

@ -0,0 +1,141 @@
/**
* @preserve This file is part of Mibew Messenger project.
* http://mibew.org
*
* Copyright (c) 2005-2011 Mibew Messenger Community
* License: http://mibew.org/license.php
*/
(function(Mibew, _){
/**
* Holds visitor controls constructors
* @type Array
*/
var controlsConstructors = [];
/**
* @class Represents a visitor.
*/
var Visitor = Mibew.Models.Visitor = Mibew.Models.User.extend(
/** @lends Mibew.Models.Visitor.prototype */
{
/**
* A list of default model values.
* Inherits values from Mibew.Models.User
* @type Object
*/
defaults: _.extend(
{},
Mibew.Models.User.prototype.defaults,
{
/**
* Collection of visitor controls
* @type Mibew.Collections.Controls
*/
controls: null,
/**
* Name of the user
* @type String
*/
userName: '',
/**
* Ip address of the user
* @type String
*/
userIp: '',
/**
* Full remote address returned by web server. Generally
* equals to userIp.
* @type String
*/
remote: '',
/**
* User agent
* @type String
*/
userAgent: '',
/**
* Unix timestamp when visitor was first time observed
* on site
* @type Number
*/
firstTime: 0,
/**
* Unix timestamp when visitor was first time observed
* on site
* @type Number
*/
lastTime: 0,
/**
* Total invitations count
* @type Number
*/
invitations: 0,
/**
* Total chats count with visitor
* @type Number
*/
chats: 0,
/**
* Information about invitation or booean false if there is
* no invitation yet.
*
* Information object contains following keys:
* - 'agentName': name of the agent who invited the visitor
* - 'time': invitation time
* @type Object|Boolean
*/
invitationInfo: false
}
),
/**
* Model initializer.
* Create controls collection and store it in the model field.
*/
initialize: function() {
var self = this;
var controls = [];
var constructors = Visitor.getControls();
for (var i = 0, l = constructors.length; i < l; i++) {
controls.push(new constructors[i]({visitor: self}));
}
this.set({
controls: new Mibew.Collections.Controls(controls)
});
}
},
/** @lends Mibew.Models.Visitor */
{
/**
* Add visitor control constructor
* @static
* @param {Function} Mibew.Models.Control or inherited constructor
*/
addControl: function(control) {
controlsConstructors.push(control)
},
/**
* Returns list of visitor controls constructors
* @static
* @returns {Array} List of controls constructors
*/
getControls: function() {
return controlsConstructors;
}
}
);
})(Mibew, _);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
<span class="agent-status-{{#if away}}away{{else}}online{{/if}} inline-block" title="{{#if away}}{{L10n "pending.status.away"}}{{else}}{{L10n "pending.status.online"}}{{/if}}"></span>{{name}}{{#unless isLast}},{{/unless}}

View File

@ -0,0 +1 @@
<td class="no-threads" colspan="8">{{L10n "clients.no_clients"}}</td>

View File

@ -0,0 +1 @@
<td class="no-visitors" colspan="9">{{L10n "visitors.no_visitors"}}</td>

View File

@ -0,0 +1,27 @@
<td class="visitor">
<div><a href="javascript:void(0);" class="user-name open-dialog" title="{{#if canOpen}}{{L10n "pending.table.speak"}}{{else}}{{L10n "pending.table.view"}}{{/if}}">{{#if ban}}{{L10n "chat.client.spam.prefix"}}&nbsp;{{/if}}{{userName}}</a></div>
{{#if firstMessage}}<div class="first-message"><a href="javascript:void(0);" title="{{firstMessage}}">{{firstMessagePreview}}</a></div>{{/if}}
</td>
<td class="visitor">
<div class="default-thread-controls inline-block">
{{#if canOpen}}
<div class="control open-dialog open-control inline-block" title="{{L10n "pending.table.speak"}}"></div>
{{/if}}
{{#if canView}}
<div class="control view-control inline-block" title="{{L10n "pending.table.view"}}"></div>
{{/if}}
{{#if tracked}}
<div class="control track-control inline-block" title="{{L10n "pending.table.tracked"}}"></div>
{{/if}}
{{#if canBan}}
<div class="control ban-control inline-block" title="{{L10n "pending.table.ban"}}"></div>
{{/if}}
</div>
<div class="thread-controls inline-block"></div>
</td>
<td class="visitor">{{#if userIp}}<a href="javascript:void(0);" class="geo-link" title="GeoLocation">{{remote}}</a>{{else}}{{remote}}{{/if}}</td>
<td class="visitor">{{stateDesc}}</td>
<td class="visitor">{{agentName}}</td>
<td class="visitor">{{formatTimeSince totalTime}}</td>
<td class="visitor">{{#unless chatting}}{{formatTimeSince waitingTime}}{{else}}-{{/unless}}</td>
<td class="visitor">{{#if ban}}{{ban.reason}}{{else}}{{userAgent}}{{/if}}</td>

View File

@ -0,0 +1 @@
<div id="connstatus">{{message}}{{#if agent.away}}{{L10n "pending.status.away"}}{{else}}{{L10n "pending.status.online"}}{{/if}}</div><div id="connlinks"><a href="javascript:void(0);" id="change-status">{{#if agent.away}}{{L10n "pending.status.setonline"}}{{else}}{{L10n "pending.status.setaway"}}{{/if}}</a></div>

View File

@ -0,0 +1,17 @@
<table class="awaiting" border="0">
<thead>
<tr>
<th class="first">{{L10n "pending.table.head.name"}}</th>
<th>{{L10n "pending.table.head.actions"}}</th>
<th>{{L10n "pending.table.head.contactid"}}</th>
<th>{{L10n "pending.table.head.state"}}</th>
<th>{{L10n "pending.table.head.operator"}}</th>
<th>{{L10n "pending.table.head.total"}}</th>
<th>{{L10n "pending.table.head.waittime"}}</th>
<th>{{L10n "pending.table.head.etc"}}</th>
</tr>
</thead>
<tbody id="threads-container">
</tbody>
</table>

View File

@ -0,0 +1,16 @@
<td class="visitor">
{{#unless invitationInfo}}<a href="javascript:void(0);" class="invite-link" title="{{L10n "pending.table.invite"}}">{{userName}}</a>{{else}}{{userName}}{{/unless}}
</td>
<td class="visitor">
<div class="default-visitor-controls inline-block">
<div class="control track-control inline-block" title="{{L10n "pending.table.tracked"}}"></div>
</div>
<div class="visitor-controls inline-block"></div>
</td>
<td class="visitor">{{#if userIp}}<a href="javascript:void(0);" class="geo-link" title="GeoLocation">{{remote}}</a>{{else}}{{remote}}{{/if}}</td>
<td class="visitor">{{formatTimeSince firstTime}}</td>
<td class="visitor">{{formatTimeSince lastTime}}</td>
<td class="visitor">{{#if invitationInfo}}{{invitationInfo.agentName}}{{else}}-{{/if}}</td>
<td class="visitor">{{#if invitationInfo}}{{formatTimeSince invitationInfo.time}}{{else}}-{{/if}}</td>
<td class="visitor">{{invitations}} / {{chats}}</td>
<td class="visitor">{{userAgent}}</td>

View File

@ -0,0 +1,17 @@
<table id="visitorslist" class="awaiting" border="0">
<thead>
<tr>
<th class="first">{{L10n "visitors.table.head.name"}}</th>
<th>{{L10n "visitors.table.head.actions"}}</th>
<th>{{L10n "visitors.table.head.contactid"}}</th>
<th>{{L10n "visitors.table.head.firsttimeonsite"}}</th>
<th>{{L10n "visitors.table.head.lasttimeonsite"}}</th>
<th>{{L10n "visitors.table.head.invited.by"}}</th>
<th>{{L10n "visitors.table.head.invitationtime"}}</th>
<th>{{L10n "visitors.table.head.invitations"}}</th>
<th>{{L10n "visitors.table.head.etc"}}</th>
</tr>
</thead>
<tbody id="visitors-container">
</tbody>
</table>

View File

@ -0,0 +1,51 @@
<?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.
*/
/**
* Implements Mibew Core - Mibew Users list interaction
*/
class MibewAPIUsersInteraction extends MibewAPIInteraction {
/**
* Defines obligatory arguments and default values for them
* @var array
* @see MibewAPIInteraction::$obligatoryArgumnents
*/
protected $obligatoryArguments = array(
'*' => array(
'agentId' => null,
'references' => array(),
'return' => array()
),
'updateThreads' => array(
'revision' => 0
),
'result' => array(
'errorCode' => 0
)
);
/**
* Reserved function's names
* @var array
* @see MibewAPIInteraction::$reservedFunctionNames
*/
public $reservedFunctionNames = array(
'result'
);
}
?>

View File

@ -0,0 +1,578 @@
<?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.
*/
/**
* Incapsulates awaiting users list api related functions.
*
* Register events (see RequestProcessor::registerEvents() for details):
* - usersRequestReceived
* - usersReceiveRequestError
* - usersCallError
* - usersFunctionCall
*
* WARNING:
* usersResponseReceived registered but never called because of asynchronous
* nature of Core-to-Window interaction
*
* Implements Singleton pattern
*/
class UsersProcessor extends ClientSideProcessor {
/**
* An instance of the UsersProcessor class
* @var UsersProcessor
*/
protected static $instance = null;
/**
* Return an instance of the ThreadProcessor class.
* @return UsersProcessor
*/
public static function getInstance() {
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Class constructor
*
* Do not use directly __construct method! Use ThreadProcessor::getInstance() instead!
* @todo Think about why the method is not protected
*/
public function __construct() {
parent::__construct(array(
'signature' => '',
'trusted_signatures' => array(''),
'event_prefix' => 'users'
));
}
/**
* Creates and returns an instance of the MibewAPI class.
*
* @return MibewAPI
*/
protected function getMibewAPIInstance() {
return MibewAPI::getAPI('MibewAPIUsersInteraction');
}
/**
* Sends asynchronous request
*
* @param array $request The 'request' array. See Mibew API for details
* @return boolean true on success or false on failure
*/
protected function sendAsyncRequest($request) {
// Define empty agent id
$agent_id = null;
foreach ($request['functions'] as $function) {
// Save agent id from first function in package
if (is_null($agent_id)) {
$agent_id = $function['arguments']['agentId'];
continue;
}
// Check agent id for the remaining functions
if ($agent_id != $function['arguments']['agentId']) {
throw new UsersProcessorException(
'Various agent ids in different functions in one package!',
UsersProcessorException::VARIOUS_AGENT_ID
);
}
}
// Store request in buffer
$this->addRequestToBuffer('users_'.$agent_id, $request);
return true;
}
/**
* Check operator id equals to $operatorId is current logged in operator
*
* @param int $operatorId Operator id to check
* @return array Operators info array
*
* @throws UsersProcessorException If operators not logged in or if
* $operatorId varies from current logged in operator.
*/
protected static function checkOperator($operatorId) {
$operator = get_logged_in();
if (!$operator) {
throw new UsersProcessorException(
getstring("agent.not_logged_in"),
UsersProcessorException::ERROR_AGENT_NOT_LOGGED_IN
);
}
if ($operatorId != $operator['operatorid']) {
throw new UsersProcessorException(
"Wrong agent id: '{$operatorId}' instead of {$operator['operatorid']}",
UsersProcessorException::ERROR_WRONG_AGENT_ID
);
}
return $operator;
}
/**
* Mark operator as away. API function
*
* @param array $args Associative array of arguments. It must contains
* following keys:
* - 'agentId': Id of the agent related to users window
*/
protected function apiAway($args) {
$operator = self::checkOperator($args['agentId']);
notify_operator_alive($operator['operatorid'], 1);
}
/**
* Mark operator as available. API function
*
* @param array $args Associative array of arguments. It must contains
* following keys:
* - 'agentId': Id of the agent related to users window
*/
protected function apiAvailable($args) {
$operator = self::checkOperator($args['agentId']);
notify_operator_alive($operator['operatorid'], 0);
}
/**
* Return updated threads list. API function
*
* @global string $mysqlprefix Database tables prefix
* @global int $can_viewthreads View threads permission code
* @global int $can_takeover Take threads over permission code
* @param array $args Associative array of arguments. It must contains
* following keys:
* - 'agentId': Id of the agent related to users window
* - 'revision': last revision number at client side
* @return array Array of results. It contains following keys:
* - 'threads': array of threads changes
*/
protected function apiUpdateThreads($args) {
global $mysqlprefix, $can_viewthreads, $can_takeover;
$operator = self::checkOperator($args['agentId']);
$since = $args['revision'];
// Get operator groups
if (!isset($_SESSION["${mysqlprefix}operatorgroups"])) {
$_SESSION["${mysqlprefix}operatorgroups"]
= get_operator_groupslist($operator['operatorid']);
}
$groupids = $_SESSION["${mysqlprefix}operatorgroups"];
$db = Database::getInstance();
$query = "select t.*, " .
" g.vclocalname as group_localname, " .
" g.vccommonname as group_commonname " .
" from {chatthread} t left outer join {chatgroup} g on " .
" t.groupid = g.groupid " .
" where t.lrevision > :since " .
($since == 0
// Select only active threads at first time when lrevision = 0
? " AND t.istate <> " . Thread::STATE_CLOSED .
" AND t.istate <> " . Thread::STATE_LEFT
// Select all threads at when lrevision > 0. It provides the
// ability to update(and probably hide) closed threads at the
// clien side.
: ""
) .
(Settings::get('enablegroups') == '1'
// If groups are enabled select only threads with empty groupid
// or groups related to current operator
? " AND (g.groupid is NULL" . ($groupids
? " OR g.groupid IN ($groupids) OR g.groupid IN " .
"(SELECT parent FROM {chatgroup} " .
"WHERE groupid IN ($groupids)) "
: "") .
") "
: ""
) .
" ORDER BY t.threadid";
$rows = $db->query(
$query,
array(':since' => $since),
array('return_rows' => Database::RETURN_ALL_ROWS)
);
$revision = $since;
$threads = array();
foreach($rows as $row) {
// Create thread instance
$thread = Thread::createFromDbInfo($row);
// Calculate agent permissions
$can_open = !($thread->state == Thread::STATE_CHATTING
&& $thread->agentId != $operator['operatorid']
&& !is_capable($can_takeover, $operator));
$can_view = ($thread->agentId != $operator['operatorid']
&& $thread->nextAgent != $operator['operatorid']
&& is_capable($can_viewthreads, $operator));
$can_ban = (Settings::get('enableban') == "1");
// Get ban info
$ban_info = (Settings::get('enableban') == "1")
? ban_for_addr($thread->remote)
: false;
if ($ban_info !== false) {
$ban = array(
'id' => $ban_info['banid'],
'reason' => $ban_info['comment']
);
} else {
$ban = false;
}
// Get user name
$user_name = get_user_name(
$thread->userName,
$thread->remote,
$thread->userId
);
// Get user ip
if (preg_match("/(\\d+\\.\\d+\\.\\d+\\.\\d+)/", $thread->remote, $matches) != 0) {
$user_ip = $matches[1];
} else {
$user_ip = false;
}
// Get thread operartor name
$nextagent = $thread->nextAgent != 0
? operator_by_id($thread->nextAgent)
: false;
if ($nextagent) {
$agent_name = get_operator_name($nextagent);
} else {
if ($thread->agentName) {
$agent_name = $thread->agentName;
} else {
$group_name = get_group_name(array(
'vccommonname' => $row['group_commonname'],
'vclocalname' => $row['group_localname']
));
if($group_name) {
$agent_name = '-' . $group_name . '-';
} else {
$agent_name = '-';
}
}
}
// Get first message
$first_message = null;
if ($thread->shownMessageId != 0) {
$line = $db->query(
"select tmessage from {chatmessage} " .
" where messageid = ? limit 1",
array($thread->shownMessageId),
array('return_rows' => Database::RETURN_ONE_ROW)
);
if ($line) {
$first_message = preg_replace(
"/[\r\n\t]+/",
" ",
$line["tmessage"]
);
}
}
$threads[] = array(
'id' => $thread->id,
'token' => $thread->lastToken,
'userName' => $user_name,
'userIp' => $user_ip,
'remote' => $thread->remote,
'userAgent' => get_useragent_version($thread->userAgent),
'agentName' => $agent_name,
'canOpen' => $can_open,
'canView' => $can_view,
'canBan' => $can_ban,
'ban' => $ban,
'state' => $thread->state,
'totalTime' => $thread->created,
'waitingTime' => $thread->modified,
'firstMessage' => $first_message
);
// Get max revision
if ($thread->lastRevision > $revision) {
$revision = $thread->lastRevision;
}
// Clean up
unset($thread);
}
// Send results back to the client
return array(
'threads' => $threads,
'lastRevision' => $revision
);
}
/**
* Return updated visitors list. API function
*
* @param array $args Associative array of arguments. It must contains
* following keys:
* - 'agentId': Id of the agent related to users window
* @return array Array of results. It contains following keys:
* - 'visitors': array of visitors on the site
*/
protected function apiUpdateVisitors($args) {
// Check access
self::checkOperator($args['agentId']);
$db = Database::getInstance();
// Remove old visitors
$db->query(
"DELETE FROM {chatsitevisitor} " .
"WHERE (:now - lasttime) > :lifetime ".
"AND (threadid IS NULL OR " .
"(SELECT count(*) FROM {chatthread} " .
"WHERE threadid = {chatsitevisitor}.threadid " .
"AND istate <> " . Thread::STATE_CLOSED . " " .
"AND istate <> " . Thread::STATE_LEFT . ") = 0)",
array(
':lifetime' => Settings::get('tracking_lifetime'),
':now' => time()
)
);
// Remove old invitations
$db->query(
"UPDATE {chatsitevisitor} SET invited = 0, " .
"invitationtime = NULL, invitedby = NULL".
" WHERE threadid IS NULL AND (:now - invitationtime) > :lifetime",
array(
':lifetime' => Settings::get('invitation_lifetime'),
':now' => time()
)
);
// Remove associations of visitors with closed threads
$db->query(
"UPDATE {chatsitevisitor} SET threadid = NULL " .
"WHERE threadid IS NOT NULL AND " .
" (SELECT count(*) FROM {chatthread} " .
"WHERE threadid = {chatsitevisitor}.threadid" .
" AND istate <> " . Thread::STATE_CLOSED . " " .
" AND istate <> " . Thread::STATE_LEFT . ") = 0"
);
// Remove old visitors' tracks
$db->query(
"DELETE FROM {visitedpage} WHERE (:now - visittime) > :lifetime " .
" AND visitorid NOT IN (SELECT visitorid FROM {chatsitevisitor})",
array(
':lifetime' => Settings::get('tracking_lifetime'),
':now' => time()
)
);
// 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 .= (Settings::get('visitors_limit') == '0')
? ""
: " LIMIT " . Settings::get('visitors_limit');
$rows = $db->query(
$query,
NULL,
array('return_rows' => Database::RETURN_ALL_ROWS)
);
$visitors = array();
foreach ($rows as $row) {
// Get visitor details
$details = track_retrieve_details($row);
// Get user agent
$user_agent = get_useragent_version($details['user_agent']);
// Get user ip
if (preg_match("/(\\d+\\.\\d+\\.\\d+\\.\\d+)/", $details['remote_host'], $matches) != 0) {
$user_ip = $matches[1];
} else {
$user_ip = false;
}
// Get invitation info
if ($row['invited']) {
$agent_name = get_operator_name(
operator_by_id($row['invitedby'])
);
$invitation_info = array(
'time' => $row['invitationtime'],
'agentName' => $agent_name
);
} else {
$invitation_info = false;
}
// Create resulting visitor structure
$visitors[] = array(
'id' => (int)$row['visitorid'],
'userName' => $row['username'],
'userAgent' => $user_agent,
'userIp' => $user_ip,
'remote' => $details['remote_host'],
'firstTime' => $row['firsttime'],
'lastTime' => $row['lasttime'],
'invitations' => (int)$row['invitations'],
'chats' => (int)$row['chats'],
'invitationInfo' => $invitation_info
);
}
return array(
'visitors' => $visitors
);
}
/**
* Return updated operators list. API function
*
* @global string $webim_encoding Encoding for the current locale
* @param array $args Associative array of arguments. It must contains
* following keys:
* - 'agentId': Id of the agent related to users window
* @return array Array of results. It contains following keys:
* - 'operators': array of online operators
*/
protected function apiUpdateOperators($args) {
global $webim_encoding;
// Check access and get operators info
$operator = self::checkOperator($args['agentId']);
// Return empty array if show operators option disabled
if (Settings::get('showonlineoperators') != '1') {
return array(
'operators' => array()
);
}
// Check if curent operator is in isolation
$list_options = in_isolation($operator)
? array('isolated_operator_id' => $operator['operatorid'])
: array();
// Get operators list
$operators = get_operators_list($list_options);
// Create resulting list of operators
$result_list = array();
foreach ($operators as $item) {
if (!operator_is_online($item)) {
continue;
}
$result_list[] = array(
'id' => (int)$item['operatorid'],
// Convert name to UTF-8
'name' => myiconv(
$webim_encoding,
"utf-8",
htmlspecialchars($item['vclocalename'])
),
'away' => (bool)operator_is_away($item)
);
}
// Send operators list to the client side
return array(
'operators' => $result_list
);
}
/**
* Update chat window state. API function
*
* Call periodically by chat window
* @param array $args Associative array of arguments. It must contains
* following keys:
* - 'agentId': Id of the agent related to users window
*/
protected function apiUpdate($args) {
// Check access and get operator array
$operator = self::checkOperator($args['agentId']);
// Update operator status
notify_operator_alive($operator['operatorid'], $operator['istatus']);
// Close old threads
Thread::closeOldThreads();
// Load stored requests
$stored_requests = $this->getRequestsFromBuffer('users_'.$args['agentId']);
if ($stored_requests !== false) {
$this->responses = array_merge($this->responses, $stored_requests);
}
}
/**
* Returns current server time. API function
*
* @param array $args Associative array of arguments. It must contains
* following keys:
* - 'agentId': Id of the agent related to users window
* @return array Array of results. It contains following keys:
* - 'time': current server time
*/
protected function apiCurrentTime($args) {
// Check access
self::checkOperator($args['agentId']);
// Return time
return array(
'time' => time()
);
}
}
/**
* Class for users processor exceptions
*/
class UsersProcessorException extends RequestProcessorException {
/**
* Operator is not logged in
*/
const ERROR_AGENT_NOT_LOGGED_IN = 1;
/**
* Wrong agent id
*/
const ERROR_WRONG_AGENT_ID = 2;
/**
* Various agent ids in different functions in one package
*/
const VARIOUS_AGENT_ID = 3;
}
?>

View File

@ -46,6 +46,20 @@ function get_core_style_config() {
$config += array(
'history' => array(
'window_params' => ''
),
'users' => array(
'thread_tag' => 'div',
'visitor_tag' => 'div'
),
'tracked' => array(
'user_window_params' => '',
'visitor_window_params' => ''
),
'invitation' => array(
'window_params' => ''
),
'ban' => array(
'window_params' => ''
)
);

View File

@ -40,7 +40,7 @@ char.redirect.operator.online_suff=(online)
chat.came.from=Vistor came from page {0}
chat.client.changename=Change name
chat.client.name=You are
chat.client.spam.prefix=[spam]&nbsp;
chat.client.spam.prefix=[spam]
chat.client.visited.page=Visitor navigated to {0}
chat.close.confirmation=Are you sure want to leave chat?
chat.default.username=Guest
@ -615,6 +615,7 @@ updates.title=Updates
visitors.how_to=To invite the visitor to chat click on his/her name in the list.
visitors.intro=The table below represents a list of visitors ready to chat on your site.
visitors.no_visitors=There are no visitors ready to chat on your site at present time
visitors.table.head.actions=Actions
visitors.table.head.contactid=Visitor's address
visitors.table.head.etc=Misc
visitors.table.head.firsttimeonsite=First seen

View File

@ -40,7 +40,7 @@ char.redirect.operator.online_suff=(
chat.came.from=Посетитель пришел со страницы {0}
chat.client.changename=Изменить имя
chat.client.name=Вы
chat.client.spam.prefix=[ñïàì]&nbsp;
chat.client.spam.prefix=[ñïàì]
chat.client.visited.page=Посетитель перешел на {0}
chat.close.confirmation=Вы действительно хотите покинуть диалог?
chat.default.username=Посетитель
@ -617,6 +617,7 @@ updates.title=
visitors.how_to=Для приглашения посетителя к диалогу кликните на его или её имя в списке.
visitors.intro=В расположенной ниже таблице представлен список готовых к диалогу посетителей на Вашем сайте.
visitors.no_visitors=В настоящее время на Вашем сайте нет готовых к диалогу посетителей
visitors.table.head.actions=Äåéñòâèÿ
visitors.table.head.contactid=Адрес посетителя
visitors.table.head.etc=Разное
visitors.table.head.firsttimeonsite=Впервые замечен

View File

@ -22,298 +22,14 @@ require_once('../libs/operator.php');
require_once('../libs/groups.php');
require_once('../libs/track.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_users_interaction.php');
require_once('../libs/classes/mibew_api_execution_context.php');
require_once('../libs/classes/client_side_processor.php');
require_once('../libs/classes/users_processor.php');
$operator = get_logged_in();
if (!$operator) {
start_xml_output();
echo "<error><descr>" . myiconv($webim_encoding, "utf-8", escape_with_cdata(getstring("agent.not_logged_in"))) . "</descr></error>";
exit;
}
$threadstate_to_string = array(
Thread::STATE_QUEUE => "wait",
Thread::STATE_WAITING => "prio",
Thread::STATE_CHATTING => "chat",
Thread::STATE_CLOSED => "closed",
Thread::STATE_LOADING => "wait",
Thread::STATE_LEFT => "closed"
);
$threadstate_key = array(
Thread::STATE_QUEUE => "chat.thread.state_wait",
Thread::STATE_WAITING => "chat.thread.state_wait_for_another_agent",
Thread::STATE_CHATTING => "chat.thread.state_chatting_with_agent",
Thread::STATE_CLOSED => "chat.thread.state_closed",
Thread::STATE_LOADING => "chat.thread.state_loading"
);
function thread_to_xml($thread_info)
{
global $threadstate_to_string, $threadstate_key,
$webim_encoding, $operator, $can_viewthreads, $can_takeover;
$thread = $thread_info['thread'];
$state = $threadstate_to_string[$thread->state];
$result = "<thread id=\"" . $thread->id . "\" stateid=\"$state\"";
if ($state == "closed")
return $result . "/>";
$state = getstring($threadstate_key[$thread->state]);
$nextagent = $thread->nextAgent != 0 ? operator_by_id($thread->nextAgent) : null;
$threadoperator = $nextagent ? get_operator_name($nextagent)
: ($thread->agentName ? $thread->agentName : "-");
if ($threadoperator == "-" && ! empty($thread_info['groupname'])) {
$threadoperator = "- " . $thread_info['groupname'] . " -";
}
if (!($thread->state == Thread::STATE_CHATTING && $thread->agentId != $operator['operatorid'] && !is_capable($can_takeover, $operator))) {
$result .= " canopen=\"true\"";
}
if ($thread->agentId != $operator['operatorid'] && $thread->nextAgent != $operator['operatorid']
&& is_capable($can_viewthreads, $operator)) {
$result .= " canview=\"true\"";
}
if (Settings::get('enableban') == "1") {
$result .= " canban=\"true\"";
}
$banForThread = Settings::get('enableban') == "1" ? ban_for_addr($thread->remote) : false;
if ($banForThread) {
$result .= " ban=\"blocked\" banid=\"" . $banForThread['banid'] . "\"";
}
$result .= " state=\"$state\" typing=\"" . $thread->userTyping . "\">";
$result .= "<name>";
if ($banForThread) {
$result .= htmlspecialchars(getstring('chat.client.spam.prefix'));
}
$result .= htmlspecialchars(
htmlspecialchars(get_user_name($thread->userName, $thread->remote, $thread->userId))
) . "</name>";
$result .= "<addr>" . htmlspecialchars(get_user_addr($thread->remote)) . "</addr>";
$result .= "<agent>" . htmlspecialchars(htmlspecialchars($threadoperator)) . "</agent>";
$result .= "<time>" . $thread->created . "000</time>";
$result .= "<modified>" . $thread->modified . "000</modified>";
if ($banForThread) {
$result .= "<reason>" . $banForThread['comment'] . "</reason>";
}
$userAgent = get_useragent_version($thread->userAgent);
$result .= "<useragent>" . $userAgent . "</useragent>";
if ($thread->shownMessageId != 0) {
$db = Database::getInstance();
$line = $db->query(
"select tmessage from {chatmessage} where messageid = ?",
array($thread->shownMessageId),
array('return_rows' => Database::RETURN_ONE_ROW)
);
if ($line) {
$message = preg_replace("/[\r\n\t]+/", " ", $line["tmessage"]);
$result .= "<message>" . htmlspecialchars(htmlspecialchars($message)) . "</message>";
}
}
$result .= "</thread>";
return $result;
}
function print_pending_threads($groupids, $since)
{
global $webim_encoding;
$db = Database::getInstance();
$revision = $since;
$query = "select {chatthread}.*, " .
"(select vclocalname from {chatgroup} where {chatgroup}.groupid = {chatthread}.groupid) as groupname " .
"from {chatthread} where lrevision > :since " .
($since <= 0
? "AND istate <> " . Thread::STATE_CLOSED . " AND istate <> " . Thread::STATE_LEFT . " "
: "") .
(Settings::get('enablegroups') == '1'
? "AND (groupid is NULL" . ($groupids
? " OR groupid IN ($groupids) OR groupid IN (SELECT parent FROM {chatgroup} WHERE groupid IN ($groupids)) "
: "") .
") "
: "") .
"ORDER BY threadid";
$rows = $db->query(
$query,
array(':since' => $since),
array('return_rows' => Database::RETURN_ALL_ROWS)
);
$output = array();
foreach ($rows as $row) {
$thread = Thread::createFromDbInfo($row);
$thread_info = array(
'thread' => $thread,
'groupname' => $row['groupname']
);
$thread_as_xml = thread_to_xml($thread_info);
$output[] = $thread_as_xml;
if ($thread->lastRevision > $revision) {
$revision = $thread->lastRevision;
}
}
echo "<threads revision=\"$revision\" time=\"" . time() . "000\">";
foreach ($output as $thr) {
print myiconv($webim_encoding, "utf-8", $thr);
}
echo "</threads>";
}
function print_operators($operator)
{
global $webim_encoding;
echo "<operators>";
$list_options = in_isolation($operator)?array('isolated_operator_id' => $operator['operatorid']):array();
$operators = get_operators_list($list_options);
foreach ($operators as $operator) {
if (!operator_is_online($operator))
continue;
$name = myiconv($webim_encoding, "utf-8", htmlspecialchars(htmlspecialchars($operator['vclocalename'])));
$away = operator_is_away($operator) ? " away=\"1\"" : "";
echo "<operator name=\"$name\"$away/>";
}
echo "</operators>";
}
function visitor_to_xml($visitor)
{
$result = "<visitor id=\"" . $visitor['visitorid'] . "\">";
// $result .= "<userid>" . htmlspecialchars($visitor['userid']) . "</userid>";
$result .= "<username>" . htmlspecialchars($visitor['username']) . "</username>";
$result .= "<time>" . $visitor['firsttime'] . "000</time>";
$result .= "<modified>" . $visitor['lasttime'] . "000</modified>";
// $result .= "<entry>" . htmlspecialchars($visitor['entry']) . "</entry>";
// $result .= "<path>";
// $path = track_retrieve_path($visitor);
// ksort($path);
// foreach ($path as $k => $v) {
// $result .= "<url visited=\"" . $k . "000\">" . htmlspecialchars($v) . "</url>";
// }
// $result .= "</path>";
$details = track_retrieve_details($visitor);
$userAgent = get_useragent_version($details['user_agent']);
$result .= "<useragent>" . $userAgent . "</useragent>";
$result .= "<addr>" . htmlspecialchars(get_user_addr($details['remote_host'])) . "</addr>";
$result .= "<invitations>" . $visitor['invitations'] . "</invitations>";
$result .= "<chats>" . $visitor['chats'] . "</chats>";
$result .= "<invitation>";
if ($visitor['invited']) {
$result .= "<invitationtime>" . $visitor['invitationtime'] . "000</invitationtime>";
$operator = get_operator_name(operator_by_id($visitor['invitedby']));
$result .= "<operator>" . htmlspecialchars(htmlspecialchars($operator)) . "</operator>";
}
$result .= "</invitation>";
$result .= "</visitor>";
return $result;
}
function print_visitors()
{
global $webim_encoding;
$db = Database::getInstance();
// Remove old visitors
$db->query(
"DELETE FROM {chatsitevisitor} " .
"WHERE (:now - lasttime) > :lifetime ".
"AND (threadid IS NULL OR " .
"(SELECT count(*) FROM {chatthread} WHERE threadid = {chatsitevisitor}.threadid " .
"AND istate <> " . Thread::STATE_CLOSED . " AND istate <> " . Thread::STATE_LEFT . ") = 0)",
array(
':lifetime' => Settings::get('tracking_lifetime'),
':now' => time()
)
);
// Remove old invitations
$db->query(
"UPDATE {chatsitevisitor} SET invited = 0, invitationtime = NULL, invitedby = NULL".
" WHERE threadid IS NULL AND (:now - invitationtime) > :lifetime",
array(
':lifetime' => Settings::get('invitation_lifetime'),
':now' => time()
)
);
// Remove associations of visitors with closed threads
$db->query(
"UPDATE {chatsitevisitor} SET threadid = NULL WHERE threadid IS NOT NULL AND" .
" (SELECT count(*) FROM {chatthread} WHERE threadid = {chatsitevisitor}.threadid" .
" AND istate <> " . Thread::STATE_CLOSED . " AND istate <> " . Thread::STATE_LEFT . ") = 0"
);
// Remove old visitors' tracks
$db->query(
"DELETE FROM {visitedpage} WHERE (:now - visittime) > :lifetime " .
" AND visitorid NOT IN (SELECT visitorid FROM {chatsitevisitor})",
array(
':lifetime' => Settings::get('tracking_lifetime'),
':now' => time()
)
);
$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 .= (Settings::get('visitors_limit') == '0') ? "" : " LIMIT " . Settings::get('visitors_limit');
$rows = $db->query($query, NULL, array('return_rows' => Database::RETURN_ALL_ROWS));
$output = array();
foreach ($rows as $row) {
$visitor = visitor_to_xml($row);
$output[] = $visitor;
}
echo "<visitors>";
foreach ($output as $thr) {
print myiconv($webim_encoding, "utf-8", $thr);
}
echo "</visitors>";
}
$since = verifyparam("since", "/^\d{1,9}$/", 0);
$status = verifyparam("status", "/^\d{1,2}$/", 0);
$showonline = verifyparam("showonline", "/^1$/", 0);
$showvisitors = verifyparam("showvisitors", "/^1$/", 0);
if (!isset($_SESSION["${mysqlprefix}operatorgroups"])) {
$_SESSION["${mysqlprefix}operatorgroups"] = get_operator_groupslist($operator['operatorid']);
}
Thread::closeOldThreads();
$groupids = $_SESSION["${mysqlprefix}operatorgroups"];
start_xml_output();
echo '<update>';
if ($showonline) {
print_operators($operator);
}
print_pending_threads($groupids, $since);
if ($showvisitors) {
print_visitors();
}
echo '</update>';
notify_operator_alive($operator['operatorid'], $status);
exit;
$processor = UsersProcessor::getInstance();
$processor->receiveRequest($_POST['data']);
?>

View File

@ -35,6 +35,22 @@ $page['frequency'] = Settings::get('updatefrequency_operator');
$page['istatus'] = $status;
$page['showonline'] = Settings::get('showonlineoperators') == '1' ? "1" : "0";
$page['showvisitors'] = Settings::get('enabletracking') == '1' ? "1" : "0";
$page['agentId'] = $operator['operatorid'];
$page['geoLink'] = Settings::get('geolink');
$page['geoWindowParams'] = Settings::get('geolinkparams');
// Load dialogs style options
$style_config = get_dialogs_style_config(getchatstyle());
$page['chatStyles.chatWindowParams'] = $style_config['chat']['window_params'];
// Load core style options
$style_config = get_core_style_config();
$page['coreStyles.threadTag'] = $style_config['users']['thread_tag'];
$page['coreStyles.visitorTag'] = $style_config['users']['visitor_tag'];
$page['coreStyles.trackedUserWindowParams'] = $style_config['tracked']['user_window_params'];
$page['coreStyles.trackedVisitorWindowParams'] = $style_config['tracked']['visitor_window_params'];
$page['coreStyles.inviteWindowParams'] = $style_config['invitation']['window_params'];
$page['coreStyles.banWindowParams'] = $style_config['ban']['window_params'];
prepare_menu($operator);
start_html_output();

View File

@ -4,3 +4,22 @@
[history]
; window_param use as param string in JavaScript window.open method
window_params = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,width=720,height=560,resizable=1"
[users]
; Use as wrap tag for the thread element
thread_tag = "tr"
; Use as wrap tag for the visitor element
visitor_tag = "tr"
[tracked]
; window_param use as param string in JavaScript window.open method
user_window_params = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,width=640,height=480,resizable=1"
visitor_window_params = "toolbar=0,scrollbars=1,location=0,status=1,menubar=0,width=640,height=480,resizable=1"
[invitation]
; window_param use as param string in JavaScript window.open method
window_params = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,width=640,height=480,resizable=1"
[ban]
; window_param use as param string in JavaScript window.open method
window_params = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,width=720,height=480,resizable=1"

View File

@ -30,6 +30,7 @@ $isrtl = getlocal("localedirection") == 'rtl';
<?php echo $page['title'] ?> - <?php echo getlocal("app.title") ?>
</title>
<link href="<?php echo $webimroot ?>/default.css" rel="stylesheet" type="text/css" />
<!--[if lte IE 7]><link href="<?php echo $webimroot ?>/default_ie.css" rel="stylesheet" type="text/css" /><![endif] -->
<!--[if lte IE 6]><script language="JavaScript" type="text/javascript" src="<?php echo $webimroot ?>/<?php echo jspath() ?>/ie.js"></script><![endif]-->
</head>
<body<?php if(!function_exists('tpl_menu')) { ?> style="min-width: 400px;"<?php } ?>>

View File

@ -22,28 +22,97 @@ $page['menuid'] = "users";
function tpl_header() { global $page, $webimroot;
?>
<script type="text/javascript" language="javascript" src="<?php echo $webimroot ?>/js/compiled/common.js"></script>
<script type="text/javascript" language="javascript"><!--
var localized = new Array(
"<?php echo getlocal("pending.table.speak") ?>",
"<?php echo getlocal("pending.table.view") ?>",
"<?php echo getlocal("pending.table.ban") ?>",
"<?php echo htmlspecialchars(getlocal("pending.menu.show")) ?>",
"<?php echo htmlspecialchars(getlocal("pending.menu.hide")) ?>",
"<?php echo htmlspecialchars(getlocal("pending.popup_notification")) ?>",
"<?php echo getlocal("pending.table.tracked") ?>",
"<?php echo getlocal("pending.table.invite") ?>",
"<?php echo getlocal("pending.status.away") ?>",
"<?php echo getlocal("pending.status.online") ?>"
);
var updaterOptions = {
url:"<?php echo $webimroot ?>/operator/update.php",wroot:"<?php echo $webimroot ?>",
agentservl:"<?php echo $webimroot ?>/operator/agent.php", frequency:<?php echo $page['frequency'] ?>, istatus:<?php echo $page['istatus'] ?>,
noclients:"<?php echo getlocal("clients.no_clients") ?>", havemenu: <?php echo $page['havemenu'] ?>, showpopup: <?php echo $page['showpopup'] ?>,
showonline: <?php echo $page['showonline'] ?>, showvisitors: <?php echo $page['showvisitors'] ?>, novisitors: "<?php echo getlocal("visitors.no_visitors") ?>",
trackedservl:"<?php echo $webimroot ?>/operator/tracked.php", inviteservl:"<?php echo $webimroot ?>/operator/invite.php" };
<!-- 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/users_app.js"></script>
<script type="text/javascript"><!--
Mibew.Localization.set({
'pending.table.speak': "<?php echo getlocal('pending.table.speak') ?>",
'pending.table.view': "<?php echo getlocal('pending.table.view') ?>",
'pending.table.ban': "<?php echo getlocal('pending.table.ban') ?>",
'pending.menu.show': "<?php echo htmlspecialchars(getlocal('pending.menu.show')) ?>",
'pending.menu.hide': "<?php echo htmlspecialchars(getlocal('pending.menu.hide')) ?>",
'pending.popup_notification': "<?php echo htmlspecialchars(getlocal('pending.popup_notification')) ?>",
'pending.table.tracked': "<?php echo getlocal('pending.table.tracked') ?>",
'pending.table.invite': "<?php echo getlocal('pending.table.invite') ?>",
'pending.status.away': "<?php echo getlocal('pending.status.away') ?>",
'pending.status.online': "<?php echo getlocal('pending.status.online') ?>",
'pending.status.setonline': "<?php echo addslashes(getlocal('pending.status.setonline')) ?>",
'pending.status.setaway': "<?php echo addslashes(getlocal('pending.status.setaway')) ?>",
'pending.table.head.name': "<?php echo getlocal('pending.table.head.name') ?>",
'pending.table.head.actions': "<?php echo getlocal('pending.table.head.actions') ?>",
'pending.table.head.contactid': "<?php echo getlocal('pending.table.head.contactid') ?>",
'pending.table.head.state': "<?php echo getlocal('pending.table.head.state') ?>",
'pending.table.head.operator': "<?php echo getlocal('pending.table.head.operator') ?>",
'pending.table.head.total': "<?php echo getlocal('pending.table.head.total') ?>",
'pending.table.head.waittime': "<?php echo getlocal('pending.table.head.waittime') ?>",
'pending.table.head.etc': "<?php echo getlocal('pending.table.head.etc') ?>",
'visitors.table.head.actions': "<?php echo getlocal('visitors.table.head.actions') ?>",
'visitors.table.head.name': "<?php echo getlocal('visitors.table.head.name') ?>",
'visitors.table.head.contactid': "<?php echo getlocal('visitors.table.head.contactid') ?>",
'visitors.table.head.firsttimeonsite': "<?php echo getlocal('visitors.table.head.firsttimeonsite') ?>",
'visitors.table.head.lasttimeonsite': "<?php echo getlocal('visitors.table.head.lasttimeonsite') ?>",
'visitors.table.head.invited.by': "<?php echo getlocal('visitors.table.head.invited.by') ?>",
'visitors.table.head.invitationtime': "<?php echo getlocal('visitors.table.head.invitationtime') ?>",
'visitors.table.head.invitations': "<?php echo getlocal('visitors.table.head.invitations') ?>",
'visitors.table.head.etc': "<?php echo getlocal('visitors.table.head.etc') ?>",
'visitors.no_visitors': "<?php echo getlocal('visitors.no_visitors') ?>",
'clients.no_clients': "<?php echo getlocal('clients.no_clients') ?>",
'chat.thread.state_wait': "<?php echo getlocal('chat.thread.state_wait'); ?>",
'chat.thread.state_wait_for_another_agent': "<?php echo getlocal('chat.thread.state_wait_for_another_agent'); ?>",
'chat.thread.state_chatting_with_agent': "<?php echo getlocal('chat.thread.state_chatting_with_agent'); ?>",
'chat.thread.state_closed': "<?php echo getlocal('chat.thread.state_closed'); ?>",
'chat.thread.state_loading': "<?php echo getlocal('chat.thread.state_loading'); ?>",
'chat.client.spam.prefix': "<?php echo getstring('chat.client.spam.prefix'); ?>"
});
//--></script>
<script type="text/javascript" language="javascript" src="<?php echo $webimroot ?>/js/compiled/users.js"></script>
<script type="text/javascript"><!--
jQuery(document).ready(function(){
Mibew.Application.start({
server: {
url: "<?php echo $webimroot ?>/operator/update.php",
requestsFrequency: <?php echo $page['frequency'] ?>
},
agent: {
id: <?php echo $page['agentId'] ?>
},
page: {
showOnlineOperators: <?php echo($page['showonline']?'true':'false'); ?>,
showVisitors: <?php echo ($page['showvisitors']?'true':'false'); ?>,
threadTag: "<?php echo $page['coreStyles.threadTag']; ?>",
visitorTag: "<?php echo $page['coreStyles.visitorTag']; ?>",
agentLink: "<?php echo $webimroot ?>/operator/agent.php",
geoLink: "<?php echo $page['geoLink']; ?>",
trackedLink: "<?php echo $webimroot ?>/operator/tracked.php",
banLink: "<?php echo $webimroot ?>/operator/ban.php",
inviteLink: "<?php echo $webimroot ?>/operator/invite.php",
chatWindowParams: "<?php echo $page['chatStyles.chatWindowParams']; ?>",
geoWindowParams: "<?php echo $page['geoWindowParams'];?>",
trackedUserWindowParams: "<?php echo $page['coreStyles.trackedUserWindowParams']; ?>",
trackedVisitorWindowParams: "<?php echo $page['coreStyles.trackedVisitorWindowParams']; ?>",
banWindowParams: "<?php echo $page['coreStyles.banWindowParams']; ?>",
inviteWindowParams: "<?php echo $page['coreStyles.inviteWindowParams']; ?>"
}
});
});
//--></script>
<?php
}
@ -51,89 +120,27 @@ function tpl_content() { global $page, $webimroot;
?>
<div>
<div id="togglediv">
<a href="#" id="togglemenu"></a>
</div>
<?php echo getlocal("clients.intro") ?>
<br/>
<?php echo getlocal("clients.how_to") ?>
</div>
<br/>
<table id="threadlist" class="awaiting" border="0">
<thead>
<tr>
<th class="first"><?php echo getlocal("pending.table.head.name") ?></th>
<th><?php echo getlocal("pending.table.head.actions") ?></th>
<th><?php echo getlocal("pending.table.head.contactid") ?></th>
<th><?php echo getlocal("pending.table.head.state") ?></th>
<th><?php echo getlocal("pending.table.head.operator") ?></th>
<th><?php echo getlocal("pending.table.head.total") ?></th>
<th><?php echo getlocal("pending.table.head.waittime") ?></th>
<th><?php echo getlocal("pending.table.head.etc") ?></th>
</tr>
</thead>
<tbody>
<tr id="tprio"><td colspan="8"></td></tr>
<tr id="tprioend"><td colspan="8"></td></tr>
<tr id="twait"><td colspan="8"></td></tr>
<tr id="twaitend"><td colspan="8"></td></tr>
<tr id="tchat"><td colspan="8"></td></tr>
<tr id="tchatend"><td colspan="8"></td></tr>
<tr><td id="statustd" colspan="8" height="30">Loading....</td></tr>
</tbody>
</table>
<div id="threads-region"></div>
<?php if ($page['showvisitors']) { ?>
<div class="tabletitle"><?php echo getlocal("visitors.title") ?></div>
<?php echo getlocal("visitors.intro") ?>
<br/>
<?php echo getlocal("visitors.how_to") ?>
<table id="visitorslist" class="awaiting" border="0">
<thead>
<tr>
<th class="first"><?php echo getlocal("visitors.table.head.name") ?></th>
<th><?php echo getlocal("visitors.table.head.contactid") ?></th>
<th><?php echo getlocal("visitors.table.head.firsttimeonsite") ?></th>
<th><?php echo getlocal("visitors.table.head.lasttimeonsite") ?></th>
<th><?php echo getlocal("visitors.table.head.invited.by") ?></th>
<th><?php echo getlocal("visitors.table.head.invitationtime") ?></th>
<th><?php echo getlocal("visitors.table.head.invitations") ?></th>
<th><?php echo getlocal("visitors.table.head.etc") ?></th>
</tr>
</thead>
<tbody>
<tr id="visfree"><td colspan="8"></td></tr>
<tr id="visfreeend"><td colspan="8"></td></tr>
<tr id="visinvited"><td colspan="8"></td></tr>
<tr id="visinvitedend"><td colspan="8"></td></tr>
<tr><td id="visstatustd" colspan="8" height="30">Loading....</td></tr>
</tbody>
</table>
<div id="visitors-region"></div>
<hr/>
<?php } ?>
<div id="connstatus">
</div>
<div id="connlinks">
<?php if($page['istatus']) { ?>
<a href="users.php<?php echo $page['havemenu'] ? "" : "?nomenu" ?>"><?php echo getlocal("pending.status.setonline") ?></a>
<?php } else { ?>
<a href="users.php?away<?php echo $page['havemenu'] ? "" : "&amp;nomenu" ?>"><?php echo getlocal("pending.status.setaway") ?></a>
<?php } ?>
</div>
<?php if($page['showonline'] == "1") { ?>
<div id="onlineoperators">
</div>
<?php } ?>
<div id="status-panel-region"></div>
<div id="agents-region"></div>
<div id="sound-region"></div>
<?php
} /* content */