cancel
Showing results for 
Search instead for 
Did you mean: 

How to integrate chat room In Alfresco

mithun
Champ in-the-making
Champ in-the-making
i would like to integrate chat room in Alfresco ,actually i read in forum about the integration of chat in alfresco but i cant able to get the details clearly.if anyone knew about that tell me how to integrate.

Thank you
15 REPLIES 15

jonash
Champ in-the-making
Champ in-the-making
Hi,

Have a look at this blog post: http://blogs.alfresco.com/wp/dwebster/2011/10/
It show one way of adding a simple chat to Alfresco Share. There could be better ways depending on your exact requirements.

rjohnson
Star Contributor
Star Contributor
I've integrated the Candy chat client into Alfresco via dashlets and attached it to the eJabberd server. If this sounds like what you want I am happy to send you the code.

Bob Johnson

samnaction
Champ in-the-making
Champ in-the-making
Hi I am interested in chat room client for Alfresco, Can you send me the code

darkmstr
Champ in-the-making
Champ in-the-making
Hi RJohnson I'm really interested in the chat, can you tell me how you did it ?

rjohnson
Star Contributor
Star Contributor
As I'm not sure of your level of expertise in Alfresco I will start off with a high level view of what I did and we can get into more details if necessary.

The components I used were
    Alfresco Share
    eJabberd
    Candy Chat
    PHP
    Apache
I assume you want a chat room that can only be accessed by your Alfresco users, and the biggest issue in integrating chat is therefore to avoid the user having to log in to the chat room separately. Whilst this sounds simple, it wasn't for me and whilst having to log in sounds like nothing but a bit of an inconvenience its actually much worse because each time you move around Alfresco Share the form repaints and you could have to log in to the chat room again.

I chose eJabberd because it had plugable authentication scripts and supported using PHP for those scripts and thus I could authenticate chat room users against Alfresco itself. I chose Candy Chat because it was pure Javascript and therefore would work within an Alfresco dashlet.

