Tighten Ami controls

Check that have compatible version of chan-sccp for AMI and disable AMI  if not found
Check returned values
Add new classes for response to reduce testing
This commit is contained in:
steve-lad 2021-03-12 14:03:50 +01:00 committed by Diederik de Groot
parent 315a693894
commit 673f6d45e0
No known key found for this signature in database
GPG key ID: AFA728250A1BECD6
8 changed files with 324 additions and 431 deletions

View file

@ -26,8 +26,7 @@ abstract class Event extends IncomingMessage
{ {
parent::__construct($rawContent); parent::__construct($rawContent);
$this->_events = array(); $this->_events = array();
$this->_eventsCount = 0; $this->_completed = false;
// $this->_completed = !$this->isList();
} }
} }
@ -35,8 +34,6 @@ class UnknownEvent extends Event
{ {
public function __construct($rawContent = '') public function __construct($rawContent = '')
{ {
// print_r($rawContent);
// die();
} }
} }
@ -60,28 +57,13 @@ class TableEnd_Event extends Event
class SCCPSoftKeySetEntry_Event extends Event class SCCPSoftKeySetEntry_Event extends Event
{ {
// This is a list of tables, each table is an entry
protected $_data; protected $_data;
public function __construct($rawContent)
{
parent::__construct($rawContent);
return null;
}
}
class SCCPShowSoftKeySetsComplete_Event extends Event
{
public function getListItems()
{
return intval($this->getKey('ListItems'));
}
} }
class ExtensionStatus_Event extends Event class ExtensionStatus_Event extends Event
{ {
// this is a list of tables, each table is an entry
public function getPrivilege() public function getPrivilege()
{ {
return $this->getKey('Privilege'); return $this->getKey('Privilege');
@ -110,27 +92,12 @@ class ExtensionStatus_Event extends Event
class SCCPDeviceEntry_Event extends Event class SCCPDeviceEntry_Event extends Event
{ {
// This is a list of tables, each table is an entry
}
class SCCPShowDeviceComplete_Event extends Event
{
public function getListItems()
{
return intval($this->getKey('ListItems'));
}
public function __construct($rawContent)
{
parent::__construct($rawContent);
$this->_completed = $this->getKey('EventList');
// return null;
}
} }
class SCCPShowDevice_Event extends Event class SCCPShowDevice_Event extends Event
{ {
// This is a list of tables
public function getCapabilities() public function getCapabilities()
{ {
$ret = array(); $ret = array();
@ -153,15 +120,6 @@ class SCCPShowDevice_Event extends Event
return $ret; return $ret;
} }
} }
class SCCPShowDevicesComplete_Event extends Event
{
public function getListItems()
{
return intval($this->getKey('ListItems'));
}
}
class SCCPDeviceButtonEntry_Event extends Event class SCCPDeviceButtonEntry_Event extends Event
{ {
} }
@ -182,7 +140,67 @@ class SCCPDeviceStatisticsEntry_Event extends Event
class SCCPDeviceSpeeddialEntry_Event extends Event class SCCPDeviceSpeeddialEntry_Event extends Event
{ {
} }
class ExtensionStateListComplete_Event extends Event abstract class ClosingEvent extends Event
{ {
public function __construct($message) {
parent::__construct($message);
$this->_completed = true;
}
public function getListItems() {
return intval($this->getKey('ListItems'));
}
} }
class ResponseComplete_Event extends ClosingEvent
{
// dummy event to avoid unnecessary testing
public function listCorrectlyReceived($_message, $_eventCount){
return true;
}
}
class SCCPShowDeviceComplete_Event extends ClosingEvent
{
public function listCorrectlyReceived($_message, $_eventCount){
// Have end of list event. Check with number of lines received and send true if match.
// Remove 9 for the start and end events, and then 4.
if ($this->getKey('listitems') === substr_count( $_message, "\n") -13) {
return true;
}
return false;
}
}
class SCCPShowDevicesComplete_Event extends ClosingEvent
{
public function listCorrectlyReceived($_message, $_eventCount) {
// Have end of list event. Check with number of events received and send true if match.
// Remove 9 for the lines in the list start and end, and the 2 blank lines.
if ($this->getKey('listitems') === substr_count( $_message, "\n") -11) {
return true;
}
return false;
}
}
class ExtensionStateListComplete_Event extends ClosingEvent
{
public function listCorrectlyReceived($_message, $_eventCount){
// Have end of list event. Check with number of events received and send true if match.
// Remove 1 as the closing event is included in the count.
if ($this->getKey('listitems') === $_eventCount -1) {
return true;
}
return false;
}
}
class SCCPShowSoftKeySetsComplete_Event extends ClosingEvent
{
public function listCorrectlyReceived($_message, $_eventCount){
// Have the end of list event. Check the number of lines received and
// return true if match. Remove 8 for the complete event.
if ($this->getKey('listitems') === substr_count( $_message, "\n") -11) {
return true;
}
return false;
}
}

View file

@ -176,7 +176,7 @@ abstract class Message
return (double) $value; return (double) $value;
} }
default: default:
throw new PAMIException("Don't know how to convert: '" . $value . "'\n"); throw new AMIException("Don't know how to convert: '" . $value . "'\n");
break; break;
} }
} }
@ -242,6 +242,11 @@ abstract class IncomingMessage extends Message
return $this->rawContent; return $this->rawContent;
} }
public function isComplete()
{
return $this->_completed;
}
public function __sleep() public function __sleep()
{ {
$ret = parent::__sleep(); $ret = parent::__sleep();
@ -418,6 +423,6 @@ class SCCPConfigMetaDataAction extends ActionMessage
if ($segment != false) { if ($segment != false) {
$this->setKey('Segment', $segment); $this->setKey('Segment', $segment);
} }
$this->setResponseHandler("SCCPGeneric"); $this->setResponseHandler("SCCPJSON");
} }
} }

View file

