--- /dev/null
+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
<?php\r
-\r
+//V1.0\r
require_once(__DIR__.DIRECTORY_SEPARATOR.'baseagi.php');\r
\r
class AGI extends baseAGI\r
<?php
+//V1.0
/*
}
}
+ //Набор методов для работы со встроенной БД 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)
{
<?php\r
-\r
+//V1.0\r
class baseAGI\r
{\r
protected $request = FALSE;\r
<?php
-
+//V1.0
class baseAMI
{
protected $conn_handle = FALSE;
//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);}
}
}
return FALSE;
}
}
-
+
//подписка на события ami
public function enable_events($toggle = FALSE)
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+#!/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;
+}
+
+
+?>
--- /dev/null
+<?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
--- /dev/null
+#!/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
--- /dev/null
+<?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