sábado, 10 de marzo de 2012

Vtiger + popup + llamada entrante + cola

Se supone configurado el módulo PBX Manager del Vtiger CRM correctamente.

Vtiger CRM, permite que un usuario registrado en él, reciba notificaciones de llamadas entrantes a su extensión. Esta extensión, necesariamente deberá ser especificada en el módulo de Configuración del CRM. Para llegar hasta allí, una vez autenticado, haga click en el link " Configuración " y luego click en el link "Usuarios". Seguidamente aparecerán todos los usuarios registrados en el Vtiger. Al hacer click en el nombre de algún usuario, aparecerá un formulario con su información disponible para ser modificada si usted cuenta con los privilegios.

Existe un campo de texto que es para colocar el número de extensión de Elastix. Esta extensión deberá estar previamente registrada en dicho servidor de comunicaciones unificadas. Si usted es usuario del CRM, puede hacer click en link "Mis preferencias" (zona superior derecha de la página) y llenar su propio perfil. Para poder recibir notificaciones de llamadas, deberá introducir un número de extensión, ya registrado en el servidor Elastix, que usted previamante habrá asociado con el CRM, en el campo correspondiente.

Si usted está autenticado en el Vtiger, y su extensión recibe una llamada, se mostrará un "popup" como notificación de que la está recibiendo. Pero existe una limitante. Si su extensión recibe una llamada procedente de una cola... no le llegará a usted la notificación.

El fichero /var/www/http/vtigercrm/cron/modules/PBXManager/AsteriskClient.php tiene como función escuchar los eventos que se llevan acabo en el servidor que contiene a Asterisk. En este fichero, existe la función "asterisk_handleResponse1". Esta función filtra los eventos que hacen "Ring" a través de esta condición:

if(
   (($mainresponse['Event'] == 'Newstate' || $mainresponse['Event'] == 'Newchannel') && ($mainresponse[$state] == 'Ring') 
   || ($mainresponse['Event'] == 'Newstate' && $mainresponse[$state] == 'Ringing'))
)

Y la función "asterisk_handleResponse2" filtra los eventos de la siguiente forma:

if(
     $mainresponse['Event'] == 'Newexten' && (strstr($appdata, "__DIALED_NUMBER") || strstr($appdata, "EXTTOCALL"))
)
Los eventos que no se capturen en asterisk_handleResponse1, pasarán a ser filtrados por asterisk_handleResponse2 y los que no, por asterisk_handleResponse3, el resto de los eventos no son tomados en cuenta.

El flujo de una llamada entrante hasta que se muestra el popup es el siguiente:
(se supone corriendo el fichero AsteriskClient.php)
1-) Llamada entrante dispara los eventos Newstate y/o Newchannel que contienen los headers: ChannelStateDesc (en la version 1.6) o State (en la version 1.4) cuyos valores pueden ser "Ring" o "Ringing". Y son capturados en asterisk_handleResponse1. Ejemplos de eventos:

Event: Newstate
Privilege: call,all
Channel: SIP/11179-0000f060
ChannelState: 4
ChannelStateDesc: Ring
CallerIDNum: 11179
CallerIDName: device
Uniqueid: 1330550725.97318

Event: Newstate
Privilege: call,all
Channel: Local/95155@from-queue-392a;1
ChannelState: 5
ChannelStateDesc: Ringing
CallerIDNum: 11179
CallerIDName: CallCenter79
Uniqueid: 1330550733.97321

2) asterisk_handleResponse1 inserta evento en la tabla vtiger_asteriskincomingevents con atributo flag = -1
3) Se espera otro evento que pase por filtro de la función asterisk_handleResponse2. Si hay algún evento que cumpla con la condición, se actualiza la tabla vtiger_asteriskincomingevents modificando el atributo flag = 0 donde el atributo Uniqueid se igual al de algún evento anteriormente insertado por la función asterisk_handleResponse1. Se verifica que este evento modificado tenga el atributo from_number (número del llamante) con algún número de teléfono. Si es así y se cumple que el atributo to_number posee un número de extensión registrado en el servidor donde corre Asterisk:
4) Se procede a insertar en la tabla vtiger_asteriskincomingcalls desde donde se leerán las llamadas entrantes para poder lanzar el popup.

