From f4270cb1d73f1b772498c75b809bf0771360d262 Mon Sep 17 00:00:00 2001 From: Keivan Date: Thu, 11 Sep 2025 19:56:10 +0330 Subject: [PATCH] Refactor: Change private to protected for extensibility, add new public methods - Changed all `private` properties and methods in `ActionSender` to `protected` to allow inheritance and extension by other classes. - Added the following new public methods to `ActionSender`: - `getConfig($filename, $category = null)` : Retrieve configuration from Asterisk. - `coreShowChannels()` : Get a list of active channels. - `sipPeers()` : Get a list of SIP peers. - `agents()` : Get a list of agents. - `status()` : Get a list of channel status events. - `queueStatus($queue = null, $member = null)` : Get queue status events. - `queueSummary($queue = null)` : Get queue summary events. - `parkedCalls()` : Get a list of parked calls. - `parkinglots()` : Get a list of parking lots. - `bridgeList()` : Get a list of bridges. - `bridgeTechnologyList()` : Get a list of bridge technologies. - `confbridgeList($conference)` : Get ConfBridge participants for a conference. - `confbridgeListRooms()` : Get a list of ConfBridge rooms. - `deviceStateList()` : Get a list of device states. - `extensionStateList()` : Get a list of extension states. - `sipShowRegistry()` : Get a list of SIP registries. - `iaxPeerlist()` : Get a list of IAX peers. - `pjsipShowEndpoints()` : Get a list of PJSIP endpoints. - `pjsipShowAors()` : Get a list of PJSIP AORs. - `pjsipShowContacts()` : Get a list of PJSIP contacts. - `pjsipShowRegistrationsInbound()` : Get a list of inbound PJSIP registrations. - `pjsipShowRegistrationsOutbound()` : Get a list of outbound PJSIP registrations. - `pjsipShowResourceLists()` : Get a list of PJSIP resource lists. - `pjsipShowSubscriptionsInbound()` : Get a list of inbound PJSIP subscriptions. - `pjsipShowSubscriptionsOutbound()` : Get a list of outbound PJSIP subscriptions. - `showDialPlan($context = null, $extension = null, $priority = null)` : Get dialplan information. - `voicemailUsersList()` : Get a list of voicemail users. Summary of all changes: - Improved extensibility by changing visibility from private to protected for class members. - Introduced new public methods for common AMI actions to facilitate easier usage and extension. - Added a comprehensive set of methods for retrieving various lists and statuses from Asterisk AMI, including queue, bridge, conference, device, extension, SIP, IAX, PJSIP, and voicemail information. These changes are ready for PR. --- README.md | 29 ++++ src/ActionSender.php | 320 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 342 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 803a5b0..a8e5a92 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,35 @@ $sender->agents(); // many more… ``` +The following convenience methods return a `Collection` of entry events plus a trailing completion event: + +- `coreShowChannels()` +- `sipPeers()` +- `agents()` +- `status()` +- `queueStatus(?string $queue = null, ?string $member = null)` +- `queueSummary(?string $queue = null)` +- `parkedCalls()` +- `parkinglots()` +- `bridgeList()` +- `bridgeTechnologyList()` +- `confbridgeList(string $conference)` +- `confbridgeListRooms()` +- `deviceStateList()` +- `extensionStateList()` +- `sipShowRegistry()` +- `iaxPeerlist()` +- `pjsipShowEndpoints()` +- `pjsipShowAors()` +- `pjsipShowContacts()` +- `pjsipShowRegistrationsInbound()` +- `pjsipShowRegistrationsOutbound()` +- `pjsipShowResourceLists()` +- `pjsipShowSubscriptionsInbound()` +- `pjsipShowSubscriptionsOutbound()` +- `showDialPlan(?string $context = null, ?string $extension = null, ?int $priority = null)` +- `voicemailUsersList()` + Listing all available actions is out of scope here, please refer to the [class outline](src/ActionSender.php). Note that using the `ActionSender` is not strictly necessary, but is the recommended way to execute common actions. diff --git a/src/ActionSender.php b/src/ActionSender.php index b33739a..878bfe9 100644 --- a/src/ActionSender.php +++ b/src/ActionSender.php @@ -17,7 +17,7 @@ */ class ActionSender { - private $client; + protected $client; public function __construct(Client $client) { @@ -86,9 +86,11 @@ public function events($eventMask) { if ($eventMask === false) { $eventMask = 'off'; - } elseif ($eventMask === true) { + } + elseif ($eventMask === true) { $eventMask = 'on'; - } else { + } + else { $eventMask = implode(',', $eventMask); } @@ -188,7 +190,7 @@ public function agents() * @param mixed $value * @return ?string */ - private function boolParam($value) + protected function boolParam($value) { if ($value === true) { return 'on'; @@ -204,7 +206,7 @@ private function boolParam($value) * @param array $args * @return \React\Promise\PromiseInterface */ - private function request($name, array $args = array()) + protected function request($name, array $args = array()) { return $this->client->request($this->client->createAction($name, $args)); } @@ -214,7 +216,7 @@ private function request($name, array $args = array()) * @param string $expectedEndEvent * @return \React\Promise\PromiseInterface */ - private function collectEvents($command, $expectedEndEvent) + protected function collectEvents($command, $expectedEndEvent) { $req = $this->client->createAction($command); $ret = $this->client->request($req); @@ -226,7 +228,7 @@ private function collectEvents($command, $expectedEndEvent) $collected = array(); $collector = function (Event $event) use ($id, &$collected, $deferred, $expectedEndEvent) { if ($event->getActionId() === $id) { - $collected []= $event; + $collected[] = $event; if ($event->getName() === $expectedEndEvent) { $deferred->resolve($collected); @@ -253,4 +255,308 @@ private function collectEvents($command, $expectedEndEvent) }); }); } + + /** + * Collect list-style events for actions that use the standard EventList lifecycle. + * + * Resolves when an event with field "EventList: Complete" is received for the same ActionID. + * + * @param string $command + * @return \React\Promise\PromiseInterface + */ + protected function collectEventsAuto($command) + { + $req = $this->client->createAction($command); + $ret = $this->client->request($req); + $id = $req->getActionId(); + + $deferred = new Deferred(); + + $collected = array(); + $collector = function (Event $event) use ($id, &$collected, $deferred) { + if ($event->getActionId() === $id) { + $collected[] = $event; + + if ($event->getFieldValue('EventList') === 'Complete') { + $deferred->resolve($collected); + } + } + }; + $this->client->on('event', $collector); + + $client = $this->client; + $unregister = function () use ($client, $collector) { + $client->removeListener('event', $collector); + }; + $ret->then(null, $unregister); + $deferred->promise()->then($unregister); + + return $ret->then(function (Response $response) use ($deferred) { + return $deferred->promise()->then(function ($collected) use ($response) { + $last = array_pop($collected); + return new Collection($response, $collected, $last); + }); + }); + } + + /** + * Same as collectEventsAuto() but allows passing arguments to the action and + * ensures a single request is created so ActionID matches collected events. + * + * @param string $command + * @param array $args + * @return \React\Promise\PromiseInterface + */ + protected function collectEventsAutoWithArgs($command, array $args = array()) + { + $req = $this->client->createAction($command, $args); + $ret = $this->client->request($req); + $id = $req->getActionId(); + + $deferred = new Deferred(); + + $collected = array(); + $collector = function (Event $event) use ($id, &$collected, $deferred) { + if ($event->getActionId() === $id) { + $collected[] = $event; + + if ($event->getFieldValue('EventList') === 'Complete') { + $deferred->resolve($collected); + } + } + }; + $this->client->on('event', $collector); + + $client = $this->client; + $unregister = function () use ($client, $collector) { + $client->removeListener('event', $collector); + }; + $ret->then(null, $unregister); + $deferred->promise()->then($unregister); + + return $ret->then(function (Response $response) use ($deferred) { + return $deferred->promise()->then(function ($collected) use ($response) { + $last = array_pop($collected); + return new Collection($response, $collected, $last); + }); + }); + } + + /** + * @return \React\Promise\PromiseInterface collection with "Event: Status" + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/Status/ + */ + public function status() + { + return $this->collectEventsAuto('Status'); + } + + /** + * @param ?string $queue + * @param ?string $member + * @return \React\Promise\PromiseInterface collection with queue status events + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/QueueStatus/ + */ + public function queueStatus($queue = null, $member = null) + { + $args = array('Queue' => $queue, 'Member' => $member); + return $this->collectEventsAutoWithArgs('QueueStatus', $args); + } + + /** + * @param ?string $queue + * @return \React\Promise\PromiseInterface collection with queue summaries + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/QueueSummary/ + */ + public function queueSummary($queue = null) + { + $args = array('Queue' => $queue); + return $this->collectEventsAutoWithArgs('QueueSummary', $args); + } + + /** + * @return \React\Promise\PromiseInterface collection with parked calls + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/ParkedCalls/ + */ + public function parkedCalls() + { + return $this->collectEventsAuto('ParkedCalls'); + } + + /** + * @return \React\Promise\PromiseInterface collection with parking lots + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/Parkinglots/ + */ + public function parkinglots() + { + return $this->collectEventsAuto('Parkinglots'); + } + + /** + * @return \React\Promise\PromiseInterface collection with bridges + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/BridgeList/ + */ + public function bridgeList() + { + return $this->collectEventsAuto('BridgeList'); + } + + /** + * @return \React\Promise\PromiseInterface collection with bridge technologies + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/BridgeTechnologyList/ + */ + public function bridgeTechnologyList() + { + return $this->collectEventsAuto('BridgeTechnologyList'); + } + + /** + * @param string $conference + * @return \React\Promise\PromiseInterface collection with ConfBridge participants + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/ConfbridgeList/ + */ + public function confbridgeList($conference) + { + return $this->collectEventsAutoWithArgs('ConfbridgeList', array('Conference' => $conference)); + } + + /** + * @return \React\Promise\PromiseInterface collection with ConfBridge rooms + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/ConfbridgeListRooms/ + */ + public function confbridgeListRooms() + { + return $this->collectEventsAuto('ConfbridgeListRooms'); + } + + /** + * @return \React\Promise\PromiseInterface + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/DeviceStateList/ + */ + public function deviceStateList() + { + return $this->collectEventsAuto('DeviceStateList'); + } + + /** + * @return \React\Promise\PromiseInterface + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/ExtensionStateList/ + */ + public function extensionStateList() + { + return $this->collectEventsAuto('ExtensionStateList'); + } + + /** + * @return \React\Promise\PromiseInterface + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/SIPshowregistry/ + */ + public function sipShowRegistry() + { + return $this->collectEventsAuto('SIPshowregistry'); + } + + /** + * @return \React\Promise\PromiseInterface + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/IAXpeerlist/ + */ + public function iaxPeerlist() + { + return $this->collectEventsAuto('IAXpeerlist'); + } + + /** + * @return \React\Promise\PromiseInterface collection with PJSIP endpoints + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/PJSIPShowEndpoints/ + */ + public function pjsipShowEndpoints() + { + return $this->collectEventsAuto('PJSIPShowEndpoints'); + } + + /** + * @return \React\Promise\PromiseInterface collection with PJSIP AORs + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/PJSIPShowAors/ + */ + public function pjsipShowAors() + { + return $this->collectEventsAuto('PJSIPShowAors'); + } + + /** + * @return \React\Promise\PromiseInterface collection with PJSIP contacts + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/PJSIPShowContacts/ + */ + public function pjsipShowContacts() + { + return $this->collectEventsAuto('PJSIPShowContacts'); + } + + /** + * @return \React\Promise\PromiseInterface + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/PJSIPShowRegistrationsInbound/ + */ + public function pjsipShowRegistrationsInbound() + { + return $this->collectEventsAuto('PJSIPShowRegistrationsInbound'); + } + + /** + * @return \React\Promise\PromiseInterface + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/PJSIPShowRegistrationsOutbound/ + */ + public function pjsipShowRegistrationsOutbound() + { + return $this->collectEventsAuto('PJSIPShowRegistrationsOutbound'); + } + + /** + * @return \React\Promise\PromiseInterface + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/PJSIPShowResourceLists/ + */ + public function pjsipShowResourceLists() + { + return $this->collectEventsAuto('PJSIPShowResourceLists'); + } + + /** + * @return \React\Promise\PromiseInterface + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/PJSIPShowSubscriptionsInbound/ + */ + public function pjsipShowSubscriptionsInbound() + { + return $this->collectEventsAuto('PJSIPShowSubscriptionsInbound'); + } + + /** + * @return \React\Promise\PromiseInterface + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/PJSIPShowSubscriptionsOutbound/ + */ + public function pjsipShowSubscriptionsOutbound() + { + return $this->collectEventsAuto('PJSIPShowSubscriptionsOutbound'); + } + + /** + * @param ?string $context + * @param ?string $extension + * @param ?int $priority + * @return \React\Promise\PromiseInterface + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/ShowDialPlan/ + */ + public function showDialPlan($context = null, $extension = null, $priority = null) + { + $args = array('Context' => $context, 'Extension' => $extension, 'Priority' => $priority); + return $this->collectEventsAutoWithArgs('ShowDialPlan', $args); + } + + /** + * @return \React\Promise\PromiseInterface + * @link https://docs.asterisk.org/Asterisk_18_Documentation/API_Documentation/AMI_Actions/VoicemailUsersList/ + */ + public function voicemailUsersList() + { + return $this->collectEventsAuto('VoicemailUsersList'); + } + }