mirror of
https://github.com/fusionpbx/fusionpbx.git
synced 2025-12-30 00:53:50 +00:00
Message body was not checked. This will update the get and set methods to ensure if the body is present the body can be set and retrieved.
423 lines
12 KiB
PHP
423 lines
12 KiB
PHP
<?php
|
|
|
|
/*
|
|
* FusionPBX
|
|
* Version: MPL 1.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is FusionPBX
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Mark J Crane <markjcrane@fusionpbx.com>
|
|
* Portions created by the Initial Developer are Copyright (C) 2008-2025
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Mark J Crane <markjcrane@fusionpbx.com>
|
|
* Tim Fry <tim@fusionpbx.com>
|
|
*/
|
|
|
|
/**
|
|
* Tracks switch events in an object instead of array
|
|
*
|
|
* @author Tim Fry <tim@fusionpbx.com>
|
|
*/
|
|
class event_message implements filterable_payload {
|
|
|
|
const BODY_ARRAY_KEY = '_body';
|
|
|
|
const EVENT_SWAP_API = 0x01;
|
|
const EVENT_USE_SUBCLASS = 0x02;
|
|
|
|
// Default keys in the event to capture
|
|
public static $keys = [];
|
|
|
|
/**
|
|
* Associative array to store the event with the key name always lowercase and the hyphen replaced with an underscore
|
|
* @var array
|
|
*/
|
|
private $event;
|
|
|
|
/**
|
|
* Body of the SIP MESSAGE used in SMS
|
|
* @var string
|
|
*/
|
|
private $body;
|
|
|
|
/**
|
|
* Only permitted keys on this list are allowed to be inserted in to the event_message object
|
|
* @var filter
|
|
*/
|
|
private $event_filter;
|
|
|
|
/**
|
|
* Creates an event message
|
|
* @param array $event_array
|
|
* @param filter $filter
|
|
*/
|
|
public function __construct(array $event_array, ?filter $filter = null) {
|
|
|
|
// Set the event to an empty array
|
|
$this->event = [];
|
|
|
|
// Clear the memory area for body and key_filter
|
|
$this->body = null;
|
|
|
|
$this->event_filter = $filter;
|
|
|
|
// Set the event array to match
|
|
foreach ($event_array as $name => $value) {
|
|
$this->__set($name, $value);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Sanitizes the key name and then stores the value in the event property as an associative array
|
|
* @param string $name
|
|
* @param string $value
|
|
* @return void
|
|
*/
|
|
public function __set(string $name, $value) {
|
|
self::sanitize_event_key($name);
|
|
|
|
// Use the filter chain to ensure the key is allowed
|
|
if ($this->event_filter === null || ($this->event_filter)($name, $value)) {
|
|
if ($name === self::BODY_ARRAY_KEY) {
|
|
$this->body = $value;
|
|
return;
|
|
}
|
|
$this->event[$name] = $value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sanitizes the key name and then returns the value stored in the event property
|
|
* @param string $name Name of the event key
|
|
* @return string Returns the stored value or an empty string
|
|
*/
|
|
public function __get(string $name) {
|
|
self::sanitize_event_key($name);
|
|
if ($name === 'name') $name = 'event_name';
|
|
if ($name === self::BODY_ARRAY_KEY) {
|
|
return $this->body;
|
|
}
|
|
return $this->event[$name] ?? '';
|
|
}
|
|
|
|
/**
|
|
* Return an array representation of this object.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function __toArray(): array {
|
|
$array = [];
|
|
foreach ($this->event as $key => $value) {
|
|
$array[$key] = $value;
|
|
}
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Convert the current object into an array representation.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function to_array(): array {
|
|
return $this->__toArray();
|
|
}
|
|
|
|
/**
|
|
* Apply a filter to the event collection.
|
|
*
|
|
* @param filter $filter The filter function to apply
|
|
*
|
|
* @return self This object for method chaining
|
|
*/
|
|
public function apply_filter(filter $filter) {
|
|
foreach ($this->event as $key => $value) {
|
|
$result = ($filter)($key, $value);
|
|
if ($result === null) {
|
|
$this->event = [];
|
|
} elseif (!$result) {
|
|
unset($this->event[$key]);
|
|
}
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Parse an active calls JSON string and return a list of event messages.
|
|
*
|
|
* This method expects a JSON string where each row represents an active call, and returns
|
|
* a list of event_message objects populated with the relevant details for each call.
|
|
*
|
|
* @param string $json_string The JSON string to parse.
|
|
*
|
|
* @return array A list of event_message objects.
|
|
*/
|
|
public static function parse_active_calls($json_string): array {
|
|
$calls = [];
|
|
$json_array = json_decode($json_string, true);
|
|
if (empty($json_array["rows"])) {
|
|
return $calls;
|
|
}
|
|
foreach ($json_array["rows"] as $call) {
|
|
$message = new event_message($call);
|
|
// adjust basic info to match an event setting the callstate to ringing
|
|
// so that a row can be created for it
|
|
$message['event_name'] = 'CHANNEL_CALLSTATE';
|
|
$message['answer_state'] = 'ringing';
|
|
$message['channel_call_state'] = 'ACTIVE';
|
|
$message['unique_id'] = $call['uuid'];
|
|
$message['call_direction'] = $call['direction'];
|
|
|
|
//set the codecs
|
|
$message['caller_channel_created_time'] = intval($call['created_epoch']) * 1000000;
|
|
$message['channel_read_codec_name'] = $call['read_codec'];
|
|
$message['channel_read_codec_rate'] = $call['read_rate'];
|
|
$message['channel_write_codec_name'] = $call['write_codec'];
|
|
$message['channel_write_codec_rate'] = $call['write_rate'];
|
|
|
|
//get the profile name
|
|
$message['caller_channel_name'] = $call['name'];
|
|
|
|
//domain or context
|
|
$message['caller_context'] = $call['context'];
|
|
$message['caller_caller_id_name'] = $call['initial_cid_name'];
|
|
$message['caller_caller_id_number'] = $call['initial_cid_num'];
|
|
$message['caller_destination_number'] = $call['initial_dest'];
|
|
$message['application'] = $call['application'] ?? '';
|
|
$message['secure'] = $call['secure'] ?? '';
|
|
$calls[] = $message;
|
|
}
|
|
return $calls;
|
|
}
|
|
|
|
/**
|
|
* Creates a websocket_message_event object from a json string
|
|
* @param type $json_string
|
|
* @return self|null
|
|
*/
|
|
public static function create_from_json($json_string) {
|
|
if (is_array($json_string)) {
|
|
print_r(debug_backtrace());
|
|
die();
|
|
}
|
|
$array = json_decode($json_string, true);
|
|
if ($array !== false) {
|
|
return new static($array);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Creates a new instance from a switch event.
|
|
*
|
|
* @param array|string $raw_event The raw event data.
|
|
* @param filter|null $filter Optional filter to be applied on the created object.
|
|
* @param int $flags Flags controlling the creation process (see EVENT_SWAP_API and EVENT_USE_SUBCLASS).
|
|
*
|
|
* @return self
|
|
*/
|
|
public static function create_from_switch_event($raw_event, filter $filter = null, $flags = 3): self {
|
|
|
|
// Set the options from the flags passed
|
|
$swap_api_name_with_event_name = ($flags & self::EVENT_SWAP_API) !== 0;
|
|
$swap_subclass_event_name_with_event_name = ($flags & self::EVENT_USE_SUBCLASS) !== 0;
|
|
|
|
// Get the payload and ignore the headers
|
|
if (is_array($raw_event) && isset($raw_event['$'])) {
|
|
$raw_event = $raw_event['$'];
|
|
}
|
|
|
|
//check if it is still an array
|
|
if (is_array($raw_event)) {
|
|
$raw_event = array_pop($raw_event);
|
|
}
|
|
|
|
$event_array = [];
|
|
foreach (explode("\n", $raw_event) as $line) {
|
|
$parts = explode(':', $line, 2);
|
|
$key = '';
|
|
$value = '';
|
|
if (count($parts) > 0) {
|
|
$key = $parts[0];
|
|
}
|
|
if (count($parts) > 1) {
|
|
$value = urldecode(trim($parts[1]));
|
|
}
|
|
if (!empty($key)) {
|
|
$event_array[$key] = $value;
|
|
}
|
|
}
|
|
|
|
//check for body
|
|
if (!empty($event_array['Content-Length'])) {
|
|
$event_array['_body'] = substr($raw_event, -1*$event_array['Content-Length']);
|
|
}
|
|
|
|
// Instead of using 'CUSTOM' for the Event-Name we use the actual API-Command when it is available instead
|
|
if ($swap_api_name_with_event_name && !empty($event_array['API-Command'])) {
|
|
// swap the values
|
|
[$event_array['Event-Name'], $event_array['API-Command']] = [$event_array['API-Command'], $event_array['Event-Name']];
|
|
}
|
|
|
|
// Promote the Event-Subclass name to the Event-Name
|
|
if ($swap_subclass_event_name_with_event_name && !empty($event_array['Event-Subclass'])) {
|
|
// swap the values
|
|
[$event_array['Event-Name'], $event_array['Event-Subclass']] = [$event_array['Event-Subclass'], $event_array['Event-Name']];
|
|
}
|
|
|
|
// Return the new object
|
|
return new static($event_array, $filter);
|
|
}
|
|
|
|
/**
|
|
* Return a Json representation for this object when the object is echoed or printed
|
|
* @return string
|
|
* @override websocket_message
|
|
*/
|
|
public function __toString(): string {
|
|
return json_encode($this->to_array());
|
|
}
|
|
|
|
/**
|
|
* Set or Get the body
|
|
* @param null|string $body
|
|
* @return self|string
|
|
*/
|
|
public function body(?string $body = null) {
|
|
|
|
// Check if we are setting the value for body
|
|
if (func_num_args() > 0) {
|
|
|
|
// Set the value
|
|
$this->body = $body;
|
|
|
|
// Return the object for chaining
|
|
return $this;
|
|
}
|
|
|
|
// A request was made to get the value from body
|
|
return $this->body;
|
|
}
|
|
|
|
/**
|
|
* Convert the event object to an array representation.
|
|
*
|
|
* This method iterates over the event properties and includes them in the returned array.
|
|
* If the body is not null, it will be included as a separate key-value pair in the resulting array.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function event_to_array(): array {
|
|
$array = [];
|
|
foreach ($this->event as $key => $value) {
|
|
$array[$key] = $value;
|
|
}
|
|
if ($this->body !== null) {
|
|
$array[self::BODY_ARRAY_KEY] = $this->body;
|
|
}
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Return an iterator for this object.
|
|
*
|
|
* This method allows iteration over the event data as a Traversable object.
|
|
*
|
|
* @return \Traversable
|
|
*/
|
|
public function getIterator(): \Traversable {
|
|
yield from $this->event_to_array();
|
|
}
|
|
|
|
/**
|
|
* Check if a specific event key exists in this object.
|
|
*
|
|
* @param mixed $offset The key of the event to check for existence
|
|
*
|
|
* @return bool True if the event key exists, false otherwise
|
|
*/
|
|
public function offsetExists(mixed $offset): bool {
|
|
self::sanitize_event_key($offset);
|
|
return isset($this->event[$offset]);
|
|
}
|
|
|
|
/**
|
|
* Return the value associated with the given key from this event object.
|
|
*
|
|
* If the key is 'body', returns the event body. Otherwise, returns the value
|
|
* stored in the 'event' array for the given key.
|
|
*
|
|
* @param mixed $offset The key to retrieve the value for.
|
|
*
|
|
* @return mixed The value associated with the given key.
|
|
*/
|
|
public function offsetGet(mixed $offset): mixed {
|
|
self::sanitize_event_key($offset);
|
|
if ($offset === self::BODY_ARRAY_KEY) {
|
|
return $this->body;
|
|
}
|
|
return $this->event[$offset];
|
|
}
|
|
|
|
/**
|
|
* Set the value for a given offset in this object.
|
|
*
|
|
* @param mixed $offset The key or index to set the value for. If it is
|
|
* {@link self::BODY_ARRAY_KEY}, this method will replace the
|
|
* entire body of the event with the provided value.
|
|
* @param mixed $value The new value to be associated with the offset.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function offsetSet(mixed $offset, mixed $value): void {
|
|
self::sanitize_event_key($offset);
|
|
if ($offset === self::BODY_ARRAY_KEY) {
|
|
$this->body = $value;
|
|
} else {
|
|
$this->event[$offset] = $value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unsets a property from the event array.
|
|
*
|
|
* This method first sanitizes the provided offset using the sanitize_event_key method to prevent potential security vulnerabilities.
|
|
* If the sanitized offset is equal to the BODY_ARRAY_KEY, it sets the body property of this object to null.
|
|
* Otherwise, it removes the specified key from the event array.
|
|
*
|
|
* @param mixed $offset The index or key to be unset from the event array.
|
|
*/
|
|
public function offsetUnset(mixed $offset): void {
|
|
self::sanitize_event_key($offset);
|
|
if ($offset === self::BODY_ARRAY_KEY) {
|
|
$this->body = null;
|
|
} else {
|
|
unset($this->event[$offset]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sanitizes key by replacing '-' with '_', converts to lowercase, and only allows digits 0-9 and letters a-z
|
|
* @param string $key
|
|
* @return string
|
|
*/
|
|
public static function sanitize_event_key(string &$key) /* : never */ {
|
|
$key = preg_replace('/[^a-z0-9_]/', '', str_replace('-', '_', strtolower($key)));
|
|
//rewrite 'name' to 'event_name'
|
|
if ($key === 'name') $key = 'event_name';
|
|
}
|
|
}
|