Advanced Asterisk/FreePBX Connector for Vtiger CRM 7.0.1 and SalesPlatform Vtiger CRM 7.0.1

Note: for older Vtiger CRM version 6.5 refer to this page
SalesPlatform Advanced Asterisk/FreePBX Connector supports Asterisk from 1.8 up to 14 and FreePBX from 2 up to 13. Key advantages comparing to the original Vtiger CRM Asterisk Connector:
Supports both Asterisk and FreePBX
Supports FreePBX queues and ring groups
Allows outgoing calls through Asterisk/FreePBX dialplan
Detects connected line for incoming calls

Installation Instructions

First, check that the following software is properly installed in you PBX server (may be separate from CRM server):
  • Linux
  • Asterisk/FreePBX
  • Java JDK/JRE version 1.7 or higher
Download the latest version of SalesPlatform Asterisk Connector. Unzip to the target folder on your server, e.g. /usr/local/SPAsteriskConnector.
# mkdir /usr/local/SPAsteriskConnector
# cd /usr/local/SPAsteriskConnector
# unzip /path/to/SPAsteriskConnector-1.4.2.zip
# chmod a+x bin/*
Configure Vtiger PBX Manager module (CRM Settings -> Integration -> PBXManager).
  • Vtiger Asterisk App URL: ip address and TCP port of the connector, e.g. http://192.168.1.1:5000
  • Outbound Context: Asterisk context for placing outbound calls. In FreePBX this is usually called from-internal.
  • Outbound Trunk: not needed for SalesPlatform Asterisk Connector, so enter any string (this field is not present in SalesPlatform Vtiger CRM distro)
  • Vtiger Secret Key: the key will be auto-generated. Copy this value to paste in the connector configuration file (see below).
Edit settings in conf/SPVtigerAsteriskConnector.properties:

 