5) La función asterisk_IncomingEventCleanup se encarga de borrar los registros viejos de vtiger_asteriskincomingevents.

Hasta aqui llega el flujo de la llamada desde el punto de vista del fichero AsteriskClient.php.

Solución:



La única condición para que esto funcione es que la cola desde donde se reciben las llamadas, sus miembros no sean agentes, si no extensiones. Sucede que he revisado los eventos generados por asterisk cuando se reciben llamadas en las extensiones que están conectados los agentes, ningún evento contiene el número de extensión que recibe el ring del llamante. Pero sin embargo cuando la cola, como miembros, no tiene agentes, sino extensiones, pues se generan eventos que contienen el número de extensión destino, o sea el miembro de la cola que recibe la llamada. De esta forma el popup sí puede mostrarse en la página del usuario que tiene asociada esta extensión, incluso las llamadas directas a su extensión. Esta solución no impide que se reciban notificaciones por llamadas directas.

El código, no me dediqué a optimizarlo, en cuanto hallé la solución, pues enseguida la estoy publicando.

Por qué no funciona el popup cuando la llamada proviene de una cola?

1) En la función handleResponse1  cuando una llamada hace Ring, o está haciendo Ringing se filtra por el evento "Newstate" o "Newchannel". Eso está bien, pero sucede, que las cabeceras CallerIDName (from_name) y CallerIDNum (from_number) están vacías cuando provienen de una cola. Y eso es un inconveniente porque se guarda el evento en la tabla asteriskincomingevents con estos datos en null y cuando se le consulta no hay forma de saber quien llama y entonces la notificación no se puede llevar cabo.

2) En la función handleResponse2  cuando una llamada viene de una cola, el evento que se dispara es "Dial". Esta función no tenía filtro para este evento en la la primera condicional "if" de la función. Y es precisamente este evento el que contiene la información del llamante y el receptor de la llamada. Una vez conocido esto, es cuando se pueden actualizar los datos del evento en la tablaasteriskincomingevents teniendo en cuenta que ya se había insertado el evento, con estos datos vacíos, desde la función handleResponse1 . Una vez encontrado el Uniqueid o UniqueID del registrado en la función anterior, se actualizan los atributos from_name,  from_number y to_number de la tabla asteriskincomingevents.
Y con estos datos actualizados ya es posible isertar en la tabla asteriskincomingcalls, desde la cual, el script TraceIncomingCall.php puede saber las llamadas entrantes para poder construir el popup con los datos.

Las funciones asterisk_handleResponse1 y asterisk_handleResponse2 están descritas debajo, con las modificaciones que permiten que funcione el popup.


