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
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:
- Make backup of Vtiger CRM code and database
- Open folder where your Vtiger CRM is installed (e.g., /var/www/vtigercrm/)
- Copy the downloaded tar.gz file with the patch to Vtiger CRM root folder. System administrator rights may be required to apply the patch.
- Untar:
# tar xzf salesplatform-asteriskconnector-vtiger7-201711.patch.tar.gz
- 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!
- Apply the patch:
# patch -p 1 < salesplatform-asteriskconnector-vtiger7-201711.patch
- 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.
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()