Parameter Description
ServerIP IP address that connector will listen (0.0.0.0 for any IP)
ServerPort Connector TCP port (e.g. 5000)
AsteriskAppDBPath Destination folder for DB files (e.g. /var/lib/asteriskConnector)
AsteriskServerIP IP address of Asterisk (e.g. 127.0.0.1)
AsteriskServerPort TCP port of Asterisk AMI service, as configured in Asterisk manager.conf (e.g.: 5038)
AsteriskUsername Manager user as configured in Asterisk manager.conf
AsteriskPassword Manager user password as configured in Asterisk manager.conf
VtigerURL URL of your Vtiger instance (e.g. http://192.168.1.1/vtigercrm)
VtigerSecretKey Copy and paste Vtiger Secret Key from Vtiger PBX Manager module settings here

 

Then create the folder specified in AsteriskAppDBPath parameter.
# mkdir /var/lib/asteriskConnector
Manager user must be configured in Asterisk /etc/asterisk/manager.conf configuration file.
[general]
enabled = yes
port = 5038
bindaddr = 127.0.0.1

[admin]
secret = your_admin_password
deny = 0.0.0.0/0.0.0.0
permit = 127.0.0.1/255.255.255.0
read = system,call,log,verbose,command,agent,user,config,command,dtmf,reporting,cdr,dialplan,originate
write = system,call,log,verbose,command,agent,user,config,command,dtmf,reporting,cdr,dialplan,originate

Applying Patch to Vtiger CRM (Not Needed for SalesPlatform Vtiger CRM!)

To make Advanced Asterisk Connector work with the original Vtiger CRM 7.0.1 you need to download and apply a patch. This step is not needed if you use SalesPlatform Vtiger CRM distro.

 

The patch contains:

 

  • Source code update for Vtiger CRM 7.0.1 PBXManager module to interact with SalesPlatform Asterisk connector
  • Database update script that adds ‘Incoming Line Name’ field to PBX Manager module

 

Installation manual:

 

  1. Make backup of Vtiger CRM code and database
  2. Open folder where your Vtiger CRM is installed (e.g., /var/www/vtigercrm/)
  3. Copy the downloaded tar.gz file with the patch to Vtiger CRM root folder. System administrator rights may be required to apply the patch.
  4. Untar:
    # tar xzf salesplatform-asteriskconnector-vtiger7-201711.patch.tar.gz
  5. Check the patch compatibility:
    # patch --dry-run -p 1 < salesplatform-asteriskconnector-vtiger7-201711.patch

    You should see output like:

        checking file file1
        checking file file2

    Warning! If you see something like: “Hunk #1 FAILED”, then do not proceed with the update!

  6. Apply the patch:
    # patch -p 1 < salesplatform-asteriskconnector-vtiger7-201711.patch
  7. Run SQL-script
    # mysql -u username -p databasename < add-incomingline-pbxmanager.sql

Starting and Stopping the Connector

To start the connector run bin/start.sh shell script in the installation folder. To stop the connector run bin/stop.sh.
Note that bin/start.sh and bin/stop.sh must be set runnable for the active Linux user. If the user is not root, you should also set write permissions to the folder that is specified in AsteriskAppDBPath parameter and log subfolder in the installation folder.
To automatically start the connector at system boot you can add /usr/local/SPAsteriskConnector/bin/start.sh to /etc/rc.local.

Configuring Asterisk/FreePBX for Call Recording

Changes in /etc/asterisk/cdr_manager.conf:
[general]
enabled = yes

[mappings]
recordingpath => recordingpath

Pure Asterisk Configuration (Not Using FreePBX)

Add call recording directives to Asterisk dialplan (/etc/asterisk/extensions.conf), e.g.:
exten = _X.,1,Set(CALLFILENAME=${STRFTIME(${EPOCH},,OUT_%d%m%Y_%H-%M)}_${CALLERID(num)}_${EXTEN})
exten = _X.,2,MixMonitor(/var/spool/asterisk/monitor/${CALLFILENAME}.wav,b)
exten = _X.,3,Set(CDR(recordingpath)=/var/spool/asterisk/monitor/${CALLFILENAME}.wav)
exten = _X.,4,Dial(SIP/${EXTEN})

FreePBX Configuration

Create /etc/asterisk/extensions_override_freepbx.conf file, copy FreePBX macros: macro-parked-call, parkedcallstimeout, sub-record-cancel, sub-record-check. Then add directives for recordingpath as in the following example for FreePBX 2.11 (changed lines marked by ‘SalesPlatform override’ comments):
[macro-parked-call]
exten => s,1,AGI(parkfetch.agi,${ARG1})
exten => s,n,GotoIf($["${REC_STATUS}" != "RECORDING"]?next)
exten => s,n,Set(AUDIOHOOK_INHERIT(MixMonitor)=yes)
exten => s,n,Set(CDR(recordingfile)=${CALLFILENAME}.${MON_FMT})
exten => s,n,Set(CDR(recordingpath)=${ASTSPOOLDIR}/monitor/${MIXMON_DIR}${YEAR}/${MONTH}/${DAY}/${CALLFILENAME}.${MON_FMT})         ;SalesPlatform override
exten => s,n,MixMonitor(${MIXMON_DIR}${YEAR}/${MONTH}/${DAY}/${CALLFILENAME}.${MIXMON_FORMAT},a,${MIXMON_POST})
exten => s,n(next),Set(CCSS_SETUP=TRUE)
exten => s,n,Macro(user-callerid,)
exten => s,n,GotoIf($["${ARG1}" = "" | ${DIALPLAN_EXISTS(${IF($["${ARG2}" = "default"]?parkedcalls:${ARG2})},${ARG1},1)} = 1]?pcall)
exten => s,n,ResetCDR()
exten => s,n,NoCDR()
exten => s,n,Wait(1)
exten => s,n,Playback(pbx-invalidpark)
exten => s,n,Wait(1)
exten => s,n,Hangup
exten => s,n(pcall),Noop(User: ${CALLERID(all)} attempting to pick up Parked Call Slot ${ARG1})
exten => s,n,ParkedCall(${ARG1},${ARG2})

exten => h,1,Macro(hangupcall,)

[parkedcallstimeout]
exten => _[0-9a-zA-Z*#].,1,Set(PARK_TARGET=${EXTEN})
exten => _[0-9a-zA-Z*#].,n,GotoIf($["${REC_STATUS}" != "RECORDING"]?next)
exten => _[0-9a-zA-Z*#].,n,Set(AUDIOHOOK_INHERIT(MixMonitor)=yes)
exten => _[0-9a-zA-Z*#].,n,MixMonitor(${MIXMON_DIR}${YEAR}/${MONTH}/${DAY}/${CALLFILENAME}.${MIXMON_FORMAT},a,${MIXMON_POST})
exten => _[0-9a-zA-Z*#].,n,Set(CDR(recordingpath)=${ASTSPOOLDIR}/monitor/${MIXMON_DIR}${YEAR}/${MONTH}/${DAY}/${CALLFILENAME}.${MON_FMT})       ;SalesPlatform override
exten => _[0-9a-zA-Z*#].,n(next),Goto(park-return-routing,${PARKINGSLOT},1)

[sub-record-cancel]
exten => s,1,Set(__REC_POLICY_MODE=${REC_POLICY_MODE_SAVE})
exten => s,n,ExecIf($["${REC_STATUS}"!="RECORDING"]?Return())
exten => s,n,StopMixMonitor()
exten => s,n,Set(__REC_STATUS=)
exten => s,n,Set(MON_BASE=${IF($[${LEN(${MIXMON_DIR})}]?${MIXMON_DIR}:${ODASTSPOOLDIR}/monitor/)}${YEAR}/${MONTH}/${DAY}/)
exten => s,n,Set(__MON_FMT=${IF($[${LEN(${MIXMON_FORMAT})}]?${IF($["${MIXMON_FORMAT}"="wav49"]?WAV:${MIXMON_FORMAT})}:wav)})
exten => s,n,ExecIf($[${LEN(${CALLFILENAME})} & ${STAT(f,${MON_BASE}${CALLFILENAME}.${MON_FMT})}]?System(rm -f ${MON_BASE}${CALLFILENAME}.${MON_FMT}))
exten => s,n,Set(__CALLFILENAME=)
exten => s,n,Set(CDR(recordingfile)=)
exten => s,n,Set(CDR(recordingpath)=)                             ;SalesPlatform override
exten => s,n,Return()

[sub-record-check]
exten => s,1,Set(REC_POLICY_MODE_SAVE=${REC_POLICY_MODE})
exten => s,n,GotoIf($["${BLINDTRANSFER}" = ""]?check)
exten => s,n,ResetCDR()
exten => s,n,GotoIf($["${REC_STATUS}" != "RECORDING"]?check)
exten => s,n,Set(AUDIOHOOK_INHERIT(MixMonitor)=yes)
exten => s,n,MixMonitor(${MIXMON_DIR}${YEAR}/${MONTH}/${DAY}/${CALLFILENAME}.${MIXMON_FORMAT},a,${MIXMON_POST})
exten => s,n(check),Set(__MON_FMT=${IF($["${MIXMON_FORMAT}"="wav49"]?WAV:${MIXMON_FORMAT})})
exten => s,n,GotoIf($["${REC_STATUS}"!="RECORDING"]?next)
exten => s,n,Set(CDR(recordingfile)=${CALLFILENAME}.${MON_FMT})
exten => s,n,Set(CDR(recordingpath)=${ASTSPOOLDIR}/monitor/${MIXMON_DIR}${YEAR}/${MONTH}/${DAY}/${CALLFILENAME}.${MON_FMT})       ;SalesPlatform override
exten => s,n,Return()
exten => s,n(next),ExecIf($[!${LEN(${ARG1})}]?Return())
exten => s,n,ExecIf($["${REC_POLICY_MODE}"="" & "${ARG3}"!=""]?Set(__REC_POLICY_MODE=${ARG3}))
exten => s,n,GotoIf($["${REC_STATUS}"!=""]?${ARG1},1)
exten => s,n,Set(__REC_STATUS=INITIALIZED)
exten => s,n,Set(NOW=${EPOCH})
exten => s,n,Set(__DAY=${STRFTIME(${NOW},,%d)})
exten => s,n,Set(__MONTH=${STRFTIME(${NOW},,%m)})
exten => s,n,Set(__YEAR=${STRFTIME(${NOW},,%Y)})
exten => s,n,Set(__TIMESTR=${YEAR}${MONTH}${DAY}-${STRFTIME(${NOW},,%H%M%S)})
exten => s,n,Set(__FROMEXTEN=${IF($[${LEN(${AMPUSER})}]?${AMPUSER}:${IF($[${LEN(${REALCALLERIDNUM})}]?${REALCALLERIDNUM}:unknown)})})
exten => s,n,Set(__CALLFILENAME=${ARG1}-${ARG2}-${FROMEXTEN}-${TIMESTR}-${UNIQUEID})
exten => s,n,Goto(${ARG1},1)

exten => rg,1,GosubIf($["${REC_POLICY_MODE}"="always"]?record,1(${EXTEN},${REC_POLICY_MODE},${FROMEXTEN}))
exten => rg,n,Return()

exten => force,1,GosubIf($["${REC_POLICY_MODE}"="always"]?record,1(${EXTEN},${REC_POLICY_MODE},${FROMEXTEN}))
exten => force,n,Return()

exten => q,1,GosubIf($["${REC_POLICY_MODE}"="always"]?recq,1(${EXTEN},${ARG2},${FROMEXTEN}))
exten => q,n,Return()

exten => out,1,ExecIf($["${REC_POLICY_MODE}"=""]?Set(__REC_POLICY_MODE=${DB(AMPUSER/${FROMEXTEN}/recording/out/external)}))
exten => out,n,GosubIf($["${REC_POLICY_MODE}"="always"]?record,1(exten,${ARG2},${FROMEXTEN}))
exten => out,n,Return()

exten => exten,1,GotoIf($["${REC_POLICY_MODE}"!=""]?callee)
exten => exten,n,Set(__REC_POLICY_MODE=${IF($[${LEN(${FROM_DID})}]?${DB(AMPUSER/${ARG2}/recording/in/external)}:${DB(AMPUSER/${ARG2}/recording/in/internal)})})
exten => exten,n,GotoIf($["${REC_POLICY_MODE}"="dontcare"]?caller)
exten => exten,n,GotoIf($["${DB(AMPUSER/${FROMEXTEN}/recording/out/internal)}"="dontcare" | "${FROM_DID}"!=""]?callee)
exten => exten,n,ExecIf($[${LEN(${DB(AMPUSER/${FROMEXTEN}/recording/priority)})}]?Set(CALLER_PRI=${DB(AMPUSER/${FROMEXTEN}/recording/priority)}):Set(CALLER_PRI=0))
exten => exten,n,ExecIf($[${LEN(${DB(AMPUSER/${ARG2}/recording/priority)})}]?Set(CALLEE_PRI=${DB(AMPUSER/${ARG2}/recording/priority)}):Set(CALLEE_PRI=0))
exten => exten,n,GotoIf($["${CALLER_PRI}"="${CALLEE_PRI}"]?${REC_POLICY}:${IF($[${CALLER_PRI}>${CALLEE_PRI}]?caller:callee)})
exten => exten,n(callee),GosubIf($["${REC_POLICY_MODE}"="always"]?record,1(${EXTEN},${ARG2},${FROMEXTEN}))
exten => exten,n,Return()
exten => exten,n(caller),Set(__REC_POLICY_MODE=${DB(AMPUSER/${FROMEXTEN}/recording/out/internal)})
exten => exten,n,GosubIf($["${REC_POLICY_MODE}"="always"]?record,1(${EXTEN},${ARG2},${FROMEXTEN}))
exten => exten,n,Return()

exten => conf,1,Gosub(recconf,1(${EXTEN},${ARG2},${ARG2}))
exten => conf,n,Return()

exten => page,1,GosubIf($["${REC_POLICY_MODE}"="always"]?recconf,1(${EXTEN},${ARG2},${FROMEXTEN}))
exten => page,n,Return()

exten => record,1,Set(AUDIOHOOK_INHERIT(MixMonitor)=yes)
exten => record,n,MixMonitor(${MIXMON_DIR}${YEAR}/${MONTH}/${DAY}/${CALLFILENAME}.${MIXMON_FORMAT},,${MIXMON_POST})
exten => record,n,Set(__REC_STATUS=RECORDING)
exten => record,n,Set(CDR(recordingfile)=${CALLFILENAME}.${MON_FMT})
exten => record,n,Set(CDR(recordingpath)=${ASTSPOOLDIR}/monitor/${MIXMON_DIR}${YEAR}/${MONTH}/${DAY}/${CALLFILENAME}.${MON_FMT})              ;SalesPlatform override
exten => record,n,Return()

exten => recq,1,Set(AUDIOHOOK_INHERIT(MixMonitor)=yes)
exten => recq,n,Set(MONITOR_FILENAME=${MIXMON_DIR}${YEAR}/${MONTH}/${DAY}/${CALLFILENAME})
exten => recq,n,MixMonitor(${MONITOR_FILENAME}.${MIXMON_FORMAT},${MONITOR_OPTIONS},${MIXMON_POST})
exten => recq,n,Set(__REC_STATUS=RECORDING)
exten => recq,n,Set(CDR(recordingfile)=${CALLFILENAME}.${MON_FMT})
exten => recq,n,Set(CDR(recordingpath)=${ASTSPOOLDIR}/monitor/${MIXMON_DIR}${YEAR}/${MONTH}/${DAY}/${CALLFILENAME}.${MON_FMT})                ;SalesPlatform override
exten => recq,n,Return()

exten => recconf,1,Set(__CALLFILENAME=${IF($[${MEETME_INFO(parties,${ARG2})}]?${DB(RECCONF/${ARG2})}:${ARG1}-${ARG2}-${ARG3}-${TIMESTR}-${UNIQUEID})})
exten => recconf,n,ExecIf($[!${MEETME_INFO(parties,${ARG2})}]?Set(DB(RECCONF/${ARG2})=${CALLFILENAME}))
exten => recconf,n,Set(MEETME_RECORDINGFILE=${IF($[${LEN(${MIXMON_DIR})}]?${MIXMON_DIR}:${ASTSPOOLDIR}/monitor/)}${YEAR}/${MONTH}/${DAY}/${CALLFILENAME})
exten => recconf,n,Set(MEETME_RECORDINGFORMAT=${MIXMON_FORMAT})
exten => recconf,n,ExecIf($["${REC_POLICY_MODE}"!="always"]?Return())
exten => recconf,n,Set(__REC_STATUS=RECORDING)
exten => recconf,n,Set(CDR(recordingfile)=${CALLFILENAME}.${MON_FMT})
exten => recconf,n,Set(CDR(recordingpath)=${ASTSPOOLDIR}/monitor/${MIXMON_DIR}${YEAR}/${MONTH}/${DAY}/${CALLFILENAME}.${MON_FMT})             ;SalesPlatform override
exten => recconf,n,Return()