@ -1,12 +1,10 @@
<?php <?php
/** /*
* *
* Core Comsnd Interface * Response class definitions
* *
* https://www.voip-info.org/asterisk-manager-example-php/
*/ */
/* !TODO!: Re-Indent this file. -TODO-: What do you mean? coreaccessinterface ?? */
namespace FreePBX\modules\Sccp_manager\aminterface; namespace FreePBX\modules\Sccp_manager\aminterface;
@ -26,12 +24,7 @@ abstract class Response extends IncomingMessage
parent::__construct($rawContent); parent::__construct($rawContent);
$this->_events = array(); $this->_events = array();
$this->_eventsCount = 0; $this->_eventsCount = 0;
$this->_completed = !$this->isList(); $this->_completed = $this->isSuccess();
}
public function isComplete()
{
return $this->_completed;
} }
public function __sleep() public function __sleep()
@ -42,20 +35,19 @@ abstract class Response extends IncomingMessage
return $ret; return $ret;
} }
public function addEvent($event)
{
$this->_events[] = $event;
if (stristr($event->getEventList(), 'complete') !== false
|| stristr($event->getName(), 'complete') !== false
|| stristr($event->getName(), 'DBGetResponse') !== false
) {
$this->_completed = true;
}
}
public function getEvents() public function getEvents()
{ {
return $this->_events; return $this->_events;
} }
public function getClosingEvent() {
return $this->_events['ClosingEvent'];
}
public function removeClosingEvent() {
unset($this->_events['ClosingEvent']);
}
public function getCountOfEvents() {
return count($this->_events);
}
public function isSuccess() public function isSuccess()
{ {
@ -65,10 +57,9 @@ abstract class Response extends IncomingMessage
public function isList() public function isList()
{ {
return if ($this->getKey('EventList') === 'start' ) {
stristr($this->getKey('EventList'), 'start') !== false return true;
|| stristr($this->getMessage(), 'follow') !== false }
;
} }
public function getMessage() public function getMessage()
@ -94,36 +85,37 @@ abstract class Response extends IncomingMessage
} }
} }
} }
class GenericResponse extends Response
{
}
//**************************************************************************** //****************************************************************************
// There are two types of Response messages returned by AMI
// Self contained responses which include any data requested;
// List Responses which contain the data in event messages that follow
// the response message.Response and Event
// Following are the self contained Response classes.
//****************************************************************************
class Generic_Response extends Response class Generic_Response extends Response
{ {
public function __construct($rawContent) public function __construct($rawContent)
{ {
parent::__construct($rawContent); parent::__construct($rawContent);
// print_r('<br>---- r --<br>'); // add dummy closing event
// print_r($rawContent); $this->_events['ClosingEvent'] = new ResponseComplete_Event($rawContent);
} }
} }
class Login_Response extends Response class Login_Response extends Generic_Response
{ {
public function __construct($rawContent)
{
parent::__construct($rawContent);
return $this->isSuccess();
}
} }
class Command_Response extends Response class Command_Response extends Generic_Response
{ {
private $_temptable; private $_temptable;
public function __construct($rawContent) public function __construct($rawContent)
{ {
// print_r('<br>---- r --<br>');
// print_r($rawContent);
// print_r('<br>---- re --<br>');
$this->_temptable = array(); $this->_temptable = array();
parent::__construct($rawContent); parent::__construct($rawContent);
$lines = explode(Message::EOL, $rawContent); $lines = explode(Message::EOL, $rawContent);
@ -150,26 +142,35 @@ class Command_Response extends Response
} }
} }
} }
/* Not required $_temptable cannot be empty as has at least an actionID - see also getResult
if (!empty($this->_temptable)) {
$this->setKey('output', 'array');
}
*/
$this->_completed = $this->isSuccess();
// return $this->isSuccess();
} }
public function getResult() public function getResult()
{ {
/* Below test no longer valid as key no longer set return $this->_temptable;
if (stristr($this->getKey('output'), 'array') !== false) {
$result = $this->_temptable;
} else {
$result = $this->getMessage();
}
*/ return $this->_temptable;
} }
} }
class SCCPJSON_Response extends Generic_Response
{
public function __construct($rawContent)
{
parent::__construct($rawContent);
$this->getVariable($rawContent, array("DataType" => "DataType:", "JSONRAW" => "JSON:"));
if (null !== $this->getKey('JSONRAW')) {
$this->setKey('Response', 'Success');
}
}
public function getResult()
{
if (($json = json_decode($this->getKey('JSON'), true)) != false) {
return $json;
}
}
}
//***************************************************************************//
// Following are the Response classes where the data is contained in a series.
// of event messages.
class SCCPGeneric_Response extends Response class SCCPGeneric_Response extends Response
{ {
protected $_tables; protected $_tables;
@ -178,18 +179,15 @@ class SCCPGeneric_Response extends Response
public function __construct($rawContent) public function __construct($rawContent)
{ {
parent::__construct($rawContent); parent::__construct($rawContent);
$_fields = array("EventList" => "EventList:", "Message" => "Message:"); // Confirm that there is a list following. This overrides any setting
// made in one of the parent constructs.
$this->_completed = !$this->isList(); $this->_completed = !$this->isList();
} }
public function addEvent($event) public function addEvent($event)
{ {
if ($event->getEventList() === 'start') { // Start of list is handled by the isList function in the Constructor
// Have started a list of events; this may include tables // which also defines the list end event
// Nothing to do with this event, only need to handle
// the events that follow
return;
}
if ( empty($thisSetEventEntryType)) { if ( empty($thisSetEventEntryType)) {
// This is empty as soon as we have received a TableStart. // This is empty as soon as we have received a TableStart.
@ -201,8 +199,9 @@ class SCCPGeneric_Response extends Response
$thisSetEventEntryType = 'undefinedAsThisIsNotASet'; $thisSetEventEntryType = 'undefinedAsThisIsNotASet';
} }
} }
$unknownevent = "FreePBX\\modules\\Sccp_manager\\aminterface\\UnknownEvent"; // Unknown events will cause an exception.
if ($event instanceof $unknownevent) { // All event classes must be defined within Event.class.
if (get_class($event) === 'FreePBX\modules\Sccp_manager\aminterface\UnknownEvent') {
$this->_events[] = $event; $this->_events[] = $event;
return; return;
} }
@ -230,18 +229,20 @@ class SCCPGeneric_Response extends Response
// If counts do not match return false and table will not be // If counts do not match return false and table will not be
//loaded //loaded
if ($event->getKey('TableEntries') != count($this->_tables[$event->getTableName()]['Entries'])) { if ($event->getKey('TableEntries') != count($this->_tables[$event->getTableName()]['Entries'])) {
return $this->_completed = false; return false;
} }
break; break;
//case $eventListEndEvent;
case $this->getKey('eventListEndEvent');
// Have the list end event. The correct number of entries is verified in the event constructor
$this->_events['ClosingEvent'] = $event;
$this->eventListEndEvent = null;
//return $this->_completed = true;
break;
default: default:
// add regular list event // add regular list event
$this->_events[] = $event; $this->_events[] = $event;
} }
if ($event->getEventList() === 'Complete') {
// Received a complete eventList.
return $this->_completed = true;
}
} }
protected function ConvertTableData(String $_tablename, Array $_fkey, Array $_fields) protected function ConvertTableData(String $_tablename, Array $_fkey, Array $_fields)
@ -305,19 +306,6 @@ class SCCPGeneric_Response extends Response
return $result; return $result;
} }
/* public function hasTable()
{
if (is_array($this->_tables)) {
return true;
}
return false;
}
public function getTableNames()
{
return (is_array($this->_tables)) ? array_keys($this->_tables) : null;
}
*/
public function Table2Array( String $tablename ) public function Table2Array( String $tablename )
{ {
$result =array(); $result =array();
@ -330,79 +318,28 @@ class SCCPGeneric_Response extends Response
return $result; return $result;
} }
/* public function Events2Array()
{
$result =array();
foreach ($this->_events as $trow) {
// $tmp_result = $trow->getKeys();
// if (is_array($tmp_result)) {
$result = array_merge($result, $trow->getKeys());
//} else {
// $result [] = $tmp_result;
// }
}
return $result;
}
public function getTable($tablename)
{
if (is_array($this->_tables) && array_key_exists($tablename, $this->_tables)) {
return $this->_tables[$tablename];
}
throw new PAMIException("No such table.");
}
public function getJSON()
{
if (strlen($this->getKey('JSON')) > 0) {
if (($json = json_decode($this->getKey('JSON'), true)) != false) {
return $json;
}
}
throw new AMIException("No JSON Key found to return.");
}
*/
public function getResult() public function getResult()
{ {
if ($this->getKey('JSON') !== null && !empty($this->getKey('JSON'))) {
if (($json = json_decode($this->getKey('JSON'), true)) != false) {
return $json;
}
} else {
return $this->getMessage(); return $this->getMessage();
}
} }
} }
class SCCPJSON_Response extends Response
{
public function __construct($rawContent)
{
parent::__construct($rawContent);
$this->getVariable($rawContent, array("DataType" => "DataType:", "JSONRAW" => "JSON:"));
if (null !== $this->getKey('JSONRAW')) {
$this->setKey('Response', 'Success');
}
return $this->isSuccess();
}
}
class SCCPShowSoftkeySets_Response extends SCCPGeneric_Response class SCCPShowSoftkeySets_Response extends SCCPGeneric_Response
{ {
public function __construct($rawContent) public function __construct($rawContent)
{ {
parent::__construct($rawContent); parent::__construct($rawContent);
$this->setKey('eventlistendevent', 'SCCPShowSoftKeySetsComplete');
} }
public function getResult() public function getResult()
{ {
// $_fields = array('description'=>'description','label'=>'label','lblid'=>'lblid');
return $this->ConvertTableData( return $this->ConvertTableData(
'SoftKeySets', 'SoftKeySets',
array('set','mode'), array('set','mode'),
array('description'=>'description','label'=>'label','lblid'=>'lblid') array('description'=>'description','label'=>'label','lblid'=>'lblid')
); );
// return $result;
} }
} }
@ -411,18 +348,16 @@ class SCCPShowDevices_Response extends SCCPGeneric_Response
public function __construct($rawContent) public function __construct($rawContent)
{ {
parent::__construct($rawContent); parent::__construct($rawContent);
$this->setKey('eventlistendevent', 'SCCPShowDevicesComplete');
} }
public function getResult() public function getResult()
{ {
// $_fields = array('mac'=>'mac','address'=>'address','descr'=>'descr','regstate'=>'status',
// 'token'=>'token','act'=>'act', 'lines'=>'lines','nat'=>'nat','regtime'=>'regtime');
return $this->ConvertTableData( return $this->ConvertTableData(
'Devices', 'Devices',
array('mac'), array('mac'),
array('mac'=>'name','address'=>'address','descr'=>'descr','regstate'=>'status', array('mac'=>'name','address'=>'address','descr'=>'descr','regstate'=>'status',
'token'=>'token','act'=>'act', 'lines'=>'lines','nat'=>'nat','regtime'=>'regtime') 'token'=>'token','act'=>'act', 'lines'=>'lines','nat'=>'nat','regtime'=>'regtime')
); );
// return $result;
} }
} }
@ -431,6 +366,7 @@ class SCCPShowDevice_Response extends SCCPGeneric_Response
public function __construct($rawContent) public function __construct($rawContent)
{ {
parent::__construct($rawContent); parent::__construct($rawContent);
$this->setKey('eventlistendevent', 'SCCPShowDeviceComplete');
} }
public function getResult() public function getResult()
{ {
@ -440,6 +376,7 @@ class SCCPShowDevice_Response extends SCCPGeneric_Response
foreach ($this->_events as $trow) { foreach ($this->_events as $trow) {
$result = array_merge($result, $trow->getKeys()); $result = array_merge($result, $trow->getKeys());
} }
// Now handle label changes so that keys from AMI correspond to db keys in _tables
$result['Buttons'] = $this->ConvertTableData( $result['Buttons'] = $this->ConvertTableData(
'Buttons', 'Buttons',
array('id'), array('id'),
@ -479,6 +416,7 @@ class ExtensionStateList_Response extends SCCPGeneric_Response
public function __construct($rawContent) public function __construct($rawContent)
{ {
parent::__construct($rawContent); parent::__construct($rawContent);
$this->setKey('eventlistendevent', 'ExtensionStateListComplete');
} }
public function getResult() public function getResult()
{ {

View file

@ -6,7 +6,6 @@
* *
* https://www.voip-info.org/asterisk-manager-example-php/ * https://www.voip-info.org/asterisk-manager-example-php/
*/ */
/* !TODO!: Re-Indent this file. -TODO-: What do you mean? coreaccessinterface ?? */
namespace FreePBX\modules\Sccp_manager; namespace FreePBX\modules\Sccp_manager;
@ -17,17 +16,15 @@ class aminterface
var $_error; var $_error;
var $_config; var $_config;
var $_test; var $_test;
var $_countE;
private $_connect_state; private $_connect_state;
// var $ProcessingMessage;
private $_lastActionClass; private $_lastActionClass;
private $_lastActionId; private $_lastActionId;
private $_lastRequestedResponseHandler; private $_lastRequestedResponseHandler;
private $_ProcessingMessage; private $_ProcessingMessage;
private $_DumpMessage; private $_DumpMessage;
private $_eventFactory;
private $_responseFactory;
private $debug_level = 1; private $debug_level = 1;
private $_incomingRawMessage;
private $eventListEndEvent;
public function load_subspace($parent_class = null) public function load_subspace($parent_class = null)
{ {
@ -56,10 +53,10 @@ class aminterface
$this->_error = array(); $this->_error = array();
$this->_config = array('host' => 'localhost', 'user' => '', 'pass' => '', 'port' => '5038', 'tsoket' => 'tcp://', 'timeout' => 30, 'enabled' => true); $this->_config = array('host' => 'localhost', 'user' => '', 'pass' => '', 'port' => '5038', 'tsoket' => 'tcp://', 'timeout' => 30, 'enabled' => true);
$this->_eventListeners = array(); $this->_eventListeners = array();
// $this->_eventFactory = new EventFactoryImpl(\Logger::getLogger('EventFactory')); $this->_incomingMsgObjectList = array();
// $this->_responseFactory = new ResponseFactoryImpl(\Logger::getLogger('ResponseFactory'));
$this->_incomingQueue = array();
$this->_lastActionId = false; $this->_lastActionId = false;
$this->_incomingRawMessage = array();
$this->eventListEndEvent = '';
$fld_conf = array('user' => 'AMPMGRUSER', 'pass' => 'AMPMGRPASS'); $fld_conf = array('user' => 'AMPMGRUSER', 'pass' => 'AMPMGRPASS');
if (isset($amp_conf['AMPMGRUSER'])) { if (isset($amp_conf['AMPMGRUSER'])) {
@ -86,7 +83,7 @@ class aminterface
public function info() public function info()
{ {
$Ver = '13.0.4'; $Ver = '13.0.4';
if ($this->_config['enabled']) { if ($this->_config['enabled']){
return array('Version' => $Ver, return array('Version' => $Ver,
'about' => 'AMI data ver: ' . $Ver, 'test' => get_declared_classes()); 'about' => 'AMI data ver: ' . $Ver, 'test' => get_declared_classes());
} else { } else {
@ -95,9 +92,8 @@ class aminterface
} }
} }
/** /*
* Opens a tcp connection to ami. * Opens a socket connection to ami.
*
*/ */
public function open() public function open()
{ {
@ -134,7 +130,7 @@ class aminterface
return true; return true;
} }
/** /*
* Closes the connection to ami. * Closes the connection to ami.
*/ */
public function close() public function close()
@ -143,80 +139,86 @@ class aminterface
$this->_ProcessingMessage = ''; $this->_ProcessingMessage = '';
@stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR); @stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR);
} }
/*
* Send action message to ami, and wait for Response
*/
public function send($message) public function send($message)
{ {
$_incomingRawMessage = array();
$messageToSend = $message->serialize(); $messageToSend = $message->serialize();
$length = strlen($messageToSend); $length = strlen($messageToSend);
$this->_countE = 0;
$this->_DumpMessage = ''; $this->_DumpMessage = '';
$this->_lastActionId = $message->getActionId(); $this->_lastActionId = $message->getActionID();
$this->_lastRequestedResponseHandler = $message->getResponseHandler(); $this->_lastRequestedResponseHandler = $message->getResponseHandler();
$this->_lastActionClass = $message; $this->_lastActionClass = $message;
$this->_incomingRawMessage[$this->_lastActionId] = '';
$this->eventListIsCompleted = array();
if (@fwrite($this->_socket, $messageToSend) < $length) { if (@fwrite($this->_socket, $messageToSend) < $length) {
$this->_errorException('Could not send message'); $this->_errorException('Could not send message');
return false; return false;
} }
$time_connect = microtime_float(); // Have sent a message and now have to wait for and read the reply
$this->_msgToDebug(90, 'Time: '. ($time_connect)); // The below infinite loop waits for $this->completed to be true.
while (1) { // The loop calls readBuffer, which calls GetMessages, which calls Process
// This loop then continues until we have _thisComplete as an object variable
$this->eventListIsCompleted[$this->_lastActionId] = false;
while (true) {
stream_set_timeout($this->_socket, 1); stream_set_timeout($this->_socket, 1);
// stream_set_timeout($this->_socket, (isset($this->socket_param['timeout']) ? $this->socket_param['timeout'] : 1)); $this->readBuffer();
$this->process();
$time_co = microtime_float();
$this->_msgToDebug(90, 'Time: '. ($time_co-$time_connect));
$info = stream_get_meta_data($this->_socket); $info = stream_get_meta_data($this->_socket);
if ($info['timed_out'] == false) { if ($info['timed_out'] == true) {
$response = $this->getRelated($message); $this->_errorException("Read waittime: " . ($this->socket_param['timeout']) . " exceeded (timeout).\n");
if ($response != false) { return false;
$this->_lastActionId = false; }
$this->_msgToDebug(98, '---- Dump MSG -------'); if ($this->eventListIsCompleted[$this->_lastActionId]) {
$this->_msgToDebug(98, $this->_DumpMessage); $response = $this->_incomingMsgObjectList[$this->_lastActionId];
// need to test that the list was successfully completed here
$allReceived = $response->getClosingEvent()
->listCorrectlyReceived($this->_incomingRawMessage[$this->_lastActionId],
$response->getCountOfEvents());
// now tidy up removing any temp variables or objects
$response->removeClosingEvent();
unset($_incomingRawMessage[$this->_lastActionId]);
unset($this->_incomingMsgObjectList[$this->_lastActionId]);
unset($this->_lastActionId);
if ($allReceived) {
return $response; return $response;
} }
} else { // Something is missing from the events list received via AMI, or
break; // the control parameter at the end of the list has changed.
// This will cause an exception as returning a boolean instead of a Response
// Maybe should handle better, but
// need to break out of the loop as nothing more coming.
try {
throw new \invalidArgumentException("Counts do not match on returned AMI Result");
} catch ( \invalidArgumentException $e) {
echo substr(strrchr(get_class($response), '\\'), 1), " ", $e->getMessage(), "\n";
}
return $response;
} }
} }
$this->_errorException("Read waittime: " . ($this->socket_param['timeout']) . " exceeded (timeout).\n");
} }
protected function getRelated($message) protected function readBuffer ()
{ {
$ret = false; $read = @fread($this->_socket, 65535);
$id = 0; // AMI never returns EOF
$id = $message->getActionID('ActionID'); if ($read === false ) {
if (isset($this->_incomingQueue[$id])) { $this->_errorException('Error reading');
$response = $this->_incomingQueue[$id];
if ($response->isComplete()) {
unset($this->_incomingQueue[$id]);
$ret = $response;
}
} }
return $ret; // Do not return empty Messages
} while ($read == "" ) {
$read = @fread($this->_socket, 65535);
private function _messageToEvent($msg) }
{ // Add read to the rest of buffer from previous read
return $this->_eventFromRaw($msg); $this->_ProcessingMessage .= $read;
$this->getMessages();
} }
protected function getMessages() protected function getMessages()
{ {
$msgs = array(); $msgs = array();
// Read something. // Extract any complete messages and leave remainder for next read
$read = @fread($this->_socket, 65535);
if ($read === false || @feof($this->_socket)) {
$this->_errorException('Error reading');
}
if ($read == "") {
usleep(100);
} else {
$this->_msgToDebug(98, '--- Not Empy AMI MSG --- ');
}
$this->_ProcessingMessage .= $read;
$this->_DumpMessage .= $read;
while (($marker = strpos($this->_ProcessingMessage, aminterface\Message::EOM))) { while (($marker = strpos($this->_ProcessingMessage, aminterface\Message::EOM))) {
$msg = substr($this->_ProcessingMessage, 0, $marker); $msg = substr($this->_ProcessingMessage, 0, $marker);
$this->_ProcessingMessage = substr( $this->_ProcessingMessage = substr(
@ -225,56 +227,39 @@ class aminterface
); );
$msgs[] = $msg; $msgs[] = $msg;
} }
return $msgs; $this->process($msgs);
} }
public function process() public function process(array $msgs)
{ {
$msgs = $this->getMessages();
$this->_msgToDebug(90, $msgs);
$this->_countE++;
if ($this->_countE > 10000) {
$this->_msgToDebug(9, '--- Procecc Die, Dump --- ');
$this->_msgToDebug(9, $this->_DumpMessage);
$this->_msgToDebug(9, '--- END Procecc Die, Dump --- ');
die();
}
foreach ($msgs as $aMsg) { foreach ($msgs as $aMsg) {
$resPos = strpos($aMsg, 'Response:'); // 2 types of message; Response or Event. Response only incudes data
$evePos = strpos($aMsg, 'Event:'); // for JSON response and Command response. All other responses expect
// data in an event list - these events need to be attached to the response.
$resPos = strpos($aMsg, 'Response: '); // Have a Response message. This may not be 0.
$evePos = strpos($aMsg, 'Event: '); // Have an Event Message. This should always be 0.
// Add the incoming message to a string that can be checked
// against the completed message event when it is received.
$this->_incomingRawMessage[$this->_lastActionId] .= "\r\n\r\n" . $aMsg;
if (($resPos !== false) && (($resPos < $evePos) || $evePos === false)) { if (($resPos !== false) && (($resPos < $evePos) || $evePos === false)) {
$response = $this->_msgToResponse($aMsg); // resp Ok $response = $this->_responseObjFromMsg($aMsg); // resp Ok
$this->_incomingQueue[$this->_lastActionId] = $response; $this->eventListEndEvent = $response->getKey('eventlistendevent');
} elseif ($evePos !== false) { $this->_incomingMsgObjectList[$this->_lastActionId] = $response;
$event = $this->_messageToEvent($aMsg); // Event Ok $this->eventListIsCompleted[$this->_lastActionId] = $response->isComplete();
} elseif ($evePos === 0) { // Event must be at the start of the msg.
$this->_msgToDebug(99, '--- Response Type 2 --- '); $event = $this->_eventObjFromMsg($aMsg); // Event Ok
$this->_msgToDebug(99, $aMsg); $this->eventListIsCompleted[$this->_lastActionId] = $event->isComplete();
$this->_msgToDebug(99, '--- Event Response Type 2 --- '); $this->_incomingMsgObjectList[$this->_lastActionId]->addEvent($event);
$this->_msgToDebug(99, $event);
if ($event != null) {
$response = $this->findResponse($event);
// print_r($response);
// print_r('<br>--- E2 Response Type 2 ----------<br>');
if ($response === false || $response->isComplete()) {
$this->dispatch($event); // не работает
} else {
$response->addEvent($event);
}
}
} else { } else {
// broken ami most probably through changes in chan_sccp_b. // broken ami most probably through changes in chan_sccp_b.
//sending a response with events without Event and ActionId // AMI is sending a message which is neither a response nor an event.
$this->_msgToDebug(1, 'resp broken ami'); $this->_msgToDebug(1, 'resp broken ami');
$bMsg = 'Event: ResponseEvent' . "\r\n"; $bMsg = 'Event: ResponseEvent' . "\r\n";
$bMsg .= 'ActionId: ' . $this->_lastActionId . "\r\n" . $aMsg; $bMsg .= 'ActionId: ' . $this->_lastActionId . "\r\n" . $aMsg;
$event = $this->_messageToEvent($bMsg); $event = $this->_eventObjFromMsg($bMsg);
$response = $this->findResponse($event); $this->_incomingMsgObjectList[$this->_lastActionId]->addEvent($event);
$response->addEvent($event);
} }
} }
// print_r('<br>--- EProcecc ----------<br>');
} }
private function _msgToDebug($level, $msg) private function _msgToDebug($level, $msg)
@ -287,74 +272,32 @@ class aminterface
print_r('<br>'); print_r('<br>');
} }
private function _msgToResponse($msg) private function _responseObjFromMsg($message)
{ {
// print_r("<br>------------hmsg----------<br>");
// print_r($this->_lastActionClass);
// print_r($this->_lastRequestedResponseHandler);
// print_r("<br>------------emsg----------<br>");
// print_r($msg);
$response = $this->_msgFromRaw($msg, $this->_lastActionClass, $this->_lastRequestedResponseHandler);
// print_r("<br>------------rmsg----------<br>");
// print_r($response);
// print_r("<br>------------ermsg----------<br>");
$actionId = $response->getActionId();
if ($actionId === null) {
$actionId = $this->_lastActionId;
$response->setActionId($this->_lastActionId);
}
return $response;
}
/*
*
*
*/
public function _msgFromRaw($message, $requestingaction = false, $responseHandler = false)
{
$_className = false; $_className = false;
$responseclass = '\\FreePBX\\modules\\Sccp_manager\\aminterface\\Generic_Response'; $responseClass = '\\FreePBX\\modules\\Sccp_manager\\aminterface\\Generic_Response';
if ($responseHandler != false) { if ($this->_lastRequestedResponseHandler != false) {
$_className = '\\FreePBX\\modules\\Sccp_manager\\aminterface\\' . $responseHandler . '_Response'; $_className = '\\FreePBX\\modules\\Sccp_manager\\aminterface\\' . $this->_lastRequestedResponseHandler . '_Response';
} elseif ($requestingaction != false) {
$_className = '\\FreePBX\\modules\\Sccp_manager\\' . substr(get_class($requestingaction), 20, -6) . '_Response';
} }
if ($_className) { if ($_className) {
if (class_exists($_className, true)) { if (class_exists($_className, true)) {
$responseclass = $_className; $responseClass = $_className;
} elseif ($responseHandler != false) { } elseif ($responseHandler != false) {
$this->_errorException('Response Class ' . $_className . ' requested via responseHandler, could not be found'); $this->_errorException('Response Class ' . $_className . ' requested via responseHandler, could not be found');
} }
} }
return new $responseclass($message); $response = new $responseClass($message);
} $actionId = $response->getActionID();
if ($actionId === null) {
protected function _errorException($msg) $response->setActionId($this->_lastActionId);
{
$this->_error[] = $msg;
}
/*
* Replace or dublicate to AMI interface
*/
public function _eventFromRaw($message)
{
$eventStart = strpos($message, 'Event: ') + 7;
if ($eventStart > strlen($message)) {
return new aminterface\UnknownEvent($message);
} }
return $response;
$eventEnd = strpos($message, aminterface\Message::EOL, $eventStart); }
if ($eventEnd === false) { public function _eventObjFromMsg($message)
$eventEnd = strlen($message); {
} $eventType = explode(aminterface\Message::EOL,$message,2);
$name = substr($message, $eventStart, $eventEnd - $eventStart); $name = trim(explode(':',$eventType[0],2)[1]);
$className = '\\FreePBX\\modules\\Sccp_manager\\aminterface\\' . $name . '_Event'; $className = '\\FreePBX\\modules\\Sccp_manager\\aminterface\\' . $name . '_Event';
if (class_exists($className, true) === false) { if (class_exists($className, true) === false) {
$className = '\\FreePBX\\modules\\Sccp_manager\\aminterface\\UnknownEvent'; $className = '\\FreePBX\\modules\\Sccp_manager\\aminterface\\UnknownEvent';
@ -362,40 +305,6 @@ class aminterface
return new $className($message); return new $className($message);
} }
public function _respnceFromRaw($message, $requestingaction = false, $responseHandler = false)
{
$responseclass = '\\FreePBX\\modules\\Sccp_manager\\aminterface\\Response';
$_className = false;
if ($responseHandler != false) {
$_className = '\\FreePBX\\modules\\Sccp_manager\\aminterface\\' . $responseHandler . '_Response';
} elseif ($requestingaction != false) {
$_className = '\\FreePBX\\modules\\Sccp_manager\\aminterface\\' . substr(get_class($requestingaction), 20, -6) . '_Response';
}
if ($_className) {
if (class_exists($_className, true)) {
$responseclass = $_className;
} elseif ($responseHandler != false) {
throw new AMIException('Response Class ' . $_className . ' requested via responseHandler, could not be found');
}
}
// if ($this->_logger->isDebugEnabled()) $this->_logger->debug('Created: ' . $responseclass . "\n");
print_r($responseclass);
die();
return new $responseclass($message);
}
// protected function findResponse(IncomingMessage $message) {
protected function findResponse($message)
{
$actionId = $message->getActionId();
if (isset($this->_incomingQueue[$actionId])) {
return $this->_incomingQueue[$actionId];
}
return false;
}
protected function dispatch($message) protected function dispatch($message)
{ {
print_r("<br>------------dispatch----------<br>"); print_r("<br>------------dispatch----------<br>");
@ -451,8 +360,7 @@ class aminterface
$result = array(); $result = array();
if ($this->_connect_state) { if ($this->_connect_state) {
$_action = new \FreePBX\modules\Sccp_manager\aminterface\ExtensionStateListAction(); $_action = new \FreePBX\modules\Sccp_manager\aminterface\ExtensionStateListAction();
$_response = $this->send($_action); $_res = $this->send($_action)->getResult();
$_res = $_response->getResult();
foreach ($_res as $key => $value) { foreach ($_res as $key => $value) {
foreach ($value as $key2 => $value2) { foreach ($value as $key2 => $value2) {
$result[$key.'@'.$key2] = $key.'@'.$key2; $result[$key.'@'.$key2] = $key.'@'.$key2;
@ -467,8 +375,7 @@ class aminterface
$result = array(); $result = array();
if ($this->_connect_state) { if ($this->_connect_state) {
$_action = new \FreePBX\modules\Sccp_manager\aminterface\SCCPShowSoftkeySetsAction(); $_action = new \FreePBX\modules\Sccp_manager\aminterface\SCCPShowSoftkeySetsAction();
$_response = $this->send($_action); $_res = $this->send($_action)->getResult();
$_res = $_response->getResult();
foreach ($_res as $key => $value) { foreach ($_res as $key => $value) {
$result[$key] = $key; $result[$key] = $key;
} }
@ -480,9 +387,8 @@ class aminterface
$result = array(); $result = array();
if ($this->_connect_state) { if ($this->_connect_state) {
$_action = new \FreePBX\modules\Sccp_manager\aminterface\SCCPShowDevicesAction(); $_action = new \FreePBX\modules\Sccp_manager\aminterface\SCCPShowDevicesAction();
$_response = $this->send($_action); $_res = $this->send($_action)->getResult();
$result = $_response->getResult(); foreach ($_res as $key => $value) {
foreach ($result as $key => $value) {
$result[$key]['name'] = $key; $result[$key]['name'] = $key;
} }
} }
@ -493,8 +399,7 @@ class aminterface
$result = array(); $result = array();
if ($this->_connect_state) { if ($this->_connect_state) {
$_action = new \FreePBX\modules\Sccp_manager\aminterface\SCCPShowDeviceAction($devicename); $_action = new \FreePBX\modules\Sccp_manager\aminterface\SCCPShowDeviceAction($devicename);
$_response = $this->send($_action); $result = $this->send($_action)->getResult();
$result = $_response->getResult();
$result['MAC_Address'] = $result['macaddress']; $result['MAC_Address'] = $result['macaddress'];
} }
return $result; return $result;
@ -510,7 +415,6 @@ class aminterface
$_response = $this->send($_action); $_response = $this->send($_action);
$result['data'] = 'Device :'.$devicename.' Result: '.$_response->getMessage(); $result['data'] = 'Device :'.$devicename.' Result: '.$_response->getMessage();
$result['Response']=$_response->getKey('Response'); $result['Response']=$_response->getKey('Response');
// $result = $_response->getResult();
} }
return $result; return $result;
} }
@ -533,55 +437,55 @@ class aminterface
$metadata = $this->send($_action)->getResult(); $metadata = $this->send($_action)->getResult();
} }
//return $result; //return $result;
if ($metadata && array_key_exists("Version", $metadata)) { if (isset($metadata['Version'])) {
$result["Version"] = $metadata["Version"]; $result['Version'] = $metadata['Version'];
$version_parts = explode(".", $metadata["Version"]); $version_parts = explode('.', $metadata['Version']);
$result["vCode"] = 0; $result['vCode'] = 0;
if ($version_parts[0] == "4") { if ($version_parts[0] === 4) {
switch ($version_parts[1]) { switch ($version_parts[1]) {
case "1": case 1:
$result["vCode"] = 410; $result['vCode'] = 410;
break; break;
case "2": case 2:
$result["vCode"] = 420; $result['vCode'] = 420;
break; break;
case 3. . .5: case 3. . .5:
if($version_parts[2] == "3"){ if($version_parts[2] == 3){
$result["vCode"] = 433; $result['vCode'] = 433;
} else { } else {
$result["vCode"] = 430; $result['vCode'] = 430;
} }
break; break;
default: default:
$result["vCode"] = 400; $result['vCode'] = 400;
break; break;
} }
} }
/* Revision got replaced by RevisionHash in 10404 (using the hash does not work) */ /* Revision got replaced by RevisionHash in 10404 (using the hash does not work) */
if (array_key_exists("Revision", $metadata)) { if (array_key_exists("Revision", $metadata)) {
if (base_convert($metadata["Revision"], 16, 10) == base_convert('702487a', 16, 10)) { if (base_convert($metadata["Revision"], 16, 10) == base_convert('702487a', 16, 10)) {
$result["vCode"] = 431; $result['vCode'] = 431;
} }
if (base_convert($metadata["Revision"], 16, 10) >= "10403") { if (base_convert($metadata["Revision"], 16, 10) >= "10403") {
$result["vCode"] = 431; $result['vCode'] = 431;
} }
} }
if (array_key_exists("RevisionHash", $metadata)) { if (array_key_exists("RevisionHash", $metadata)) {
$result["RevisionHash"] = $metadata["RevisionHash"]; $result['RevisionHash'] = $metadata["RevisionHash"];
} else { } else {
$result["RevisionHash"] = ''; $result['RevisionHash'] = '';
} }
if (array_key_exists("RevisionNum", $metadata)) { if (isset($metadata['RevisionNum'])) {
$result["RevisionNum"] = $metadata["RevisionNum"]; $result['RevisionNum'] = $metadata['RevisionNum'];
if ($metadata["RevisionNum"] >= "10403") { // new method, RevisionNum is incremental if ($metadata['RevisionNum'] >= 10403) { // new method, RevisionNum is incremental
$result["vCode"] = 432; $result['vCode'] = 432;
} }
if ($metadata["RevisionNum"] >= "10491") { // new method, RevisionNum is incremental if ($metadata['RevisionNum'] >= 10491) { // new method, RevisionNum is incremental
$result["vCode"] = 433; $result['vCode'] = 433;
} }
} }
if (array_key_exists("ConfigureEnabled", $metadata)) { if (isset($metadata['ConfigureEnabled'])) {
$result["futures"] = implode(';', $metadata["ConfigureEnabled"]); $result['futures'] = implode(';', $metadata['ConfigureEnabled']);
} }
} }
return $result; return $result;

View file

@ -14,7 +14,8 @@ class srvinterface {
var $error; var $error;
var $_info; var $_info;
var $ami_mode; var $ami_mode = false;
var $useAmiInterface = true;
public function __construct($parent_class = null) { public function __construct($parent_class = null) {
$this->paren_class = $parent_class; $this->paren_class = $parent_class;
@ -51,9 +52,13 @@ class srvinterface {
} }
} }
if ($this->aminterface->status()) { if ($this->aminterface->status()) {
$this->aminterface->open(); // Ami is not hard disabled in Amiinterface __construct 54.
if ($this->aminterface->open()) {
// Can open a connection. Now check compatibility with chan-sccp.
// will return true if compatible.
$this->ami_mode = $this->get_compatible_sccp(true)[1];
}
} }
$this->ami_mode = $this->aminterface->status();
} }
public function info() { public function info() {
@ -198,24 +203,35 @@ class srvinterface {
} }
} }
public function get_compatible_sccp() { public function get_compatible_sccp($revNumComp=false) {
// only called with args from installer to get revision and compatibility
$res = $this->getSCCPVersion(); $res = $this->getSCCPVersion();
if (empty($res)) { if (empty($res)) {
return 0; return 0;
} }
switch ($res["vCode"]) { switch ($res["vCode"]) {
case 0: case 0:
return 0; $retval = 0;
break;
case 433: case 433:
return 433; $retval = 433;
break;
case 432: case 432:
$retval = 430;
break;
case 431: case 431:
return 431; $retval = 431;
break;
default: default:
return 430; $retval = 430;
} }
if ($res['RevisionNum'] < 11063) {
$this->useAmiInterface = false;
}
if ($revNumComp) {
return array($retval, $this->useAmiInterface);
}
return $retval;
} }
public function getSCCPVersion() { public function getSCCPVersion() {
@ -227,7 +243,6 @@ class srvinterface {
} }
public function sccp_list_keysets() { public function sccp_list_keysets() {
if ($this->ami_mode) { if ($this->ami_mode) {
return $this->aminterface->sccp_list_keysets(); return $this->aminterface->sccp_list_keysets();
} else { } else {

View file

@ -241,7 +241,7 @@ $(document).ready(function () {
} }
e.preventDefault(); e.preventDefault();
}); });
// Form.buttons - Form.adddevice // Form.buttons - Form.adddevice
$('.futuretype').change(function (e) { $('.futuretype').change(function (e) {
var kid = $(this).data('id'); var kid = $(this).data('id');
@ -256,7 +256,7 @@ $(document).ready(function () {
} }
} }
}); });
}); });
$('.buttontype').change(function (e) { $('.buttontype').change(function (e) {
@ -627,7 +627,7 @@ $(document).ready(function () {
i++; i++;
}); });
} }
if (datas === '') { if (datas === '') {
if (confirm(conf_msg)) { if (confirm(conf_msg)) {
datas = 'name[0]=all'; datas = 'name[0]=all';
@ -1011,7 +1011,7 @@ function bs_page_reload()
} }
function bs_alert(data, status, reload) function bs_alert(data, status, reload)
{ {
if (document.getElementById('hwalert') === null) { if (document.getElementById('hwalert') === null) {
if (Array.isArray(data)) { if (Array.isArray(data)) {
data.forEach(function (entry) { data.forEach(function (entry) {
@ -1027,7 +1027,7 @@ function bs_alert(data, status, reload)
if (status === true) { if (status === true) {
modal.find('.modal-title').text('Operation result'); modal.find('.modal-title').text('Operation result');
} else { } else {
modal.find('.modal-title').text('Erroe operation '); modal.find('.modal-title').text('Error operation ');
} }
} else { } else {
modal.find('.modal-title').text('Operation result'); modal.find('.modal-title').text('Operation result');
@ -1075,4 +1075,3 @@ function sleep(milliseconds)
} }
} }
} }

View file

@ -18,6 +18,7 @@ global $version;
global $srvinterface; global $srvinterface;
global $mobile_hw; global $mobile_hw;
$mobile_hw = '0'; $mobile_hw = '0';
global $useAmiInterface;
$class = "\\FreePBX\\Modules\\Sccp_manager\\srvinterface"; $class = "\\FreePBX\\Modules\\Sccp_manager\\srvinterface";
if (!class_exists($class, false)) { if (!class_exists($class, false)) {
@ -378,6 +379,7 @@ $table_req = array('sccpdevice', 'sccpline');
$ss = FreePBX::create()->Sccp_manager; $ss = FreePBX::create()->Sccp_manager;
$astman = FreePBX::create()->astman; $astman = FreePBX::create()->astman;
$sccp_compatible = 0; $sccp_compatible = 0;
$chanSCCPWarning = true;
//$db_config = $db_config_v0; //$db_config = $db_config_v0;
$db_config = ''; $db_config = '';
@ -448,13 +450,13 @@ function CheckAsteriskVersion()
function CheckChanSCCPCompatible() function CheckChanSCCPCompatible()
{ {
global $chanSCCPWarning;
global $srvinterface, $astman; global $srvinterface, $astman;
if (!$astman) { if (!$astman) {
ie_freepbx('No asterisk manager connection provided!. Installation Failed'); ie_freepbx('No asterisk manager connection provided!. Installation Failed');
} }
$sccp_compatible = $srvinterface->get_compatible_sccp(); // calling with true returns array with compatibility and RevisionNumber
outn("<li>" . _("Sccp model Compatible code : ") . $sccp_compatible . "</li>"); return $srvinterface->get_compatible_sccp(true);
return $sccp_compatible;
} }
function InstallDB_Buttons() function InstallDB_Buttons()
@ -967,7 +969,11 @@ function Setup_RealTime()
CheckSCCPManagerDBTables($table_req); CheckSCCPManagerDBTables($table_req);
#CheckPermissions(); #CheckPermissions();
CheckAsteriskVersion(); CheckAsteriskVersion();
$sccp_compatible = CheckChanSCCPCompatible(); $sccp_version = array();
$sccp_version = CheckChanSCCPCompatible();
$sccp_compatible = $sccp_version[0];
$chanSCCPWarning = $sccp_version[1] ^= 1;
outn("<li>" . _("Sccp model Compatible code : ") . $resultReturned[0] . "</li>");
if ($sccp_compatible == 0) { if ($sccp_compatible == 0) {
// die_freepbx('Chan Sccp not Found. Install it before continuing'); // die_freepbx('Chan Sccp not Found. Install it before continuing');
outn("<br>"); outn("<br>");
@ -998,6 +1004,10 @@ if (!$sccp_db_ver) {
InstallDB_createButtonConfigTrigger(); InstallDB_createButtonConfigTrigger();
InstallDB_CreateSccpDeviceConfigView($sccp_compatible); InstallDB_CreateSccpDeviceConfigView($sccp_compatible);
InstallDB_updateDBVer($sccp_compatible); InstallDB_updateDBVer($sccp_compatible);
if ($chanSCCPWarning) {
outn("<br>");
outn("<font color='red'>Warning: Upgrade chan_sccp_b to use full ami functionality</font>");
}
if (!$sccp_db_ver) { if (!$sccp_db_ver) {
Setup_RealTime(); Setup_RealTime();
outn("<br>"); outn("<br>");

View file

@ -36,6 +36,10 @@ $info['aminterface'] = $this->aminterface->info();
$info['XML'] = $this->xmlinterface->info(); $info['XML'] = $this->xmlinterface->info();
$info['sccp_class'] = $driver['sccp']; $info['sccp_class'] = $driver['sccp'];
$info['Core_sccp'] = array('Version' => $core['Version'], 'about' => 'Sccp ver.' . $core['Version'] . ' r' . $core['vCode'] . ' Revision :' . $core['RevisionNum'] . ' Hash :' . $core['RevisionHash']); $info['Core_sccp'] = array('Version' => $core['Version'], 'about' => 'Sccp ver.' . $core['Version'] . ' r' . $core['vCode'] . ' Revision :' . $core['RevisionNum'] . ' Hash :' . $core['RevisionHash']);
if (!$this->srvinterface->useAmiInterface) {
$info['aminterface']['about'] .= ' -- Disabled';
$info['Core_sccp'] = array('Version' => $core['Version'], 'about' => 'Sccp ver.' . $core['Version'] . ' r' . $core['vCode'] . ' Revision :' . $core['RevisionNum'] . ' Hash :' . $core['RevisionHash'] . ' ----Warning: Upgrade chan_sccp to use full ami functionality');
}
$info['Asterisk'] = array('Version' => FreePBX::Config()->get('ASTVERSION'), 'about' => 'Asterisk.'); $info['Asterisk'] = array('Version' => FreePBX::Config()->get('ASTVERSION'), 'about' => 'Asterisk.');