]> Untitled Git - dev/commitdiff
new
authordirect <direct@1423-sleepy.empl.vvsu.ru>
Fri, 13 Mar 2020 02:02:19 +0000 (12:02 +1000)
committerdirect <direct@1423-sleepy.empl.vvsu.ru>
Fri, 13 Mar 2020 02:02:19 +0000 (12:02 +1000)
astapilib/TODO.html [new file with mode: 0644]
astapilib/agi.php
astapilib/ami.php
astapilib/baseagi.php
astapilib/baseami.php
astapilib/timer.php [new file with mode: 0644]
call.php [new file with mode: 0644]
check_code.php [new file with mode: 0644]
shmtester.php [new file with mode: 0644]
shmvar.php [new file with mode: 0644]

diff --git a/astapilib/TODO.html b/astapilib/TODO.html
new file mode 100644 (file)
index 0000000..58f65eb
--- /dev/null
@@ -0,0 +1,179 @@
+pbx*CLI> manager show commands
+  Action                       Synopsis
+  ------                       --------
+- WaitEvent                    Wait for an event to occur.
++ DeviceStateList              List the current known device states.
++ PresenceStateList            List the current known presence states.
++ QueueReset                   Reset queue statistics.
++ QueueReload                  Reload a queue, queues, or any sub-section of a q
++ QueueRule                    Queue Rules.
++ QueueMemberRingInUse         Set the ringinuse value for a queue member.
++ QueuePenalty                 Set the penalty for a queue member.
++ QueueLog                     Adds custom entry in queue_log.
++ QueuePause                   Makes a queue member temporarily unavailable.
++ QueueRemove                  Remove interface from queue.
++ QueueAdd                     Add interface to queue.
++ QueueSummary                 Show queue summary.
++ QueueStatus                  Show queue status.
+- Queues                       Queues.
++ PlayDTMF                     Play DTMF signal on a specific channel.
++ StopMixMonitor               Stop recording a call through MixMonitor, and fre
++ MixMonitor                   Record a call and mix the audio during the record
++ MixMonitorMute               Mute / unMute a Mixmonitor recording.
++ VoicemailRefresh             Tell Asterisk to poll mailboxes for a change
++ VoicemailUsersList           List All Voicemail User Information.
++ ControlPlayback              Control the playback of a file being played to a
++ MuteAudio                    Mute an audio stream.
++ DialplanExtensionRemove      Remove an extension from the dialplan
++ DialplanExtensionAdd         Add an extension to the dialplan
++ AgentLogoff                  Sets an agent as no longer logged in.
++ Agents                       Lists agents and their status.
++ ConfbridgeSetSingleVideoSrc  Set a conference user as the single video source
++ ConfbridgeStopRecord         Stop recording a Confbridge conference.
++ ConfbridgeStartRecord        Start recording a Confbridge conference.
++ ConfbridgeLock               Lock a Confbridge conference.
++ ConfbridgeUnlock             Unlock a Confbridge conference.
++ ConfbridgeKick               Kick a Confbridge user.
++ ConfbridgeUnmute             Unmute a Confbridge user.
++ ConfbridgeMute               Mute a Confbridge user.
++ ConfbridgeListRooms          List active conferences.
++ ConfbridgeList               List participants in a conference.
++ PRIDebugFileUnset            Disables file output for PRI debug messages
++ PRIDebugFileSet              Set the file used for PRI debug message output
++ PRIDebugSet                  Set PRI debug levels for a span
++ PRIShowSpans                 Show status of PRI spans.
++ DAHDIRestart                 Fully Restart DAHDI channels (terminates calls).
++ DAHDIShowChannels            Show status of DAHDI channels.
++ DAHDIDNDoff                  Toggle DAHDI channel Do Not Disturb status OFF.
++ DAHDIDNDon                   Toggle DAHDI channel Do Not Disturb status ON.
++ DAHDIDialOffhook             Dial over DAHDI channel while offhook.
++ DAHDIHangup                  Hangup DAHDI Channel.
++ DAHDITransfer                Transfer DAHDI Channel.
+- IAXregistry                  Show IAX registrations.
+- IAXnetstats                  Show IAX Netstats.
+- IAXpeerlist                  List IAX Peers.
+- IAXpeers                     List IAX peers.
++ SIPpeerstatus                Show the status of one or all of the sip peers.
++ SIPnotify                    Send a SIP notify.
++ SIPshowregistry              Show SIP registrations (text format).
++ SIPqualifypeer               Qualify SIP peers.
++ SIPshowpeer                  show SIP peer (text format).
++ SIPpeers                     List SIP peers (text format).
++ Park                         Park a channel.
++ ParkedCalls                  List parked calls.
++ Parkinglots                  Get a list of parking lots
++ FAXStats                     Responds with fax statistics
++ FAXSession                   Responds with a detailed description of a single
++ FAXSessions                  Lists active FAX sessions
+  AGI                          Add an AGI command to execute by Async AGI.
++ UnpauseMonitor               Unpause monitoring of a channel.
++ PauseMonitor                 Pause monitoring of a channel.
++ ChangeMonitor                Change monitoring filename of a channel.
++ StopMonitor                  Stop monitoring a channel.
++ Monitor                      Monitor a channel.
++ BridgeKick                   Kick a channel from a bridge.
++ BridgeDestroy                Destroy a bridge.
++ BridgeInfo                   Get information about a bridge.
++ BridgeList                   Get a list of bridges in the system.
++ BlindTransfer                Blind transfer channel(s) to the given destinatio
+? Filter                       Dynamically add filters for the current manager s
+? AOCMessage                   Generate an Advice of Charge message on a channel
++ ModuleCheck                  Check if module is loaded.
++ ModuleLoad                   Module management.
++ CoreShowChannels             List currently active channels.
++ LoggerRotate                 Reload and rotate the Asterisk logger.
++ Reload                       Send a reload event.
++ CoreStatus                   Show PBX core status variables.
++ CoreSettings                 Show PBX core settings (version etc).
++ UserEvent                    Send an arbitrary event.
++ UpdateConfig                 Update basic configuration.
++ SendText                     Send text message to channel.
++ ListCommands                 List available manager commands.
++ MailboxCount                 Check Mailbox Message Count.
++ MailboxStatus                Check mailbox.
++ AbsoluteTimeout              Set absolute timeout.
++ PresenceState                Check Presence State
++ ExtensionState               Check Extension Status.
++ Command                      Execute Asterisk CLI Command.
++ Originate                    Originate a call.
++ Atxfer                       Attended transfer.
++ Redirect                     Redirect (transfer) a call.
++ ListCategories               List categories in configuration file.
++ CreateConfig                 Creates an empty file in the configuration direct
++ Status                       List channel status.
++ GetConfigJSON                Retrieve configuration (JSON format).
++ GetConfig                    Retrieve configuration.
++ Getvar                       Gets a channel variable or function value.
++ Setvar                       Sets a channel variable or function value.
++ ShowDialPlan                 Show dialplan contexts and extensions
++ Hangup                       Hangup channel.
++ Challenge                    Generate Challenge for MD5 Auth.
++ Login                        Login Manager.
++ Logoff                       Logoff Manager.
++ Events                       Control Event Flow.
++ Ping                         Keepalive command.
++ LocalOptimizeAway            Optimize away a local channel when possible.
++ ExtensionStateList           List the current known extension states.
++ MessageSend                  Send an out of call message to an endpoint.
++ Bridge                       Bridge two channels already in the PBX.
++ BridgeTechnologyUnsuspend    Unsuspend a bridging technology.
++ BridgeTechnologySuspend      Suspend a bridging technology.
++ BridgeTechnologyList         List available bridging technologies and their st
+- DataGet                      Retrieve the data api tree.
++ DBPut                        Put DB entry.
++ DBDelTree                    Delete DB Tree.
++ DBDel                        Delete DB entry.
++ DBGet                        Get DB Entry.
+
+
+
+pbx*CLI> agi show commands topic
+ Dead                        Command   Description
++  No                         answer   Answer channel
+  Yes                 asyncagi break   Interrupts Async AGI
++  No                 channel status   Returns status of the connected channel.
++ Yes                   database del   Removes database key/value
++ Yes               database deltree   Removes database keytree/value
++ Yes                   database get   Gets database value
++ Yes                   database put   Adds/updates database value
++ Yes                           exec   Executes a given Application
++  No                       get data   Prompts for DTMF on a channel
++ Yes              get full variable   Evaluates a channel expression
++  No                     get option   Stream file, prompt for DTMF, with timeout.
++ Yes                   get variable   Gets a channel variable.
++  No                         hangup   Hangup a channel.
++ Yes                           noop   Does nothing.
++  No                   receive char   Receives one character from channels supporting it.
++  No                   receive text   Receives text from channels supporting it.
++  No                    record file   Records to a given file.
++  No                      say alpha   Says a given character string.
++  No                     say digits   Says a given digit string.
++  No                     say number   Says a given number.
++  No                   say phonetic   Says a given character string with phonetics.
++  No                       say date   Says a given date.
++  No                       say time   Says a given time.
++  No                   say datetime   Says a given time as specified by the format given.
++  No                     send image   Sends images to channels supporting it.
++  No                      send text   Sends text to channels supporting it.
++  No                 set autohangup   Autohangup channel in some time.
++  No                   set callerid   Sets callerid for the current channel.
++  No                    set context   Sets channel context.
++  No                  set extension   Changes channel extension.
++  No                      set music   Enable/Disable Music on hold generator
++  No                   set priority   Set channel dialplan priority.
++ Yes                   set variable   Sets a channel variable.
++  No                    stream file   Sends audio file on channel.
++  No            control stream file   Sends audio file on channel and allows the listener to control the stream.
++  No                       tdd mode   Toggles TDD mode (for the deaf).
++ Yes                        verbose   Logs a message to the asterisk verbose log.
++  No                 wait for digit   Waits for a digit to be pressed.
+-  No                  speech create   Creates a speech object.
+-  No                     speech set   Sets a speech engine setting.
+- Yes                 speech destroy   Destroys a speech object.
+-  No            speech load grammar   Loads a grammar.
+- Yes          speech unload grammar   Unloads a grammar.
+-  No        speech activate grammar   Activates a grammar.
+-  No      speech deactivate grammar   Deactivates a grammar.
+-  No               speech recognize   Recognizes speech.
++  No                          gosub   Cause the channel to execute the specified dialplan subroutine.
+pbx*CLI> agi show commands topic
index c74de2379f842bf611e3ce8c35600dcaf7a3b0af..426f031443a30698e91af0ba386578c038d208fd 100644 (file)
@@ -1,5 +1,5 @@
 <?php\r
