#!/usr/bin/env php connect("127.0.0.1", $config["AMPMGRUSER"] , $config["AMPMGRPASS"])) { exit (1); } $priority = get_var( $AGI, "priority" ) + 1; debug( "priority is $priority", 1 ); // Caller ID info is stored in $request in AGI class, passed from Asterisk $cidnum = $AGI->request['agi_callerid']; $cidname = $AGI->request['agi_calleridname']; debug("Caller ID name is '$cidname' number is '$cidnum'", 1); // From this point forward, Set KEEPCID in the channel so subsequent calls, CF, etc. retain the incoming // CID that get sent down channel local. $AGI->set_variable('__KEEPCID','TRUE'); $timer = get_var( $AGI, "ARG1" ); $dialopts = get_var( $AGI, "ARG2" ); $rgmethod = get_var( $AGI, "RingGroupMethod" ); $pr_dialstatus = get_var( $AGI, "PR_DIALSTATUS" ); $nodest = get_var( $AGI, "NODEST" ); if (empty($nodest)) $nodest = ''; $use_confirmation = get_var( $AGI, "USE_CONFIRMATION" ); $ringgroup_index = get_var( $AGI, "RINGGROUP_INDEX" ); if (empty($timer)) $timer = 0; if (empty($dialopts)) $dialopts = ""; if (empty($rgmethod)) $rgmethod = "none"; if (empty($use_confirmation)) $use_confirmation = "FALSE"; debug("Methodology of ring is '$rgmethod'", 1); debug("USE_CONFIRMATION: '$use_confirmation'", 4); debug("RINGGROUP_INDEX: '$ringgroup_index'", 4); // reset the ringgroup method to its fundamental algorithm and pull out if // master mode. if ($rgmethod == "ringall-prim") { $rgmethod = "ringall"; $mastermode = 1; } elseif ($rgmethod == "hunt-prim") { $rgmethod = "hunt"; $mastermode = 1; } elseif ($rgmethod == "memoryhunt-prim") { $rgmethod = "memoryhunt"; $mastermode = 1; } else { $mastermode = 0; $pr_dialstatus = ""; // not relevant if not mastermode, clear it so dnd doesn't propagate, and other } // call confirmation only works with ringall and ringall-prim. The javascripts in ringgroups // and follow-me should enforce this. If that has been overridden then force ringall. // Keep this code after the matermode check above, since they will at least get mastermode // if they set a -prim mode in one of the others // if ( ($use_confirmation != "FALSE") && ($rgmethod != "ringall") ) { debug("Unsupported RingMethod reset to: '$rgmethod'", 1); $rgmethod = "ringall"; } // Start with Arg Count set to 3 as two args are used $arg_cnt = 3; while($arg = get_var($AGI,"ARG". $arg_cnt)) { if ($arg == '-') { // not sure why, dialparties will get stuck in a loop if noresponse debug("get_variable got a \"noresponse\"! Exiting",3); exit($arg_cnt); } $extarray = split( '-', $arg ); foreach ( $extarray as $k ) { $ext[] = $k; debug("Added extension $k to extension map", 3); } $arg_cnt++; } // IF THE FIRST EXTENSION IS CALL FORWARD ENABLED (put in logic) then we don't do master mode // which means we reset the flag here after detecting that and just say we are not in master // mode and all is well. That means the loop below needs to be modified to detect the first // extension and do this if the case. // Check for call forwarding first // If call forward is enabled, we use chan_local // Hacky. We should be using an associative array, shouldn't we? $count = 0; foreach( $ext as $k) { $cf = $AGI->database_get('CF',$k); $cf = $cf['data']; if (strlen($cf)) { // append a hash sign so we can send out on chan_local below. $ext[$count] = $cf.'#'; debug("Extension $k has call forward set to $cf", 1); // if this is the primary extension and CF enabled, then cancel mastermode // whether it is or not, no need to check. // if ($count == 0) { $mastermode = 0; $pr_dialstatus = ""; // not relevant if not mastermode, clear it so dnd doesn't propagate, and other debug("Primary ext is CF so disabling mastermode if it was set", 4); } } else debug("Extension $k cf is disabled", 3); $count++; } // IF DND AND we process it as a DND (no CF enabled) then we need to some how flag that ALL THE REST // should now be ignored and not processed if in master mode (and this primary). Do this by setting some // sort of flag that says master mode DND so skip everything else below (set them all to ""). // // Hacky. $count = 0; $dndprimary = 0; // Now check for DND foreach ( $ext as $k ) { //if ( !preg_match($k, "/\#/", $matches) ) if ( (strpos($k,"#")==0) ) { // no point in doing if cf is enabled $dnd = $AGI->database_get('DND',$k); $dnd = $dnd['data']; if (strlen($dnd) || $pr_dialstatus == "BUSY") { debug("Extension $k has do not disturb enabled, or followme pre-ring returned busy", 1); unset($ext[$count]); $AGI->set_variable('DIALSTATUS','BUSY'); // if this is primary set dndprimary and figure out if needed below // if ($count == 0 && $mastermode) { $dndprimary = 1; debug("Primary extension is DND, so if mastermode, all should be dnd", 4); } } else debug("Extension $k do not disturb is disabled", 3); } $count++; } // Main calling loop $skipremaining = 0; // used to allow primary to ring but skip the rest $ds = ''; foreach ( $ext as $k ) { // mastermode description: // // if mastermode is set then the first extension will be examined and mastermode will be reset so that the others // are left alone. If the remaining extensions are not to be tried, skpremaining will be set to 1 which will // result in dndprimary being set to 1 thus diabling the remaining list. // // if cf unconditional was already detected on the primary, then mastermode will have been reset at this point // since that will negate the mastermode concpet. // // if dnd was set on the primary then dndprimary will already be set resulting in a completly blanked out list // since dnd on the primary means don't bother me on any. // Don't bother checking these if we will be blanking the extnum anyhow if ($skipremaining == 1) { $dndprimary = 1; } if ($dndprimary == 0) { // TODO what are these comments...? do we need to remove them...? $extnum = $k; $exthascw = $AGI->database_get('CW', $extnum);// ? 1 : 0; $exthascw = $exthascw['data']? 1:0; $extcfb = $AGI->database_get('CFB', $extnum);//? 1 : 0; $extcfb = $extcfb['data']; $exthascfb = (strlen($extcfb) > 0) ? 1 : 0; $extcfu = $AGI->database_get('CFU', $extnum);// ? 1 : 0; $extcfu = $extcfu['data']; $exthascfu = (strlen($extcfu) > 0) ? 1 : 0; // Dump details in level 4 debug("extnum: $extnum",4); debug("exthascw: $exthascw",4); debug("exthascfb: $exthascfb",4); debug("extcfb: $extcfb",4); debug("exthascfu: $exthascfu",4); debug("extcfu: $extcfu",4); // check if mastermode and then reset here. If mastermode, this will be the first extension so // the state is checked and a decision is made as to what to do. We have gotten all the cf variables // above. If CF unconditional was set, we never get here because we alread reset mastermode. If DND // were set then we never get here becasue didprimary was set if ($mastermode == 1) { $mastermode = 0; $extstate = is_ext_avail($extnum); debug("Extension $extnum has ExtensionState: $extstate",1); if ( ($exthascw == 1) && ($extstate == 1) ) { // process this one extension but the remaining should be skipped since there is cw and // the extension is occupied. This will try this extension but not the others. $skipremaining = 1; debug("In mastermode with cw enabled so $extnum will be tried and others skipped",4); } elseif ( ($exthascw == 0) && ($extstate == 1) ) { // no cw, ext is busy. So if cfb is set, it will forward there and if not, it will be // ignored as normal behavior. In either case, we skip the remaining numbers. $skipremaining = 1; debug("In mastermode with cw disabled so $extnum will be processed in case cfb set",4); } // All other cases should act like normal. Unavailable, not busy, ringing, etc. // should not be effected } } // end if ($dndprimary == 0) else // $dndprimary == 1 so clear the extension { // clear the current extension if dndprimary has been set. This will only be the case if in mastermode so no need to check // that. Use this to skip remaining extensions also if just ringing the primary. $extnum = ''; } // if CF is not in use and $dndprimary is not set otherwise $extnum has been cleared and nothing to do // if ( (strpos($k,"#")==0) && $dndprimary == 0) { // CW is not in use or CFB is in use on this extension, then we need to check! if ( ($exthascw == 0) || ($exthascfb == 1) || ($exthascfu == 1) ) { // get ExtensionState: 0-idle; 1-busy; 4-unavail; 8-ringing <--- these are unconfirmed $extstate = is_ext_avail($extnum); debug("Extension $extnum has ExtensionState: $extstate",1); if ( ($exthascfu == 1) && ($extstate == 4) ) // Ext has CFU and is Unavailable { // If part of a ring group, then just do what CF does, otherwise needs to // drop back to dialplan with NOANSWER if ($rgmethod != '' && $rgmethod != 'none') { debug("Extension $extnum has call forward on no answer set and is unavailable and is part of a Ring Group forwarding to '$extcfu'",1); $extnum = $extcfu . '#'; # same method as the normal cf, i.e. send to Local } else { debug("Extension $extnum has call forward on no answer set and is unavailable",1); $extnum = ''; $AGI->set_variable('DIALSTATUS','NOANSWER'); } } elseif ( ($exthascw == 0) || ($exthascfb == 1) ) { debug("Checking CW and CFB status for extension $extnum",3); if ($extstate > 0 && $extstate != 4) { // extension in use debug("Extension $extnum is not available to be called", 1); if ($exthascfb == 1) // extension in use { // CFB is in use debug("Extension $extnum has call forward on busy set to $extcfb",1); $extnum = $extcfb . '#'; # same method as the normal cf, i.e. send to Local } elseif ($exthascw == 0) { // CW not in use debug("Extension $extnum has call waiting disabled",1); $extnum = ''; $AGI->set_variable('DIALSTATUS','BUSY'); } else { debug("Extension $extnum has call waiting enabled",1); } } } elseif ($extstate < 0) { // -1 means couldn't read status usually due to missing HINT debug("ExtensionState for $extnum could not be read...assuming ok",3); } else { debug("Extension $extnum is available",1); } } elseif ( ($exthascw == 1) && ($rgmethod == 'firstnotonphone') ) { // get ExtensionState: 0-idle; 1-busy; 4-unavail; 8-ringing <--- these are unconfirmed $extstate = is_ext_avail($extnum); debug("Extension $extnum has ExtensionState: $extstate",1); if ($extstate == 1) { // CW in use - but blocked for hunt debug("Extension $extnum has call waiting enabled but blocked for hunt",1); $extnum = ''; $AGI->set_variable('DIALSTATUS','BUSY'); } } } if ($extnum != '') { // Still got an extension to be called? // check if we already have a dial string for this extension // if so, ignore it as it's pointless ringing it twice ! $realext = str_replace("#", "", $extnum); if ( isset($dsarray[$realext]) ) { debug("Extension '$realext' already in the dialstring, ignoring duplicate",1); } else { $dsarray[$realext] = 1; // could be dial string i suppose but currently only using for duplicate check $extds = get_dial_string( $AGI, $extnum, $use_confirmation, $ringgroup_index); if (strlen($extds)) $ds .= $extds . '&'; // Update Caller ID for calltrace application if ((strpos($k,"#")==0) && (($rgmethod != "hunt") && ($rgmethod != "memoryhunt") && ($rgmethod != "firstavailable") && ($rgmethod != "firstnotonphone")) ) { if ( isset($cidnum) && is_numeric($cidnum) ) { $rc = $AGI->database_put('CALLTRACE', $k, $cidnum); if ($rc['result'] == 1) { debug("dbset CALLTRACE/$k to $cidnum", 3); } else { debug("Failed to DbSet CALLTRACE/$k to $cidnum ({$rc['result']})", 1); } } else { // We don't care about retval, this key may not exist $AGI->database_del('CALLTRACE', $k); debug("DbDel CALLTRACE/$k - Caller ID is not defined", 3); } } else { $ext_hunt[$k]=$extds; // Need to have the extension HASH set with technology for hunt group ring } } } } // end foreach ( $ext as $k ) $dshunt = ''; $loops = 0; $myhuntmember = ""; if (($rgmethod == "hunt") || ($rgmethod == "memoryhunt") || ($rgmethod == "firstavailable") || ($rgmethod == "firstnotonphone")) { if ($cidnum) $AGI->set_variable('CALLTRACE_HUNT',$cidnum); foreach ($extarray as $k ) { // we loop through the original array to get the extensions in order of importance if ($ext_hunt[$k]) { //If the original array is included in the extension hash then set variables $myhuntmember="HuntMember"."$loops"; if (($rgmethod == "hunt") || ($rgmethod == "firstavailable") || ($rgmethod == "firstnotonphone")) $AGI->set_variable($myhuntmember,$ext_hunt[$k]); elseif ($rgmethod == "memoryhunt") { if ($loops==0) $dshunt =$ext_hunt[$k]; else $dshunt .='&'.$ext_hunt[$k]; $AGI->set_variable($myhuntmember,$dshunt); } $loops += 1; } } } $ds = chop($ds," &"); if ($nodest != '' && $use_confirmation == 'FALSE') { if (strpos($dialopts,"M(auto-blkvm)") > 0 || strpos($dialopts,"M(auto-blkvm)") === 0 || strpos($dialopts,"M(auto-confirm") > 0 || strpos($dialopts,"M(auto-confirm") === 0 || strpos($dialopts,"M(confirm") > 0 || strpos($dialopts,"M(confirm") === 0) { debug("NODEST: $nodest blkvm enabled macro already in dialopts: $dialopts",4); } else { $dialopts .= "M(auto-blkvm)"; debug("NODEST: $nodest adding M(auto-blkvm) to dialopts: $dialopts",4); } } if (!strlen($ds)) { $AGI->noop(''); } else { if (($rgmethod == "hunt") || ($rgmethod == "memoryhunt") || ($rgmethod == "firstavailable") || ($rgmethod == "firstnotonphone")) { $ds = '|'; if ($timer) $ds .= $timer; $ds .= '|' . $dialopts; // pound to transfer, provide ringing $AGI->set_variable('ds',$ds); $AGI->set_variable("HuntMembers",$loops); $AGI->set_priority(20); // dial command is at priority 20 where dialplan handles calling a ringgroup with strategy of "hunt" or "MemoryHunt" } else { $ds .= '|'; if ($timer) { $ds .= $timer; if (trim($use_confirmation) != "FALSE") $AGI->set_variable('__RT',$timer); } $ds .= '|' . $dialopts; // pound to transfer, provide ringing if (trim($use_confirmation) != "FALSE") { $AGI->set_variable('__RG_IDX',$ringgroup_index); if ( isset($cidnum) && is_numeric($cidnum) ) { $AGI->set_variable('__CALLCONFIRMCID',$cidnum); } else { $AGI->set_variable('__CALLCONFIRMCID',"999"); } } $AGI->set_variable('ds',$ds); $AGI->set_priority(10); // dial command is at priority 10 } } $astman->disconnect(); // EOF dialparties.agi exit( 0 ); // helper functions function get_var( $agi, $value) { $r = $agi->get_variable( $value ); if ($r['result'] == 1) { $result = $r['data']; return $result; } else return ''; } function get_dial_string( $agi, $extnum, $use_confirmation, $ringgroup_index ) { $dialstring = ''; if (strpos($extnum,'#') != 0) { // "#" used to identify external numbers in forwards and callgourps // If using call confirmation, need to put the # back into the new dialstring // we then place all external calls (denoted with a # at the end) through // the [grps] extension for the RINGGROUP_INDEX that was called. This // triggers the call confirmation macro along with the required messages // that were set. // $extnum = str_replace("#", "", $extnum); if (trim($use_confirmation) == "FALSE") $dialstring = 'Local/'.$extnum.'@from-internal/n'; else $dialstring = 'Local/RG-'.$ringgroup_index.'-'.$extnum.'#@from-internal'; debug("Built External dialstring component for $extnum: $dialstring", 4); } else { $device_str = sprintf("%s/device", $extnum); $device = $agi->database_get('AMPUSER',$device_str); $device = $device['data']; // a user can be logged into multipe devices, append the dial string for each $device_array = split( '&', $device ); foreach ($device_array as $adevice) { if (trim($use_confirmation) == "FALSE") { $dds = $agi->database_get('DEVICE',$adevice.'/dial'); $dialstring .= $dds['data']; $dialstring .= '&'; } else { $dialstring .= 'Local/LC-'.$adevice.'@from-internal&'; } } $dialstring = trim($dialstring," &"); } return $dialstring; } function debug($string, $level=3) { global $AGI; $AGI->verbose($string, $level); } function mycallback( $rc ) { debug("User hung up. (rc=" . $rc . ")", 1); exit ($rc); } function is_ext_avail( $extnum ) { global $config; global $astman; $status = $astman->ExtensionState( $extnum, 'from-internal' ); $status = $status['Status']; debug("ExtensionState: $status", 4); return $status; } function parse_amportal_conf($filename) { $file = file($filename); $matches = array(); $matchpattern = '/^\s*([a-zA-Z0-9]+)\s*=\s*(.*)\s*([;#].*)?/'; foreach ($file as $line) { if (preg_match($matchpattern, $line, $matches)) { $conf[ $matches[1] ] = $matches[2]; } } return $conf; } ?>