function asterisk_handleResponse1($mainresponse, $state, $adb)
{
    if(
        (($mainresponse['Event'] == 'Newstate' || $mainresponse['Event'] == 'Newchannel') && ($mainresponse[$state] == 'Ring'))
        || ($mainresponse['Event'] == 'Newstate' && $mainresponse[$state] == 'Ringing')
    )
    {
        $channel = $mainresponse['Channel'];
        $uniqueid = $mainresponse['Uniqueid'];
     
        if(!empty($mainresponse['CallerID']))
            $callerNumber = $mainresponse['CallerID'];
     
        elseif(!empty($mainresponse['CallerIDNum']))
            $callerNumber = $mainresponse['CallerIDNum'];
     
        if(!empty($mainresponse['CallerIDName']))
            $callerName = $mainresponse['CallerIDName'];
     
        $select = "SELECT COUNT(uid) as cant FROM vtiger_asteriskincomingevents WHERE flag = -1 and uid = ?";
        $resultado = $adb->pquery($select, array($uniqueid));
        $cant = $adb->query_result($resultado, 0, "cant");
        if($cant <= 0)
        {
            $sql = "INSERT INTO vtiger_asteriskincomingevents
            (uid, channel, from_number, from_name, timer, flag) VALUES(?,?,?,?,?,?)";
            $resultado = $adb->pquery($sql, array($uniqueid, $channel, $callerNumber, $callerName, time(), -1));
        }
        else
        {
            if(!empty($callerNumber) && !is_null($callerNumber))
            {
                $update = "UPDATE vtiger_asteriskincomingevents SET from_number=?, timer=?, from_name=? WHERE uid=$uniqueid";
                                        $adb->pquery($update, array($callerNumber, time(), $callerName));
        }
        return false;
    }
    return true;
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

function asterisk_handleResponse2($mainresponse, $adb, $asterisk, $state)
{
    $appdata = $mainresponse['AppData'];
    $uniqueid = $channel = $callerType = $extension = $caller = $name = null;
    $parseSuccess = false;
    $dial_event = false;
    if(
        ($mainresponse['Event'] == 'Newexten' && ($mainresponse['Application'] == 'Dial' || strstr($appdata, "EXTTOCALL")))
        || ($mainresponse['Event'] == 'Dial' && $mainresponse['SubEvent'] == "Begin")
      )
    {
        $uniqueid = $mainresponse['Uniqueid'];
        if($mainresponse['Event'] == 'Dial')
            $uniqueid = $mainresponse['UniqueID'];
        $channel = $mainresponse['Channel'];
        $splits = explode('/', $channel);
        $callerType = $splits[0];
        $splits = explode('=', $appdata);
        $extension = $splits[1];
        if($mainresponse['Event'] == 'Dial' && $mainresponse['SubEvent'] == "Begin" && is_numeric($extension = $mainresponse['Dialstring']))
        {
            $extension = $mainresponse['Dialstring'];
            $caller = $mainresponse['CallerIDNum'];
            $name = $mainresponse['CallerIDName'];
            $dial_event = true;
        }
        $parseSuccess = true;
    }
    else
    if($mainresponse['Event'] == 'OriginateResponse')
    {
        //if the event is OriginateResponse then its an outgoing call and set the flag to 1, so that AsteriskClient does not pick up as incoming call
        $uniqueid = $mainresponse['Uniqueid'];
        $adb->pquery("UPDATE vtiger_asteriskincomingevents set flag = 1 WHERE uid = ?", array($uniqueid));
    }

    if($parseSuccess)
    {
        if(checkExtension($extension, $adb))
        {
            $sql = "UPDATE vtiger_asteriskincomingevents SET to_number=?, callertype=?, timer=?, flag=? WHERE uid=?";
            $adb->pquery($sql, array($extension, $callerType, time(), 0, $uniqueid));
            if($dial_event)
            {
               $sql = "UPDATE vtiger_asteriskincomingevents SET from_number=?, to_number=?, from_name=?,callertype=?, timer=?, flag=? WHERE uid=?";
                $adb->pquery($sql, array($caller, $extension, $name,$callerType, time(), 0, $uniqueid));
            }
           $callerinfo = $adb->pquery("SELECT from_number,from_name FROM vtiger_asteriskincomingevents WHERE uid = ?",array($uniqueid));
            if($adb->num_rows($callerinfo) > 0)
            {
                $callerNumber = $adb->query_result($callerinfo, 0, "from_number");
                $callerName = $adb->query_result($callerinfo, 0, "from_name");
                if(empty($callerNumber) || $callerNumber == '0')
                {
                    $sql = "UPDATE vtiger_asteriskincomingevents SET flag=? WHERE uid=?";
                    $adb->pquery($sql, array(-1, $uniqueid));
                }
                else
                {
                    $query = "INSERT INTO vtiger_asteriskincomingcalls (refuid, from_number, from_name, to_number, callertype, flag, timer) VALUES(?,?,?,?,?,?,?)";
                    $adb->pquery($query,array($uniqueid, $callerNumber, $callerName, $extension, $callerType, 0, time()));
                }
            }
        }
        return false;
    }
    return true;
}