-\r
+//V1.0\r
 require_once(__DIR__.DIRECTORY_SEPARATOR.'baseagi.php');\r
 \r
 class AGI extends baseAGI\r
index aeafd560eaab09ea5fefa2ac9b66770fc0dba8a3..1e6ac1dffdbd08caf03f0f1cae7a5b002a5b29b7 100644 (file)
@@ -1,4 +1,5 @@
 <?php
+//V1.0
 /*
 
 
@@ -134,6 +135,23 @@ class AMI extends baseAMI
        }
     }
     
+    //Набор методов для работы со встроенной БД asterisk
+    public function AGI($Channel, $Command, $CommandID = NULL)
+    {  
+       $params = $this->make_params(get_defined_vars());
+       $ActId = $this->send_action('AGI', $params);
+       $response = $this->get_response($ActId);
+       LOG::log(__METHOD__.' '.$response['Message'], 5);
+       if ($response['Response'] == 'Success')
+       {
+           return TRUE;
+       }
+       else
+       {
+           return FALSE;
+       }
+    }
+
     //Набор методов для работы со встроенной БД asterisk
     public function DBPut($Family, $Key, $Value=NULL)
     {  
index 5daa8ee30351bd6c1c9fc3cf63ac8f267c21a2cc..ca0a369ea640a47eb1b3bf3380bdb8785fec2159 100644 (file)
@@ -1,5 +1,5 @@
 <?php\r
-\r
+//V1.0\r
 class baseAGI\r
 {\r
     protected $request = FALSE;\r
index ca3bd130979d0e84572207b21672109ff1e6255a..28dae65c2693ff7c2291827758b9545a4d763324 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-
+//V1.0
 class baseAMI
 {
     protected $conn_handle = FALSE;
@@ -17,9 +17,9 @@ class baseAMI
        //parse parameters
        foreach ($config as $opt => $val)
        {
-           if ($opt == 'autorefresh' and $val === TRUE) {new Timer(0.5, array(&$this,'refresh'), TRUE);}
+           if ($opt == 'autorefresh' and $val === TRUE) {new Timer(0.5, array(&$this,'refresh'), NULL, TRUE);}
            if ($opt == 'logverbose') {LOG::set_verbose($val);}
-           if ($opt == 'keepalive' and $val === TRUE) {new Timer(60, array(&$this,'ping'), TRUE);}
+           if ($opt == 'keepalive' and $val === TRUE) {new Timer(60, array(&$this,'ping'), NULL, TRUE);}
        }
     }
     
@@ -186,7 +186,7 @@ class baseAMI
            return FALSE;
        }
     }
-
+    
 
     //подписка на события ami
     public function enable_events($toggle = FALSE)
diff --git a/astapilib/timer.php b/astapilib/timer.php
new file mode 100644 (file)
index 0000000..dc577cd
--- /dev/null
@@ -0,0 +1,119 @@
+<?php
+//VERSION 2.4
+register_tick_function(array('Timer','poll'));
+
+class Timer
+{
+    private $id; //метка
+    private $time; //таймер
+    private $IsStop = TRUE; //флаг остановленности true/false
+    private $UserCall; //задача
+    private $UserCallArg; //параметр к задаче в виде массива для передачи нескольких значений или единственного.
+    private $StartTime; //момент запуска таймера
+    private $ltime; //оставшееся время
+    private $type; //тип true регенерируемый, false не регенерируемый, null самоуничтожаемый не регенерируемый, применяется по умолчанию без присвоения, не может быть перезапущен.
+    private static $timers = []; 
+
+    public function __construct($time, $callable, $args = NULL,  $type = NULL) 
+    {
+       if ($args === NULL)
+       {
+           $args = [];
+       }
+       if (!is_array($args))
+       {
+           throw new Exception("parameter 3 must be an array, \$args=".gettype($args));
+       }
+       $this->id = uniqid();
+        $this->time = $time;
+        $this->UserCall = $callable;
+       $this->UserCallArg = $args;
+        $this->type = $type;
+       if (!is_callable($this->UserCall, TRUE, $callablename))
+       {
+           throw new Exception("parameter 2 must be callable, \$callable=". $callablename);
+       }
+       else
+       {    
+           $this->start();
+           self::$timers[$this->id] = &$this;
+       }
+    }
+
+    public function __destruct() 
+    {
+       unset(self::$timers[$this->id]);
+    }
+
+    private function getId()
+    {
+       return $this->id;
+    }
+
+    public function getTimeLeft()
+    {
+           return $this->ltime;
+    }
+
+    public function setTimeLeft($time)
+    {
+           $this->time = $time;
+    }
+
+
+    public function reset()
+    {
+       $this->StartTime = microtime(true);
+       $this->IsStop = FALSE;
+    }
+
+    public function start() 
+    {
+       $this->reset();
+       $this->IsStop = FALSE;
+    }
+
+    public function stop() 
+    {
+       $this->IsStop = TRUE;
+    }
+
+    public static function poll()
+    {
+       foreach (self::$timers as $timer)
+       {
+           if ($timer->IsStop == FALSE)
+           {
+               $timer->update();
+           }
+       }
+    }
+
+    private function update()
+    {
+       $this->ltime = $this->StartTime - microtime(true) + $this->time;
+       if ($this->ltime <= 0)
+       {
+           $this->ltime = 0;
+           $this->task();
+           if ($this->type === FALSE)
+           {
+               $this->stop();
+           }
+           elseif ($this->type === TRUE)
+           {
+               $this->reset();
+           }
+           elseif ($this->type === NULL)
+           {
+               $this->stop();
+               $this->__destruct();
+           }
+       }
+    }
+
+    private function task() 
+    {
+       call_user_func_array ($this->UserCall, $this->UserCallArg);
+    }
+}
diff --git a/call.php b/call.php
new file mode 100644 (file)
index 0000000..6eaa22a
--- /dev/null
+++ b/call.php
@@ -0,0 +1,296 @@
+#!/usr/bin/env php
+<?php
+/*
+// def: (SIP/...)              параметр 1 - вызываемые каналы
+// def: (iIkKtT...)            параметр 2 - доп опции вызываемых каналов
+// def:group (group|serial)    параметр 3 - метод вызова нескольких каналов 
+// def:inf (1-9999)            параметр 4 - ограничение колличества звонков на extension
+*/
+
+//установка временной зоны для php
+date_default_timezone_set('Etc/GMT-10');
+
+set_time_limit(0);
+require_once('phpagi.php');
+require_once('utf82lat.php');
+
+//INIT DATA
+$AGI = new AGI();
+$timeout = $AGI->get_variable('WAAN',true);
+$NO_PROGRESS = $AGI->get_variable('NO_PROGRESS',true);
+$NO_LINEUPDATE = $AGI->get_variable('NO_LINEUPDATE',true);
+$IS_BLACKLISTED = $AGI->database_get('SET/'.$AGI->request['agi_extension'].'/BL',$AGI->request['agi_callerid']);
+if ($IS_BLACKLISTED['data'] == 1)
+{
+    $AGI->verbose('The CID '.$AGI->request['agi_callerid'].' for extension '.$AGI->request['agi_extension'].' is blacklisted.');
+    if ($NO_PROGRESS == 'YES')
+    {
+       $AGI->exec('Answer');
+    }
+    else
+    {
+       $AGI->exec('Proceeding');
+    }
+    sleep(1);
+    $AGI->exec('Playback','cid_blacklisted,noanswer');
+    $AGI->exec('Hangup',54);
+    
+}
+
+$is_busy = false;
+$CIDCP = '';
+//пишем все вызовы
+$UID = $AGI->get_variable('UNIQUEID',true);
+$MONFILENAME = date('Y').'/'.date('m').'/'.date('d').'/'.$UID;
+$AGI->set_variable('CDR(moni_file)',$MONFILENAME);
+//$AGI->set_variable('MONITOR_EXEC','/opt/monproc $1 $2 $3');
+
+$AGI->exec('Monitor','wav,'.$MONFILENAME.',mb');
+//канал или каналы, которые вызываются (обязательный)
+if (isset($AGI->request['agi_arg_1']))
+    {
+        $dialchans = $AGI->request['agi_arg_1'];
+        //создание в astDB автоматически создаваемого hint
+        $AGI->database_put('hints',$AGI->request['agi_extension'],$AGI->request['agi_arg_1']);
+    }
+else
+    {
+       $AGI->verbose('Missing 1 pagameter');
+    }
+//параметры для Dial()
+if (isset($AGI->request['agi_arg_2']))
+    {
+        $opts = $AGI->request['agi_arg_2'];
+    }
+else
+    {
+        $opts = '';
+    }
+if ($NO_LINEUPDATE == 'YES')
+{
+    $opts .= 'I';
+}
+
+//метод вызова нескольких каналов
+if (isset($AGI->request['agi_arg_3']))
+    {
+       $mode = $AGI->request['agi_arg_3'];
+    }
+else
+    {
+       $mode = 'group';
+    }
+//ограничение колличества звонков на extension
+if (isset($AGI->request['agi_arg_4']))
+    {
+       $_extenlimit = $AGI->request['agi_arg_4'];
+    }
+
+//проверка callerid name на пустоту
+if ($AGI->request['agi_calleridname'] == '')
+{
+    $AGI->set_variable('CALLERID(name)',$AGI->request['agi_callerid']);
+}
+
+//кодировка callerid name
+//определение колличества пиров для вызова и ограничителей вызова
+$peers = explode('&',$dialchans);
+//когда пир один
+if (count($peers) == 1)
+    {
+       $is_single_peer = true;
+       $dialchan_arr = explode('/',$dialchans);
+       $_busylevel = $AGI->get_variable('SIPPEER('.$dialchan_arr[1].',busylevel)',true);
+       $_curcalls = $AGI->get_variable('SIPPEER('.$dialchan_arr[1].',curcalls)',true);
+       if ($_curcalls >= $_busylevel and $_busylevel != 0) {$is_busy = true;}
+       $CIDCP = $AGI->get_variable('SIPPEER('.$dialchan_arr[1].',chanvar[CIDNAMECP])',true);
+    }
+//когда пир не один
+else
+    {
+
+       $is_single_peer = false;
+    }
+//если есть ограничения на extension
+if (isset($_extenlimit))
+{
+    $_cur_exten_calls = $AGI->get_variable('GROUP_COUNT('.$AGI->request['agi_extension'].'@extenlimit)',true);
+    if ($_cur_exten_calls >= $_extenlimit)
+    {
+       $is_busy = true;
+    }
+    else
+    {
+       $AGI->set_variable('GROUP(extenlimit)',$AGI->request['agi_extension']);
+    }
+}
+
+if ($CIDCP == ''){$CIDCP = 'lat';}
+//преобразование кодировки
+switch ($CIDCP)
+    {
+    case 'lat':
+        $AGI->set_variable('CALLERID(name)',mb_transliterate($AGI->request['agi_calleridname']));
+        break;
+    case 'cp1251':
+        $AGI->set_variable('CALLERID(name)',mb_convert_encoding($AGI->request['agi_calleridname'], $CIDCP['data'], 'UTF-8'));
+        break;
+    }
+
+
+
+//dial process
+//посылать или не посылать progress
+
+if ($NO_PROGRESS != 'YES')
+    {
+       $AGI->exec('Proceeding');
+       $AGI->exec('Playtones',('!0/600,!1000/50'));
+       sleep(1);
+    }
+       //если пир один то позволять переадресации
+if ($is_single_peer)
+    {
+       //подготовка данных в случае переадресации
+       $dialchan_arr = explode('/',$dialchans);
+        $peername = $dialchan_arr[1];
+        $peercontext = $AGI->get_variable('SIPPEER('.$peername.',context)',true); 
+        $db_query = 'SET/'.$peername.'/CF';
+        //get settings for extension
+
+        $CF = $AGI->database_get($db_query,'mode');
+        if($CF['result'] == 1)
+           {
+               if ($CF['data'] == '1')
+               {
+                   $AGI->set_variable('REDIRECTING(from-num)',$peername);
+                   $AGI->set_variable('REDIRECTING(reason)','4');
+                   $FWDNUM = $AGI->database_get($db_query,'uncond');
+                   $AGI->exec_goto($peercontext,$FWDNUM['data'],'1');
+                   die();
+               }
+               if ($CF['data'] == '2')
+               {
+                   $FWDNUM = $AGI->database_get($db_query,'unansw');
+                   $timeout = $AGI->database_get($db_query,'timeout');
+                   $timeout = $timeout['data'];
+                   if ($timeout < 1)
+                   {
+                       $timeout = '15';
+                   }
+               }
+               if ($CF['data'] == '3')
+               {
+                   $FWDNUM = $AGI->database_get($db_query,'busy');
+               }
+           }
+    }
+else
+    {
+       //для груповых вызовов не позволять переадресацию во избежание неадекватного поведения
+       $CF['data'] = '0';
+    }
+
+//одиночный вызов
+if ($is_single_peer and !$is_busy)
+    {
+       $AGI->exec('Dial',$dialchans.','.$timeout.',TtkKg'.$opts);
+    }
+//групповой вызов
+if ($mode == 'group' and !$is_single_peer)
+    {
+       if (!$is_busy)
+       {
+           $AGI->exec('Dial',$dialchans.','.$timeout.',iTtkK'.$opts);
+           $AGI->verbose("CAUSE:".$AGI->get_variable('HANGUPCAUSE',true));
+       }
+    }
+
+//последовательный вызов
+if ($mode == 'serial' and !$is_single_peer)
+    {
+        foreach ($peers as $val)
+           {
+               $AGI->exec('Dial',$val.','.$timeout.',iTtkKg'.$opts);
+               $HANGUPCAUSE = $AGI->get_variable('HANGUPCAUSE',true);
+               if ($HANGUPCAUSE == 16 or $HANGUPCAUSE == 0 or $HANGUPCAUSE == 18){break;}
+           }
+    }
+
+//получение статуса завершения
+$HANGUPCAUSE = $AGI->get_variable('HANGUPCAUSE',true);
+$DIALSTATUS = $AGI->get_variable('DIALSTATUS',true);
+
+if ($is_busy){$HANGUPCAUSE = '17';}
+if ($DIALSTATUS == 'NOANSWER'){$HANGUPCAUSE = '18';}
+
+switch ($HANGUPCAUSE)
+{
+    case "20":
+    if ($CF['data'] == '2')
+    {
+       $AGI->set_variable('REDIRECTING(reason)','3');
+       $AGI->set_variable('REDIRECTING(from-num)',$peername);
+       $AGI->exec('Playback','pereadresacija-zvonka&ozhidajte-soedinenija,noanswer');
+       $AGI->exec_goto($peercontext,$FWDNUM['data'],'1');
+       exit();
+    }
+    else
+    {
+       for ($i=0;$i<2;$i++)
+       {
+           $AGI->exec('Playtones','info');
+           sleep(2);
+           $AGI->exec('Playback','abonent-not-connected,noanswer');
+           sleep(2);
+       }
+       sleep(1);
+       $AGI->exec('Hangup',$HANGUPCAUSE);
+       exit();
+    }
+    break;
+
+    case "18":
+    if ($CF['data'] == '2')
+    {
+       $AGI->set_variable('REDIRECTING(reason)','3');
+       $AGI->set_variable('REDIRECTING(from-num)',$peername);
+       //$AGI->exec('Playback','pereadresacija-zvonka&ozhidajte-soedinenija,noanswer');
+       $AGI->exec_goto($peercontext,$FWDNUM['data'],'1');
+       die();
+    }
+    else
+    {
+       $AGI->exec('Playback','abonent-noanswer,noanswer');
+       $AGI->exec('Hangup',$HANGUPCAUSE);
+    }
+    break;
+
+    case "17":
+    if ($CF['data'] == '3')
+    {
+       $AGI->set_variable('REDIRECTING(reason)','1');
+       $AGI->set_variable('REDIRECTING(from-num)',$peername);
+       $AGI->exec_goto($peercontext,$FWDNUM['data'],'1');
+       exit();
+    }
+    else
+    {
+       $AGI->exec('Playback','number&vm-isonphone,noanswer');
+       $AGI->exec('Hangup',$HANGUPCAUSE);
+
+    }
+    break;
+
+    case "0":
+    case "16":
+    $AGI->verbose("Normal call clearing, CAUSECODE: ".$HANGUPCAUSE);
+    break;
+
+    default:
+    $AGI->verbose("CAUSECODE not handled, value: ".$HANGUPCAUSE);
+    break;
+}
+
+
+?>
diff --git a/check_code.php b/check_code.php
new file mode 100644 (file)
index 0000000..17b7393
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+function waitanswer($a,$b)
+{
+    var_dump($a,$b);
+    var_dump(get_defined_vars());
+}
+echo "<pre>\n";
+require_once 'astapilib/ami.php';
+
+var_dump($_GET);
+if (!isset($_GET['phone']))
+{
+    exit();
+}
+if(preg_match('/^7\d{10}$/', $_GET['phone']) != 1)
+{
+    //exit();
+}
+
+$AMI = new AMI(array('autorefresh' => TRUE, 'logverbose' => 6));
+$is_connected = $AMI->connect("127.0.0.1", "monast", "blabla");
+$AMI->add_event_handler('originateresponse', 'waitanswer');
+$AMI->enable_events(TRUE);
+
+if (!$is_connected)
+{
+    exit();
+}
+
+var_dump($AMI->Originate("Local/{$_GET['phone']}@c-2", NULL, NULL, NULL, 'AGI', 'agi:async', 30, 3500, NULL, NULL, NULL, NULL, 'azaza', 'ololo'));
+
+sleep(5);
+$AMI->AGI("Local/{$_GET['phone']}@c-2", 'NoOp');
\ No newline at end of file
diff --git a/shmtester.php b/shmtester.php
new file mode 100644 (file)
index 0000000..724f8fe
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/env php
+<?php
+
+/* 
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+include_once 'shmvar.php';
+$MEM = new SHMVAR(1234,5,256);
+//var_dump(isset($MEM->abc));
+//$MEM->abc = 2;
+//$MEM->bsd = 'asasa';
+
+//var_dump(isset($MEM->abc));
+//var_dump($MEM->SearchFreeTOCSlot());
+var_dump($MEM->ReadTOCSlot(0));
+var_dump(isset($MEM->somevar));
+$MEM->somevar = 'aaaaaaaa';
+$MEM->var2 = 'bbbbbbbb';
+$MEM->var3 = 'cccccccc';
+$MEM->var4 = 'dddddddd';
+
+var_dump($MEM->ReadTOCSlot(0));
+
+//var_dump($MEM->ReadTOCSlot(1));
+//var_dump($MEM->ReadTOCSlot(2));
+//var_dump($MEM->ReadTOCSlot(3));
+
+
+
+$MEM->printshm();
\ No newline at end of file
diff --git a/shmvar.php b/shmvar.php
new file mode 100644 (file)
index 0000000..1bb53c8
--- /dev/null
@@ -0,0 +1,369 @@
+<?php
+
+/* 
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+
+ * TODO 
+ * сделать многостраничное выделение
+ *  */
+define ( 'TOCBLOCKSIZE' , 141 );
+
+class SHMVAR
+{
+    protected $shm;
+    protected $shmkey;
+    protected $max_toc_slots;
+    protected $success_init;
+    protected $namecache;
+    protected $dataoffset;
+    protected $datasize;
+
+    public function __construct($shmkey = NULL, $maxvars = 32768, $datasize = 5242880)
+    {
+       if($shmkey === NULL)
+       {
+           $this->shmkey = ftok(__FILE__, 'a');            
+       }
+       else
+       {
+           $this->shmkey = $shmkey;
+       }
+       $this->max_toc_slots = $maxvars;
+       $this->datasize = $datasize;
+       $this->dataoffset = $maxvars * TOCBLOCKSIZE;
+       $this->namecache = [];
+       $pagesize = $maxvars * TOCBLOCKSIZE + $datasize;
+       //открытие страницы памяти
+       $this->shm = @shmop_open($this->shmkey, 'w', 0, 0);
+       if (!$this->shm)
+       {
+           $this->shm = @shmop_open($this->shmkey, 'c', 0640, $pagesize);
+       }
+//     // объявление свободного пространства
+//     $this->WriteTOCSlot(0, '', 0xFF, 0x00, 0, $datasize, 0xFFAAFFAA);
+//     $this->WriteHeaderSegment(0, 0xFFFFFFFF, $datasize);
+       
+    }
+    public function __isset ($name)
+    {
+       if ($this->SearchVarByName($name))
+       {
+           return TRUE;
+       }
+       else
+       {
+           return FALSE;
+       }
+    }
+
+    public function __set ($name , $value)
+    {
+       $toc_slot = $this->SearchVarByName($name);
+       if ($toc_slot === FALSE)
+       {
+           $toc_slot = $this->SearchFreeTOCSlot();
+       }
+
+       switch (gettype($value))
+       {
+           case "NULL":
+               $type = 0;
+               break;
+           case "boolean": 
+               $type = 1;
+               break;
+           case "integer": 
+               $type = 2;
+               break;
+           case "double":
+               $type = 3;
+               break;
+           case "string":
+               $type = 4;
+               break;
+           case "array":
+               $type = 5;
+               break;
+           case "object":
+               $type = 6;
+               break;
+           case "resource":
+               $type = 7;
+               break;
+           case "resource (closed)":
+               $type = 8;
+               break;
+           case "unknown type":
+               $type = 9;
+               break;
+           default:
+               $type = 10;
+               break;
+       }
+       $size = strlen($value);
+       $segments = $this->ReserveDataSegments($size);
+       var_dump($segments);
+       $lastdatapos = 0;
+       foreach ($segments as $segment)
+       {
+           $segmentsize = $segment['e'] - $segment['s'];
+           $dataforwrite = substr($value, $lastdatapos, $segmentsize);
+           $lastdatapos = $lastdatapos + $segmentsize;
+           $this->WriteDataSegment($segment['s'], $value);
+       }
+       $flags = 0;
+       $startaddr = 0;
+       $nexttoc_slot = 0xAAAAAAAA;
+
+       $data = $value;
+       $this->WriteTOCSlot($toc_slot, $name, 0xFF, $flags, $startaddr, $size, $nexttoc_slot);
+    }
+    
+//public mixed __get ( string $name )
+    //public bool __isset ( string $name )
+    //public void __unset ( string $name )
+    public function ReserveDataSegments($size)
+    {
+       $segments = $this->SearchFreeSegments($size);
+       $totalsize = 0;
+       foreach ($segments as $segment => $address)
+       {
+           $totalsize = $totalsize + ($address['e'] - $address['s'] - 8);
+       }
+       
+       $lastsegmentsize = ($address['e'] - $address['s']);
+       $newlastsegmentsize = $lastsegmentsize - ($totalsize - $size);
+       $newlastsegmentaddress = $address['e'] - $newlastsegmentsize;
+       $segments[$segment]['e'] = $segments[$segment]['s'] + $newlastsegmentsize;
+       $newfreesegmentaddress = $segments[$segment]['e'] + 1;
+       $newfreesegmentsize = ($lastsegmentsize - $newlastsegmentsize);
+       $TocSlot0 = $this->ReadTOCSlot(0);
+       $LastSegmentHeader = $this->ReadDataSegment($segments[$segment]['s'], 0);
+       $this->WriteTOCSlot(0, '', 0xFF, 0x00, $newfreesegmentaddress, $TocSlot0['size'] - $size, 0);
+       $this->WriteHeaderSegment($newfreesegmentaddress, $LastSegmentHeader['nextaddress'], $newfreesegmentsize);
+       $this->WriteHeaderSegment($segments[$segment]['s'], 0xFFFFFFFF, $newlastsegmentsize);
+       return $segments;
+    }
+
+    public function FreeDataSegments($size)
+    {
+       
+    }
+
+    public function SearchFreeTOCSlot()
+    {
+       for ($TocSlotId = 0; $TocSlotId < $this->max_toc_slots; $TocSlotId++)
+       {
+           $data = $this->ReadTOCSlot($TocSlotId);
+           if($data['name'] == '' && $data['type'] == 0)
+           {
+               return $TocSlotId;
+           }
+       }
+       return FALSE;
+    }
+    
+    public function SearchFreeSegments($size)
+    {
+       for ($TocSlotId = 0; $TocSlotId < $this->max_toc_slots; $TocSlotId++)
+       {
+           $data = $this->ReadTOCSlot($TocSlotId);
+           if($data['type'] != 0)
+           {
+               $usedsegments[] = $data['address'];
+               $usedsegments[] = $data['address'] + $data['size'];
+
+           }
+       }
+       sort($usedsegments, SORT_NUMERIC);
+       foreach ($usedsegments as $key => $val)
+       {
+           if ($key % 2 == 0)
+           {
+               $tmp1['s'] = $val;
+           }
+           else
+           {
+               $tmp1['e'] = $val;
+               $tmp2[] = $tmp1;
+           }
+           $usedsegments = $tmp2;
+           unset($tmp1,$tmp2);
+       }
+       $addrpointer = 0;
+       $counter = 0;
+       foreach ($usedsegments as $segment)
+       {
+           if($segment['s'] == 0)
+           {
+               $addrpointer = $segment['e'] + 1;
+               continue;
+           }
+           if ($segment['s'] == $addrpointer)
+           {
+               $addrpointer = $segment['e'] + 1;
+           }
+           if ($segment['s'] != $addrpointer)
+           {
+               $freesegment['s'] = $addrpointer;
+               $freesegment['e'] = $segment['s'] - 1;
+           }
+           $freesegments[] = $freesegment;
+       }
+
+       $start - 0;
+       $stop = $this->datasize;
+       
+       
+       $freepointer = $this->ReadTOCSlot(0);
+       if ($size > $freepointer['size'])
+       {
+           return FALSE;
+       }       
+       $segaddr = $freepointer['address'];
+       $DiscoveredSpaseSize = 0;
+       while ($segaddr != 0xFFFFFFFF)
+       {
+           $segheader = $this->ReadDataSegment($segaddr, 0);
+           $freesegment['s'] = $segaddr;
+           $freesegment['e'] = $segaddr + $segheader['size'];
+           $freesegments[] = $freesegment;
+           $segaddr = $segheader['nextaddress'];
+           $DiscoveredSpaseSize = $DiscoveredSpaseSize + ($freesegment['e'] - $freesegment['s'] - 8);
+           if ($DiscoveredSpaseSize >= $size)
+           {
+               break;
+           }
+       }
+       
+       return $freesegments;
+    }
+    
+        protected function SearchVarByName($name)
+    {
+       for ($TocSlotId = 0; $TocSlotId < $this->max_toc_slots; $TocSlotId++)
+       {
+           $data = $this->ReadTOCSlot($TocSlotId);
+           if($data['name'] == $name && $data['type'] != 0)
+           {
+               return $TocSlotId;
+           }
+       }
+       return FALSE;
+    }
+    
+    /* modes:
+     * 0 - only header
+     * 1 - one page data
+     * 2 - one page data + header as named array
+     * 3 - autoassemble all data pages (default)
+     */ 
+    protected function ReadDataSegment($address, $mode = 3)
+    {
+       $address = $this->dataoffset + $address;
+       $header = shmop_read ($this->shm, $address, 8);
+       $header = unpack('Nnextaddress/Nsize', $header);
+       switch ($mode)
+       {
+           case 0:
+               return $header;
+           case 1:
+               return shmop_read($this->shm, $address + 8 , $header['size']);
+           case 2:
+               $header['data'] = shmop_read($this->shm, $address + 8 , $header['size']);
+               return $header;
+       }
+       $data = '';
+       while (TRUE);
+       {
+           $data .= shmop_read($this->shm, $address + 8 , $header['size']);
+           if ($header['nextaddress'] == 0xFFFFFFFF)
+           {
+               return $data;
+           }
+           $header = shmop_read ($this->shm, $this->dataoffset + $header['nextaddress'], 8);
+           $header = unpack('Nnextaddress/Nsize', $header);
+           $address = $this->dataoffset + $header['nextaddress'];
+       }
+       
+    }
+
+    protected function WriteDataSegment($address, $data)
+    {
+       $address = $this->dataoffset + $address;
+       $nextaddress = 0xFFFFFFFF;
+       $size = strlen($data);
+//     shmop_write ($this->shm , pack('NNC*', $nextaddress, $size) , $address);
+       shmop_write ($this->shm , pack('NNa*', $nextaddress, $size, $data) , $address);
+       }
+    
+    protected function WriteHeaderSegment($address, $nextaddress, $size)
+    {
+       $address = $this->dataoffset + $address;
+       shmop_write ($this->shm , pack('NN', $nextaddress, $size) , $address);
+    }
+
+    protected function WriteTOCSlot($TocSlotId, $name, $type, $lock, $startaddr, $size, $nexttoc_slot)
+    {
+       $recaddr = $TocSlotId * TOCBLOCKSIZE;
+       $block = pack('a127CCNNN', $name, $type, $lock, $startaddr, $size, $nexttoc_slot);
+       shmop_write ($this->shm , $block , $recaddr);
+       
+    }
+    public function ReadTOCSlot($TocSlotId)
+    {
+       // 1-127 var name
+       // 128 type
+       // 129 lock flag
+       // 130-133 address
+       // 134-137 size
+       // 138-141 next nexttoc_slot
+       $recaddr = $TocSlotId * TOCBLOCKSIZE;
+       $block = shmop_read ($this->shm, $recaddr, TOCBLOCKSIZE);
+//     $this->debug($block);
+       $result = unpack('a127name/Ctype/Cflags/Naddress/Nsize/Nnexttocslot', $block);
+       return $result;
+    }
+    
+    public function printshm ()
+    {
+       $this->debug(shmop_read ($this->shm, 0, shmop_size($this->shm)));
+    }
+
+    public function debug($data)
+    {
+       $str = '';
+       $CPS = 16;
+       $pc = 0;
+       $maxaddrlen = strlen(dechex(strlen($data)));    
+       echo str_repeat(" ", $maxaddrlen+2);
+       for ($i = 0; $i < $CPS; $i++)
+       {
+           echo sprintf("%'02X", $i)," ";
+       }
+
+       for ($i = 0; $i < strlen($data); $i++)
+       {
+           $pc++;
+           if(($i % $CPS) == 0)
+           {
+               echo "\t",$str, PHP_EOL, sprintf("%'0{$maxaddrlen}X", $i),"  ";
+               $str = '';
+               $pc = 0;
+           }
+           $byte = ord($data{$i});
+           echo sprintf("%'02X", $byte)," ";
+           if($byte > 31 && $byte < 127 )
+           {
+               $str .= chr($byte);
+           }
+           else
+           {
+               $str .= ".";
+           }
+       }
+       echo str_repeat("   ", ($CPS-$pc)-1), "\t", $str, PHP_EOL;
+    }
+}
\ No newline at end of file