I first installed eJabberd on the server I was running Alfresco on (it doesn't have to be on the same server, it was just what I did) and got it running with a single chat room.

I then installed Candy Chat and got that running in a standard web page that I created which looks like below.


<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>Candy - Chats are not dead yet</title>
   <link rel="shortcut icon" href="../res/img/favicon.png" type="image/gif" />
   <link rel="stylesheet" type="text/css" href="../res/rbj.css" />
   
   <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
   <script type="text/javascript" src="../libs/libs.min.js"></script>
   <script type="text/javascript" src="../candy.rbj.js"></script>
   <script type="text/javascript">
      $(document).ready(function() {
         Candy.init('http-bind/', {
            core: { debug: false , autojoin: ['My-Chat@conference.alfresco4']},
            view: { resources: '../res/' }
         });
         
         Candy.Core.connect();
      });
   </script>
</head>
<body>
   <div id="candy"></div>
</body>
</html>

rbj.css and candy.rbj.js are my custom versions of the standard Candy stuff. You can use the standard Candy stuff for testing at this point but you will need to modify the css and js to make the Candy Chat fit in a dashlet.

Instructions for installing ejabberd and Candy are on the web sites of the software, the only mildly tricky bit is getting eJabberd to work via Apache, there are instructions but they are not brilliantly clear.

Having done that you need to create a dashlet that will show Candy on a share dashboard. The only tricky bit of this was customising the candy css so that the chat fitted in a sensible sized dashlet. I haven't posted this code (you can have it if you want - just ask) because it is very long specific to my dashlet and my installation and yours would be different.

Authentication works thus. The Share Dashlet calls a webscript that returns the current Alfresco ticket for that user session. The main dashlet js is shown below.


/* Protocol and URL of notice site */
function getGlobalConfig(configItem)
{
    var myConfig = config.scoped["farthest-gate"]["im"];
    var cItem = myConfig.getChildValue(configItem);

   if (cItem)
   {
      cItem = myConfig.getChildValue(configItem).toString();
   }
   return cItem;
}

/**
* Live chat component GET method
*/

function main()
{
   // Call the repo for the alf_ticket so ejabberd can authorise
   var profile =
   {
      user: "",
      ticket: ""
   }
  
   var profile = null;
   var json = remote.call("/farthest-gate/getticket");
   if (json.status == 200)
   {
      // Create javascript object from the repo response
      var obj = eval('(' + json + ')');
      if (obj)
      {
         profile = obj;
      }
   }
     
   // Prepare the model
   model.chatproto = getGlobalConfig("protocol");
   model.chaturl = getGlobalConfig("url");
   model.chatroom = getGlobalConfig("room");
   model.ticket = profile.ticket;
   model.userid = profile.user;
}

main();

and this paints the dashlet using the template below


<script type="text/javascript">//<![CDATA[
(function()
{
   new Alfresco.widget.DashletTitleBarActions("${args.htmlid}").setOptions(
   {
      actions:
      [
         {
            cssClass: "help",
            bubbleOnClick:
            {
               message: "${msg("dashlet.help")?js_string}"
            },
            tooltip: "${msg("dashlet.help.tooltip")?js_string}"
         }
      ]
   });
})();
//]]></script>
<div class="dashlet fg-im-chat">
   <div class="title">${msg("header")}</div>
   <div class="body">  
      <iframe src="${chaturl}?room=${chatroom}&uid=${userid}&pwd=${ticket}" width="100%" height="50%" frameborder="0" marginheight="2" marginwidth="2" scrolling="no" ></iframe>
   </div>
</div>

The webscript "getticket" is 2 files, the descriptor XML and an Freemarker template that returns a JSON as shown below. Note that you need to ensure this is run in the repo layer NOT share.


<#escape x as jsonUtils.encodeJSONString(x)>
{"user":"${person.properties.userName}", "ticket":"${session.ticket}"}
</#escape>


When the dashlet renders it creates an iFrame that calls the PHP web page with the Candy Chat embedded in it passing the Alfresco username as a userid and the alfresco ticket as a password and the chat room name as URL parameters.


<?php
   $room = $_GET['room'];
   $uid = $_GET['uid'];
   $pwd = $_GET['pwd'];
?>
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>Candy - Chats are not dead yet</title>
   <link rel="shortcut icon" href="../res/img/favicon.png" type="image/gif" />
   <link rel="stylesheet" type="text/css" href="../res/rbj.css" />
   
   <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
   <script type="text/javascript" src="../libs/libs.min.js"></script>
   <script type="text/javascript" src="../candy.rbj.js"></script>
   <script type="text/javascript">
      $(document).ready(function() {
         Candy.init('http-bind/', {
            core: { debug: false , autojoin: ['<?php echo $room ?>']},
            view: { resources: '../res/' }
         });
         
         Candy.Core.connect('<?php echo $uid ?>@alfresco4','<?php echo $pwd ?>');
      });
   </script>
</head>
<body>
   <div id="candy"></div>
</body>
</html


That hits ejabberd with a user id and a password which ejabberd passes to the custom authentication script that I wrote that then validates the Alfresco ticket against Alfresco using the standard Alfresco API shown below.


#!/usr/bin/php
<?php

/*
* 02/01/2012
* ejabberd external authorisation script, integration with Alfresco 4.0
* originally written by Bob Johnson <robert@rowanshire.net>
* The script is passed either a UID & PWD in plain text
* or (and more expected) an Alfresco ticket from a previously authorised
* (and supposedly still valid) session. It then invokes the relevant
* alfresco webscript which returns true or false dependent upon whether
* or not the credentials are valid.
*
* published under LGPL
*/

error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);
include 'xml_regex.php';

$sAlfrescoHost = "http://alfresco4.local:8080";
$sAuthTicketScript = "/alfresco/service/api/people/";
$sAuthUIDPWDScript = "/alfresco/service/api/login";

/********************************
* the logfile to which to write.
* Must be writeable by the user
* which is running the server
* which is usually ejabberd
*********************************/
$sLogFile = "/var/log/ejabberd/alfrescoauth.log";

// set true to debug if needed
$bDebug = false;

$oAuth = new AlfrescoAuth($sAlfrescoHost, $sAuthTicketScript, $sAuthUIDPWDScript, $sLogFile, $bDebug);

class AlfrescoAuth
{
   private $sLogFile;
   private $sAlfrescoHost;
   private $sAuthTicketScript;
   private $sAuthUIDPWDScript;

   private $bDebug;
   private $rLogFile;
   
   public function __construct($sAlfrescoHost, $sAuthTicketScript, $sAuthUIDPWDScript, $sLogFile, $bDebug)
   {
      $this->sAlfrescoHost = $sAlfrescoHost;
      $this->sAuthTicketScript = $sAuthTicketScript;
      $this->sAuthUIDPWDScript = $sAuthUIDPWDScript;
      $this->sLogFile = $sLogFile;
      $this->bDebug = $bDebug;
      
      // Open log for append. If you can't drop dead.
      $this->rLogFile = fopen($this->sLogFile, "a") or die("Error opening log file: ". $this->sLogFile);

      $this->writeLog("[AlfrescoAuth] start");
      /***************************************************************************
      * Read stdin to get the request type and parameters
      * Forever loop, but it blocks on the fgets so won't burn CPU
      * Get 2 bytes from STDIN which is a binary string.
      * Remember fgets reads length -1 bytes
      * unpack to an associative array of unsigned integers
      * but note that there is only one unsigned int in a 2 byte binary string
      * which is the number of bytes that are left in STDIN to read
      * and in an associatve array, the array pointer "1" is the first value
      * in the associative array because thats the way unpack works
      ****************************************************************************/
      do {
         $iHeader = fgets(STDIN, 3);
         $aLength = unpack("n", $iHeader);
         $iLength = $aLength["1"];
         if($iLength > 0) {
            /*********************************************************************
            * Assuming we have something to process - read it
            * and explode it into an array of command / parmeters split on a :
            * Remember fgets reads length -1 bytes
            **********************************************************************/
            $sData = fgets(STDIN, $iLength + 1);
            $this->writeDebugLog("[debug] received data: ". $sData);
            $aCommand = explode(":", $sData);
            if (is_array($aCommand)){
               switch ($aCommand[0]){
                  case "isuser":
                     /*******************************************************
                     * ejabberd is asking. Is this a registered user name?
                     * NOTE this is NOT authorising the user to chat, merely
                     * checking if that user name exists
                     *********************************************************/
                     if (!isset($aCommand[1])){
                        $this->writeLog("[AlfrescoAuth] invalid isuser command, no username given");
                        /*************************************************
                        *Essentially, this is reponding boolean "false"
                        * Create a binary string with unsigned ints 2 & 0)
                        **************************************************/
                        fwrite(STDOUT, pack("nn", 2, 0));
                     } else {
                        /*********************************************
                        * Format the user name in the string
                        * which is likely to be in the format
                        * This command searches for "%20" and "(a) and
                        * replaces them with space and @ respecively
                        **********************************************/
                        $sUser = str_replace(array("%20", "(a)"), array(" ", "@"), $aCommand[1]);
                        $this->writeDebugLog("[debug] checking isuser for ". $sUser);
                        // Now check the user name
                        if ($sUser == "peter"){
                           /*************************************************
                           *Essentially, this is reponding boolean "true"
                           * Create a binary string with unsigned ints 2 & 1)
                           **************************************************/
                           $this->writeLog("[AlfrescoAuth] valid user: ". $sUser);
                           fwrite(STDOUT, pack("nn", 2, 1));
                        } else {
                           /*************************************************
                           *Essentially, this is reponding boolean "false"
                           * Create a binary string with unsigned ints 2 & 0)
                           **************************************************/
                           $this->writeLog("[AlfrescoAuth] invalid user: ". $sUser);
                           fwrite(STDOUT, pack("nn", 2, 0));
                        }
                     }
                     break;
                  case "auth":
                     /******************************************************
                      * ejabberd is asking is this user name / password
                      * allowed to access me?
                      * in our case, the password may be an alfreco ticket
                      * or could be a real password. We work this out later
                      ******************************************************/
                     if (sizeof($aCommand) != 4){
                        $this->writeLog("[AlfrescoAuth] invalid auth command, data missing");
                        /*************************************************
                        *Essentially, this is reponding boolean "false"
                        * Create a binary string with unsigned ints 2 & 0)
                        **************************************************/
                        fwrite(STDOUT, pack("nn", 2, 0));
                     } else {
                        /*********************************************
                        * Format the user name in the string
                        * which is likely to be in the format
                        * This command searches for "%20" and "(a) and
                        * replaces them with space and @ respecively
                        **********************************************/
                        $sUser = str_replace(array("%20", "(a)"), array(" ", "@"), $aCommand[1]);
                        $this->writeDebugLog("[debug] doing auth for ". $sUser);
                        $sPwd = $aCommand[3];
                        /***********************************************
                        * see if the "password" is a real password or
                        * and Alfresco ticket
                        ************************************************/                        
                        $bTicket = false;
                        if(strlen($sPwd) > 7) {  // might be a ticket
                           if(substr($sPwd,0,7) == 'TICKET_') { // Its an Alfresco ticket
                              $bTicket = true;
                           }
                        }
                        if($bTicket) {
                           $this->writeDebugLog("[debug] doing auth by user / ticket for ". $sUser ." / " . $sPwd);
                           $bReturn = true;

                           $checkURL = $sAlfrescoHost . $sAuthTicketScript . $sUser;
                           $checkPOSTFields = "alf_ticket=" . $sPwd;
                           $checkURL .= "?" . $checkPOSTFields;
                           /********************************************************
                           * this enquires on a person the we know does not exist
                           * and returns a JSON in a given format that we can check
                           * If the tickets is bad, it does not return JSON
                           *********************************************************/
                           $this->writeDebugLog("[debug] URL ". $checkURL);
                           try {
                              $ch = curl_init();
                              curl_setopt($ch, CURLOPT_URL,$checkURL);
                              curl_setopt($ch, CURLOPT_HEADER, false);
                              curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                              $json = curl_exec($ch);
                              curl_close($ch);
                              $this->writeDebugLog("[debug] result ". $json);
                              $json_output = json_decode($json,true);
                              $this->writeDebugLog("[debug] decoded ". print_r($json_output,TRUE));
                              /***********************************************************
                              * We may (or may not) have access to the person object
                              * dependent upon who the ticket belongs to and whether or
                              * not repository access is limited to admins. So, we may
                              * have a "person not found" message or we may have a person
                              * JSON object, so deal with either. If we have JSON, then
                              * the ticket we sent was valid which is the main issue
                              ************************************************************/
                              $this->writeDebugLog("[debug] userName ". $json_output["userName"]);
                              if($sUser != $json_output["userName"]) {
                                    $bReturn = false;                                 
                              }
                           } catch(Exception $e) {
                              $this->writeDebugLog("[debug] curl failed ");
                              $bReturn = false;
                           }   
                        } else {
                           $bReturn = true;
                           $this->writeDebugLog("[debug] doing auth by UID PWD for ". $sUser);
                           $checkURL = $sAlfrescoHost . $sAuthUIDPWDScript;
                           $checkPOSTFields = "u=" . $sUser . "&pw=" . $sPwd;
                           $checkURL .= "?" . $checkPOSTFields;
                           /***********************************************************
                           * Invoke the URL. It will return an XML doc that looks like
                           |———————————————————————|
                           |   <?xml version="1.0" encoding="UTF-8"?>                            |
                           |   <ticket>TICKET_XXXXXXXXXXXXXXXXXXXXXXXX</ticket>  |
                           |———————————————————————|   
                           * if everything is OK
                           * or
                           |——————————————————————————————————————|
                           |   <?xml version="1.0" encoding="UTF-8"?>                                                                         |
                           |   <response>                                                                                                     |
                           |     <status>                                                                                                     |
                            |       403                                                                                           |
                           |       <name>Forbidden</name>                                                                                     |
                           |       <description>Server understood the request but refused to fulfill it.</description>                        |
                           |     </status>                                                                                                    |
                           |     <message>00031049 Login failed</message>                                                                     |
                           |     <exception>org.springframework.extensions.webscripts.WebScriptException - 00031049 Login failed</exception>  |
                           |     <callstack>                                                                                                  |
                           |     …… Loads of stuff that doesn't matter for this                                                           |
                           | </callstack>                                                                                                     |
                           |     <server>Community v4.0.0 (a 3755) schema 5,018</server>                                                      |
                           |     <time>Jan 3, 2012 6:33:18 PM</time>                                                                          |
                           |   </response>                                                                                                    |
                           |——————————————————————————————————————|
                           * if the UID / PWD does not constitute a valid combination
                           *******************************************************************/
                           // Use cURL to get the document in case allow_url_fopen is switched off
                           $this->writeDebugLog("[debug] URL ". $checkURL);
                           try {
                              $ch = curl_init();
                              curl_setopt($ch, CURLOPT_URL,$checkURL);
                              curl_setopt($ch, CURLOPT_HEADER, false);
                              curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                              $xml = curl_exec($ch);
                              curl_close($ch);
                           } catch(Exception $e) {
                              $this->writeDebugLog("[debug] curl failed ");
                              $bReturn = false;
                           }   
                           if($bReturn) {
                              $this->writeDebugLog("[debug] XML " . $xml);
                              try {
                                 $response = simplexml_load_string($xml);
                                 $this->writeDebugLog("[debug] Ticket ". $response);                           
                                 if(substr($response,0,7) != 'TICKET_') {
                                    $bReturn = false;
                                 }
                              } catch(Exception $e) {
                                 $this->writeDebugLog("[debug] XML load failed " . $xml);
                                 $bReturn = false;
                              }
                           }
                        }    
                        if ($bReturn){
                           /*************************************************
                           * Essentially, this is reponding boolean "true"
                           * Create a binary string with unsigned ints 2 & 1)
                           **************************************************/
                           $this->writeLog("[AlfrescoAuth] authenticated user ". $sUser ."@". $aCommand[2]);
                           fwrite(STDOUT, pack("nn", 2, 1));
                        } else {
                           /*************************************************
                           *Essentially, this is reponding boolean "false"
                           * Create a binary string with unsigned ints 2 & 0)
                           **************************************************/
                           $this->writeLog("[AlfrescoAuth] authentication failed for user ". $sUser ."@". $aCommand[2]);
                           fwrite(STDOUT, pack("nn", 2, 0));
                        }
                     }
                     break;
                  case "setpass":
                     /****************************************************************
                     * This is asking to set the users password to a given string
                     * This is an Alfresco task and we are not about to do that inside
                     * an interface script
                     *****************************************************************/
                     $this->writeLog("[AlfrescoAuth] setpass command disabled");
                     fwrite(STDOUT, pack("nn", 2, 0));
                     break;
                  default:
                     /*************************************************
                     * If we got here, this is a command that we do not
                     * know about and are not going to do anything with
                     * (other than log the request)
                     ***************************************************/
                     $this->writeLog("[AlfrescoAuth] unknown command ". $aCommand[0]);
                     /*************************************************
                     *Essentially, this is reponding boolean "false"
                     * Create a binary string with unsigned ints 2 & 0)
                     **************************************************/   
                     fwrite(STDOUT, pack("nn", 2, 0));
                     break;
               }
            } else {
               $this->writeDebugLog("[debug] invalid command string");
               /*************************************************
               *Essentially, this is reponding boolean "false"
               * Create a binary string with unsigned ints 2 & 0)
               **************************************************/   
               fwrite(STDOUT, pack("nn", 2, 0));
            }
         }
         unset ($iHeader);
         unset ($aLength);
         unset ($iLength);
         unset($aCommand);
      } while (true);
   }

   public function __destruct()
   {
      $this->writeLog("[AlfrescoAuth] stop");
      
      if (is_resource($this->rLogFile)){
         fclose($this->rLogFile);
      }
   }

   private function writeLog($sMessage)
   {
      if (is_resource($this->rLogFile)) {
         fwrite($this->rLogFile, date("r") ." ". $sMessage ."\n");
      }
   }

   private function writeDebugLog($sMessage)
   {
      if ($this->bDebug){
         $this->writeLog($sMessage);
      }
   }
}

Bingo. Automatic and (so far as I can see) secure login to a defined chatroom inside a dashlet.

This is all brief and high (ish) level. If you want more, post again with questions and I will do my best to answer them.

ttownsend
Champ on-the-rise
Champ on-the-rise
Hey rjohnson,

Out of curiosity (because I would like to integrate real time chat into Share also), I've got jsut a few questions for you, if you don't mind:

1.  Are you using this yourself?
2.  If so, what do you like and dislike about the feature?  Does it work like you want?  Should it do more/less of anything?
3.  Did you ever write an install script or package this as an Alfresco module package (AMP)?  If not, do you need some help?

Cheers,
Trevor

darkmstr
Champ in-the-making
Champ in-the-making
Hey thank you very much rjohnson sorry I didn't answer quickly right now I'm having problems with Alfresco, so when I use this chat I tell you what I got. Thanks again.

rjohnson
Star Contributor
Star Contributor
Trevor

  • I developed this for a client who wanted integrated chat facilities, so we are not using it internally, but the client will if they move ahead with their project. They were delighted when I demonstrated it.

  • My solution (per client request) does chat in a dashlet on the user or site dashboard. I personally think that chat is better off in a separate window, it offers more screen real estate and less connect / re-connect activity as people navigate through Alfresco (although this is pretty invisible to the end user). That said, with the dashlet scenario, you cannot miss a message whereas in a separate window you can.

  • It does what it needs for the client

  • I do have JARs for the dashlets (you are welcome to them) which is the only bit that actually sits in Alfresco, the rest is installed outside of Alfresco. eJabberd has its own installer and the php stuff I just copy in a handful of files.
Best regards

Bob Johnson

vbutacu
Champ on-the-rise
Champ on-the-rise

Hello,

I'm very interested in your project as I'd like to integrate a chat solution to an Alfresco deployment I'm trying to deploy for my IT department. I managed to configure ejabberd server and CandyChat but I got stuck on the dashlet integration part.

Would you be so kind to grant me access to you jar?

Thank you