This commit is contained in:
frytimo 2025-04-04 11:24:51 -06:00 committed by GitHub
commit 778596baf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1314 additions and 393 deletions

30
app/phrases/phrase.php Normal file
View File

@ -0,0 +1,30 @@
<?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-2024
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Mark J Crane <markjcrane@fusionpbx.com>
* Tim Fry <tim@fusionpbx.com>
*/
echo "<!doctype html>";
echo "<html><head></head><body><script src='resources/javascript/phrase_fetch.js'></script></body></html>";

View File

@ -25,244 +25,286 @@
*/
//includes files
require_once dirname(__DIR__, 2) . "/resources/require.php";
require_once "resources/check_auth.php";
require_once dirname(__DIR__, 2) . "/resources/require.php";
require_once "resources/check_auth.php";
//check permissions
if (permission_exists('phrase_add') || permission_exists('phrase_edit')) {
//access granted
}
else {
echo "access denied";
exit;
if (permission_exists('phrase_add') || permission_exists('phrase_edit')) {
//access granted
}
else {
echo "access denied";
exit;
}
function build_data_array_from_post(settings $settings) {
global $domain_uuid, $drop_rows;
$phrase_uuid = $_POST['phrase_uuid'];
$array = [];
$drop_rows = [];
$drop_row_count = 0;
//load sound files from the switch so we can validate selections
$sound_files = (new file)->sounds();
//update the phrase information
$array['phrases'][0]['domain_uuid'] = $domain_uuid;
$array['phrases'][0]['phrase_uuid'] = $phrase_uuid;
$array['phrases'][0]['phrase_name'] = $_POST['phrase_name'];
$array['phrases'][0]['phrase_language'] = $_POST['phrase_language'];
$array['phrases'][0]['phrase_enabled'] = $_POST['phrase_enabled'];
$array['phrases'][0]['phrase_description'] = $_POST['phrase_description'];
//recording_files are:
// 'recording_uuid' => 'recording.wav'
// OR
// 'recording_uuid' => '${lua streamfile.lua ' . base64_data .'}'
$recording_files = phrases::get_all_domain_recordings($settings);
//
// Create two arrays - one for rows to delete and one for new/updated rows
//
for ($i = 0; $i < count($_POST['phrase_detail_function']); $i++) {
//check for function to perform
$phrase_detail_function = $_POST['phrase_detail_function'][$i];
$phrase_detail_data = null;
$recording_uuid_or_file = '';
$phrase_detail_uuid = '';
//check for the empty rows to delete -- 0,false,null is valid
if (strlen($_POST['phrase_detail_data'][$i]) === 0
&& !empty($_POST['phrase_detail_uuid'][$i])
&& empty($_POST['slider'][$i])
&& empty($_POST['phrase_detail_text'][$i])) {
$drop_rows['phrase_details'][$drop_row_count++]['phrase_detail_uuid'] = $_POST['phrase_detail_uuid'][$i];
continue;
}
switch ($phrase_detail_function) {
case 'play-file':
//only save rows with data
if (!empty($_POST['phrase_detail_data'][$i])) {
$recording_uuid_or_file = $_POST['phrase_detail_data'][$i];
//check for valid recordings and files
if (is_uuid($recording_uuid_or_file)) {
//recording UUID
$phrase_detail_data = $recording_files[$recording_uuid_or_file];
} else {
//not a recording so must be valid path inside the switch recording files
if (in_array($recording_uuid_or_file, $sound_files)) {
//valid switch audio file
$phrase_detail_data = $recording_uuid_or_file;
} else {
//ignore an invalid audio file
continue(2);
}
}
//build data array
if ($_POST['phrase_detail_function'][$i] == 'execute' && substr($_POST['phrase_detail_data'][$i], 0,5) != "sleep" && !permission_exists("phrase_execute")) {
header("Location: phrase_edit.php?id=".$phrase_uuid);
exit;
}
}
break;
case 'pause':
//check for value
$phrase_detail_data = $_POST['slider'][$i];
break;
case 'execute':
//check for the empty rows to delete
if (empty($_POST['phrase_detail_text'][$i]) && !empty($_POST['phrase_detail_uuid'][$i])) {
$drop_rows['phrase_details'][$drop_row_count++]['phrase_detail_uuid'] = $_POST['phrase_detail_uuid'][$i];
continue(2);
}
$phrase_detail_data = $_POST['phrase_detail_text'][$i];
break;
}
$_POST['phrase_detail_tag'] = 'action'; // default, for now
$_POST['phrase_detail_group'] = "0"; // one group, for now
if ($phrase_detail_data !== null) {
if (!empty($_POST['phrase_detail_uuid'][$i])) {
//update existing records in the database
$phrase_detail_uuid = $_POST['phrase_detail_uuid'][$i];
} else {
//new record
$phrase_detail_uuid = uuid();
}
$array['phrase_details'][$i]['phrase_detail_uuid'] = $phrase_detail_uuid;
$array['phrase_details'][$i]['phrase_uuid'] = $phrase_uuid;
$array['phrase_details'][$i]['domain_uuid'] = $domain_uuid;
$array['phrase_details'][$i]['phrase_detail_order'] = $i;
$array['phrase_details'][$i]['phrase_detail_tag'] = $_POST['phrase_detail_tag'];
$array['phrase_details'][$i]['phrase_detail_pattern'] = $_POST['phrase_detail_pattern'] ?? null;
$array['phrase_details'][$i]['phrase_detail_function'] = $phrase_detail_function;
$array['phrase_details'][$i]['phrase_detail_data'] = $phrase_detail_data; //path and filename of recording
$array['phrase_details'][$i]['phrase_detail_method'] = $_POST['phrase_detail_method'] ?? null;
$array['phrase_details'][$i]['phrase_detail_type'] = $_POST['phrase_detail_type'] ?? null;
$array['phrase_details'][$i]['phrase_detail_group'] = $_POST['phrase_detail_group'];
}
}
return $array;
}
//set default domain
if (empty($domain_uuid)) {
$domain_uuid = $_SESSION['domain_uuid'] ?? '';
}
//set default user
if (empty($user_uuid)) {
$user_uuid = $_SESSION['user_uuid'] ?? '';
}
//add multi-lingual support
$language = new text;
$text = $language->get();
$language = new text;
$text = $language->get();
//ensure we have a database object
$database = database::new();
//ensure we have a settings object
$settings = new settings(['database' => $database, 'domain_uuid' => $domain_uuid, 'user_uuid' => $user_uuid]);
//add the defaults
$phrase_name = '';
$phrase_language = '';
$phrase_description = '';
$phrase_name = '';
$phrase_language = '';
$phrase_description = '';
//set the action as an add or an update
if (!empty($_REQUEST["id"])) {
$action = "update";
$phrase_uuid = $_REQUEST["id"];
}
else {
$action = "add";
}
if (!empty($_REQUEST["id"])) {
$action = "update";
$phrase_uuid = $_REQUEST["id"];
}
else {
$action = "add";
}
//get the form value and set to php variables
if (count($_POST) > 0) {
if (count($_POST) > 0) {
//process the http post data by submitted action
if (!empty($_POST['action']) && is_uuid($_POST['phrase_uuid'])) {
$array[0]['checked'] = 'true';
$array[0]['uuid'] = $_POST['phrase_uuid'];
//process the http post data by submitted action
if (!empty($_POST['action']) && is_uuid($_POST['phrase_uuid'])) {
$array[0]['checked'] = 'true';
$array[0]['uuid'] = $_POST['phrase_uuid'];
switch ($_POST['action']) {
case 'delete':
if (permission_exists('phrase_delete')) {
$obj = new phrases;
$obj->delete($array);
}
break;
}
header('Location: phrases.php');
exit;
switch ($_POST['action']) {
case 'delete':
if (permission_exists('phrase_delete')) {
$obj = new phrases;
$obj->delete($array);
}
break;
}
if (permission_exists('phrase_domain')) {
$domain_uuid = $_POST["domain_uuid"];
header('Location: phrases.php');
exit;
}
$phrase_name = $_POST["phrase_name"];
$phrase_language = $_POST["phrase_language"];
$phrase_enabled = $_POST["phrase_enabled"] ?? 'false';
$phrase_description = $_POST["phrase_description"];
$phrase_details_delete = $_POST["phrase_details_delete"] ?? '';
//clean the name
$phrase_name = str_replace(" ", "_", $phrase_name);
$phrase_name = str_replace("'", "", $phrase_name);
if (permission_exists('phrase_domain')) {
$domain_uuid = $_POST["domain_uuid"];
}
$phrase_name = $_POST["phrase_name"];
$phrase_language = $_POST["phrase_language"];
$phrase_enabled = $_POST["phrase_enabled"] ?? 'false';
$phrase_description = $_POST["phrase_description"];
$phrase_details_delete = $_POST["phrase_details_delete"] ?? '';
//clean the name
$phrase_name = str_replace(" ", "_", $phrase_name);
$phrase_name = str_replace("'", "", $phrase_name);
}
//process the changes from the http post
if (count($_POST) > 0 && empty($_POST["persistformvar"])) {
//get the uuid
if ($action == "update") {
$phrase_uuid = $_POST["phrase_uuid"];
}
if ($action == "update") {
$phrase_uuid = $_POST["phrase_uuid"];
}
//validate the token
$token = new token;
if (!$token->validate($_SERVER['PHP_SELF'])) {
message::add($text['message-invalid_token'],'negative');
header('Location: phrases.php');
exit;
}
$token = new token;
if (!$token->validate($_SERVER['PHP_SELF'])) {
message::add($text['message-invalid_token'],'negative');
header('Location: phrases.php');
exit;
}
//check for all required data
$msg = '';
if (empty($phrase_name)) { $msg .= $text['message-required']." ".$text['label-name']."<br>\n"; }
if (empty($phrase_language)) { $msg .= $text['message-required']." ".$text['label-language']."<br>\n"; }
if (!empty($msg) && empty($_POST["persistformvar"])) {
require_once "resources/header.php";
require_once "resources/persist_form_var.php";
echo "<div align='center'>\n";
echo "<table><tr><td>\n";
echo $msg."<br />";
echo "</td></tr></table>\n";
persistformvar($_POST);
echo "</div>\n";
require_once "resources/footer.php";
return;
}
$msg = '';
if (empty($phrase_name)) { $msg .= $text['message-required']." ".$text['label-name']."<br>\n"; }
if (empty($phrase_language)) { $msg .= $text['message-required']." ".$text['label-language']."<br>\n"; }
if (!empty($msg) && empty($_POST["persistformvar"])) {
require_once "resources/header.php";
require_once "resources/persist_form_var.php";
echo "<div align='center'>\n";
echo "<table><tr><td>\n";
echo $msg."<br />";
echo "</td></tr></table>\n";
persistformvar($_POST);
echo "</div>\n";
require_once "resources/footer.php";
return;
}
//add the phrase
if (empty($_POST["persistformvar"]) || $_POST["persistformvar"] != "true") {
if ($action == "add" && permission_exists('phrase_add')) {
//build data array
$phrase_uuid = uuid();
$array['phrases'][0]['domain_uuid'] = $domain_uuid;
$array['phrases'][0]['phrase_uuid'] = $phrase_uuid;
$array['phrases'][0]['phrase_name'] = $phrase_name;
$array['phrases'][0]['phrase_language'] = $phrase_language;
$array['phrases'][0]['phrase_enabled'] = $phrase_enabled;
$array['phrases'][0]['phrase_description'] = $phrase_description;
if ($_POST['phrase_detail_function'] != '') {
if ($_POST['phrase_detail_function'] == 'execute' && substr($_POST['phrase_detail_data'], 0,5) != "sleep" && !permission_exists("phrase_execute")) {
header("Location: phrase_edit.php");
exit;
}
$_POST['phrase_detail_tag'] = 'action'; // default, for now
$_POST['phrase_detail_group'] = "0"; // one group, for now
if ($_POST['phrase_detail_data'] != '') {
$phrase_detail_uuid = uuid();
$array['phrase_details'][0]['phrase_detail_uuid'] = $phrase_detail_uuid;
$array['phrase_details'][0]['phrase_uuid'] = $phrase_uuid;
$array['phrase_details'][0]['domain_uuid'] = $domain_uuid;
$array['phrase_details'][0]['phrase_detail_order'] = $_POST['phrase_detail_order'];
$array['phrase_details'][0]['phrase_detail_tag'] = $_POST['phrase_detail_tag'];
$array['phrase_details'][0]['phrase_detail_pattern'] = $_POST['phrase_detail_pattern'] ?? null;
$array['phrase_details'][0]['phrase_detail_function'] = $_POST['phrase_detail_function'];
$array['phrase_details'][0]['phrase_detail_data'] = $_POST['phrase_detail_data'];
$array['phrase_details'][0]['phrase_detail_method'] = $_POST['phrase_detail_method'] ?? null;
$array['phrase_details'][0]['phrase_detail_type'] = $_POST['phrase_detail_type'] ?? null;
$array['phrase_details'][0]['phrase_detail_group'] = $_POST['phrase_detail_group'];
}
if (empty($_POST["persistformvar"]) || $_POST["persistformvar"] != "true") {
$message = '';
switch ($action) {
case 'add':
//redirect when they don't have permission to add a phrase
if (!permission_exists('phrase_add')) {
header('Location: phrases.php');
exit();
}
//set user feedback message to add
$message = $text['message-add'];
$phrase_uuid = uuid();
//do not break
case 'update':
//redirect when not adding and don't have permission to edit a phrase
if (empty($message)) {
if (!permission_exists('phrase_edit')) {
header('Location: phrases.php');
exit();
}
//execute insert
$p = permissions::new();
$p->add('phrase_detail_add', 'temp');
$database = new database;
$database->app_name = 'phrases';
$database->app_uuid = '5c6f597c-9b78-11e4-89d3-123b93f75cba';
$database->save($array);
unset($array);
$p->delete('phrase_detail_add', 'temp');
//save the xml to the file system if the phrase directory is set
//save_phrases_xml();
//clear the cache
$cache = new cache;
$cache->delete("languages:".$phrase_language.".".$phrase_uuid);
//clear the destinations session array
if (isset($_SESSION['destinations']['array'])) {
unset($_SESSION['destinations']['array']);
}
//send a redirect
message::add($text['message-add']);
header("Location: phrase_edit.php?id=".$phrase_uuid);
exit;
}
//update the phrase
if ($action == "update" && permission_exists('phrase_edit')) {
//build data array
$array['phrases'][0]['domain_uuid'] = $domain_uuid;
$array['phrases'][0]['phrase_uuid'] = $phrase_uuid;
$array['phrases'][0]['phrase_name'] = $phrase_name;
$array['phrases'][0]['phrase_language'] = $phrase_language;
$array['phrases'][0]['phrase_enabled'] = $phrase_enabled;
$array['phrases'][0]['phrase_description'] = $phrase_description;
if ($_POST['phrase_detail_function'] != '') {
if ($_POST['phrase_detail_function'] == 'execute' && substr($_POST['phrase_detail_data'], 0,5) != "sleep" && !permission_exists("phrase_execute")) {
header("Location: phrase_edit.php?id=".$phrase_uuid);
exit;
}
$_POST['phrase_detail_tag'] = 'action'; // default, for now
$_POST['phrase_detail_group'] = "0"; // one group, for now
if ($_POST['phrase_detail_data'] != '') {
$phrase_detail_uuid = uuid();
$array['phrase_details'][0]['phrase_detail_uuid'] = $phrase_detail_uuid;
$array['phrase_details'][0]['phrase_uuid'] = $phrase_uuid;
$array['phrase_details'][0]['domain_uuid'] = $domain_uuid;
$array['phrase_details'][0]['phrase_detail_order'] = $_POST['phrase_detail_order'];
$array['phrase_details'][0]['phrase_detail_tag'] = $_POST['phrase_detail_tag'];
$array['phrase_details'][0]['phrase_detail_pattern'] = $_POST['phrase_detail_pattern'] ?? null;
$array['phrase_details'][0]['phrase_detail_function'] = $_POST['phrase_detail_function'];
$array['phrase_details'][0]['phrase_detail_data'] = $_POST['phrase_detail_data'];
$array['phrase_details'][0]['phrase_detail_method'] = $_POST['phrase_detail_method'] ?? null;
$array['phrase_details'][0]['phrase_detail_type'] = $_POST['phrase_detail_type'] ?? null;
$array['phrase_details'][0]['phrase_detail_group'] = $_POST['phrase_detail_group'];
}
}
//set user feedback message to update
$message = $text['message-update'];
}
if (!empty($_POST['phrase_detail_function'])) {
$array = build_data_array_from_post($settings);
}
//execute update/insert
$p = permissions::new();
$p->add('phrase_detail_add', 'temp');
$database = new database;
$database->app_name = 'phrases';
$database->app_uuid = '5c6f597c-9b78-11e4-89d3-123b93f75cba';
$p = permissions::new();
$p->add('phrase_detail_add', 'temp');
$p->add('phrase_detail_edit', 'temp');
$p->add('phrase_detail_delete', 'temp');
$database->app_name = 'phrases';
$database->app_uuid = '5c6f597c-9b78-11e4-89d3-123b93f75cba';
if (count($array) > 0) {
$database->save($array);
unset($array);
$p->delete('phrase_detail_add', 'temp');
//remove checked phrase details
if (
is_array($phrase_details_delete)
&& @sizeof($phrase_details_delete) != 0
) {
$obj = new phrases;
$obj->phrase_uuid = $phrase_uuid;
$obj->delete_details($phrase_details_delete);
}
}
if (count($drop_rows) > 0) {
$database->delete($drop_rows);
unset($drop_rows);
}
$p->delete('phrase_detail_add', 'temp');
//clear the cache
$cache = new cache;
$cache->delete("languages:".$phrase_language.".".$phrase_uuid);
$cache = new cache;
$cache->delete("languages:".$phrase_language.".".$phrase_uuid);
//clear the destinations session array
if (isset($_SESSION['destinations']['array'])) {
unset($_SESSION['destinations']['array']);
}
if (isset($_SESSION['destinations']['array'])) {
unset($_SESSION['destinations']['array']);
}
//send a redirect
message::add($text['message-update']);
header("Location: phrase_edit.php?id=".$phrase_uuid);
exit;;
}
message::add($message);
header("Location: phrase_edit.php?id=".$phrase_uuid);
exit;
}
}
}
//pre-populate the form
@ -276,7 +318,6 @@
$sql .= "and phrase_uuid = :phrase_uuid ";
$parameters['domain_uuid'] = $domain_uuid;
$parameters['phrase_uuid'] = $phrase_uuid;
$database = new database;
$row = $database->select($sql, $parameters, 'row');
if (is_array($row) && @sizeof($row) != 0) {
$phrase_name = $row["phrase_name"];
@ -298,20 +339,22 @@
$sql .= "order by phrase_detail_order asc ";
$parameters['domain_uuid'] = $_SESSION['domain_uuid'];
$parameters['phrase_uuid'] = $phrase_uuid;
$database = new database;
$phrase_details = $database->select($sql, $parameters, 'all');
unset($sql, $parameters);
}
//get the recording names from the database.
$sql = "select recording_name, recording_filename from v_recordings ";
$sql = "select recording_uuid, recording_name, recording_filename, domain_uuid from v_recordings ";
$sql .= "where domain_uuid = :domain_uuid ";
$sql .= "order by recording_name asc ";
$parameters['domain_uuid'] = $_SESSION['domain_uuid'];
$database = new database;
$recordings = $database->select($sql, $parameters, 'all');
unset($sql, $parameters);
//get the switch sound files
$file = new file;
$sound_files = $file->sounds();
//create token
$object = new token;
$token = $object->create($_SERVER['PHP_SELF']);
@ -321,126 +364,76 @@
if ($action == 'update') { $document['title'] = $text['title-edit_phrase']; }
require_once "resources/header.php";
//js to control action form input
echo "<script type='text/javascript'>\n";
echo "function load_action_options(selected_index) {\n";
echo " var obj_action = document.getElementById('phrase_detail_data');\n";
echo " if (selected_index == 0 || selected_index == 1) {\n";
echo " if (obj_action.type == 'text') {\n";
echo " action_to_select();\n";
echo " var obj_action = document.getElementById('phrase_detail_data');\n";
echo " obj_action.setAttribute('style', 'width: 300px; min-width: 300px; max-width: 300px;');\n";
echo " }\n";
echo " else {\n";
echo " clear_action_options();\n";
echo " }\n";
echo " }\n";
if (if_group("superadmin")) {
echo " else {\n";
echo " document.getElementById('phrase_detail_data_switch').style.display='none';\n";
echo " obj_action.setAttribute('style', 'width: 300px; min-width: 300px; max-width: 300px;');\n";
echo " }\n";
//javascript constants for use in the selection option group
echo "<script>\n";
echo "window.permission_execute = " . (permission_exists('phrase_execute') ? 'true' : 'false') . ";\n";
echo "window.phrase_label_sounds = '" . ($text['label-sounds'] ?? 'Sounds') . "';\n";
echo "window.phrase_label_recordings = '" . ($text['label-recordings'] ?? 'Recordings') . "';\n";
//only include permissive actions
$phrase_commands = [];
if (permission_exists('phrase_play')) {
$phrase_commands[] = $text['label-play'] ?? 'Play';
$phrase_commands[] = $text['label-pause'] ?? 'Pause';
}
echo " if (selected_index == 0) {\n"; //play
echo " obj_action.options[obj_action.options.length] = new Option('', '');\n"; //blank option
//recordings
$tmp_selected = false;
if (is_array($recordings) && @sizeof($recordings) != 0) {
echo "var opt_group = document.createElement('optgroup');\n";
echo "opt_group.label = \"".$text['label-recordings']."\";\n";
foreach ($recordings as $row) {
if (!empty($_SESSION['recordings']['storage_type']['text']) && $_SESSION['recordings']['storage_type']['text'] == 'base64') {
echo "opt_group.appendChild(new Option(\"".$row["recording_name"]."\", \"\${lua streamfile.lua ".$row["recording_filename"]."}\"));\n";
}
else {
echo "opt_group.appendChild(new Option(\"".$row["recording_name"]."\", \"".$_SESSION['switch']['recordings']['dir'].'/'.$_SESSION['domain_name'].'/'.$row["recording_filename"]."\"));\n";
if (permission_exists('phrase_execute')) {
$phrase_commands[] = $text['label-execute'] ?? 'Execute';
}
echo "window.phrase_commands = " . json_encode($phrase_commands, true) . ";\n";
//existing details
if (!empty($phrase_details)) {
//update the array to include the recording name for display in select box
foreach ($phrase_details as &$row) {
$row['display_name'] = '';
$file = basename($row['phrase_detail_data']);
//get the display_name from recording name based on the file matched
foreach ($recordings as $key => $recordings_row) {
//match on filename first and then domain_uuid
if ($recordings_row['recording_filename'] === $file && $recordings_row['domain_uuid'] === $row['domain_uuid']) {
$row['display_name'] = $recordings[$key]['recording_name'];
break;
}
}
echo "obj_action.appendChild(opt_group);\n";
}
unset($recordings, $row);
//sounds
$file = new file;
$sound_files = $file->sounds();
if (is_array($sound_files) && @sizeof($sound_files) != 0) {
echo "var opt_group = document.createElement('optgroup');\n";
echo "opt_group.label = \"".$text['label-sounds']."\";\n";
foreach ($sound_files as $value) {
if (!empty($value)) {
echo "opt_group.appendChild(new Option(\"".$value."\", \"".$value."\"));\n";
//check if display_name was not found in the recording names
if (strlen($row['display_name']) === 0) {
//try finding display_name in the switch sound files
if (!empty($sound_files)) {
//use optimized php function with strict comparison
$i = array_search($row['phrase_detail_data'], $sound_files, true);
//if found in the switch sound files
if ($i !== false) {
//set the display_name to the switch sound file name
$row['display_name'] = $sound_files[$i];
}
}
}
echo "obj_action.appendChild(opt_group);\n";
}
unset($sound_files, $row);
echo " }\n";
echo " else if (selected_index == 1) {\n"; //pause
echo " obj_action.options[obj_action.options.length] = new Option('', '');\n"; //blank option
for ($s = 0.1; $s <= 5; $s = $s + 0.1) {
echo " obj_action.options[obj_action.options.length] = new Option('".number_format($s, 1)."s', 'sleep(".($s * 1000).")');\n";
//send the phrase details to the browser as a global scope json array object
//echo "window.phrase_details = " . json_encode($phrase_details, true) . ";\n";
} else {
//send an empty array to the browser as a global scope json array object
//echo "window.phrase_details = [];\n";
}
echo " }\n";
if (if_group("superadmin")) {
echo " else if (selected_index == 2) {\n"; //execute
echo " action_to_input();\n";
echo " }\n";
//recording files
if ($recordings !== false) {
//send recordings to the browser as a global scope json array object
echo "window.phrase_recordings = " . json_encode($recordings, true) . ";\n";
} else {
//send an empty array
echo "window.phrase_recordings = [];\n";
}
echo "}\n";
echo "function clear_action_options() {\n";
echo " var len, groups, par;\n";
echo " sel = document.getElementById('phrase_detail_data');\n";
echo " groups = sel.getElementsByTagName('optgroup');\n";
echo " len = groups.length;\n";
echo " for (var i=len; i; i--) {\n";
echo " sel.removeChild( groups[i-1] );\n";
echo " }\n";
echo " len = sel.options.length;\n";
echo " for (var i=len; i; i--) {\n";
echo " par = sel.options[i-1].parentNode;\n";
echo " par.removeChild( sel.options[i-1] );\n";
echo " }\n";
echo "}\n";
if (if_group("superadmin")) {
echo "function action_to_input() {\n";
echo " obj = document.getElementById('phrase_detail_data');\n";
echo " tb = document.createElement('INPUT');\n";
echo " tb.type = 'text';\n";
echo " tb.name = obj.name;\n";
echo " tb.id = obj.id;\n";
echo " tb.value = obj.options[obj.selectedIndex].value;\n";
echo " tb.className = 'formfld';\n";
echo " tb_width = (document.getElementById('phrase_detail_function').selectedIndex == 2) ? '300px' : '267px';\n";
echo " tb.setAttribute('style', 'width: '+tb_width+'; min-width: '+tb_width+'; max-width: '+tb_width+';');\n";
echo " obj.parentNode.insertBefore(tb, obj);\n";
echo " obj.parentNode.removeChild(obj);\n";
echo " if (document.getElementById('phrase_detail_function').selectedIndex != 2) {\n";
echo " tb.setAttribute('style', 'width: 263px; min-width: 263px; max-width: 263px;');\n";
echo " document.getElementById('phrase_detail_data_switch').style.display='';\n";
echo " }\n";
echo " else {\n";
echo " tb.focus();\n";
echo " }\n";
echo "}\n";
echo "function action_to_select() {\n";
echo " obj = document.getElementById('phrase_detail_data');\n";
echo " sb = document.createElement('SELECT');\n";
echo " sb.name = obj.name;\n";
echo " sb.id = obj.id;\n";
echo " sb.className = 'formfld';\n";
echo " sb.setAttribute('style', 'width: 300px; min-width: 300px; max-width: 300px;');\n";
echo " sb.setAttribute('onchange', 'action_to_input();');\n";
echo " obj.parentNode.insertBefore(sb, obj);\n";
echo " obj.parentNode.removeChild(obj);\n";
echo " document.getElementById('phrase_detail_data_switch').style.display='none';\n";
echo " clear_action_options();\n";
echo "}\n";
if (!empty($sound_files)) {
//send sounds to the browser as a global scope json array object
echo "window.phrase_sounds = " . json_encode($sound_files, true) . ";\n";
}
echo "</script>\n";
//javascript to control action form input using drag and drop
echo "<script src='resources/javascript/phrase_edit.js'></script>\n";
//show the content
echo "<form method='post' name='frm' id='frm'>\n";
@ -458,7 +451,7 @@
if ($action == "update" && permission_exists('phrase_delete')) {
echo button::create(['type'=>'button','label'=>$text['button-delete'],'icon'=>$_SESSION['theme']['button_icon_delete'],'name'=>'btn_delete','style'=>'margin-left: 15px;','onclick'=>"modal_open('modal-delete','btn_delete');"]);
}
echo button::create(['type'=>'submit','label'=>$text['button-save'],'icon'=>$_SESSION['theme']['button_icon_save'],'id'=>'btn_save','style'=>'margin-left: 15px;']);
echo button::create(['type'=>'submit','onclick'=>'submit_phrase()','label'=>$text['button-save'],'icon'=>$_SESSION['theme']['button_icon_save'],'id'=>'btn_save','style'=>'margin-left: 15px;']);
echo " </div>\n";
echo " <div style='clear: both;'></div>\n";
echo "</div>\n";
@ -491,88 +484,62 @@
echo " ".$text['description-language']."\n";
echo "</td>\n";
echo "</tr>\n";
//structure row
echo "<tr>";
echo "<td class='vncell' valign='top'>".$text['label-structure']."</td>";
echo "<td class='vtable' align='left'>";
echo " <table border='0' cellpadding='0' cellspacing='0'>\n";
//style for dragging rows
echo " <link rel=stylesheet href='resources/styles/phrase_edit.css' />";
//structure table
echo " <table border='0' cellpadding='0' cellspacing='0' id='phrases_table'>\n";
//headings
echo " <thead>\n";
echo " <tr>\n";
echo " <td class='vtable'><strong>".$text['label-function']."</strong></td>\n";
echo " <td class='vtable'><strong>" . ($text['label-order'] ?? 'Order') . "</strong></td>\n";
echo " <td class='vtable'><strong>".$text['label-action']."</strong></td>\n";
echo " <td class='vtable' style='text-align: center;'><strong>".$text['label-order']."</strong></td>\n";
if (!empty($phrase_details)) {
echo " <td class='vtable edit_delete_checkbox_all' onmouseover=\"swap_display('delete_label_details', 'delete_toggle_details');\" onmouseout=\"swap_display('delete_label_details', 'delete_toggle_details');\">\n";
echo " <span id='delete_label_details'>".$text['label-delete']."</span>\n";
echo " <span id='delete_toggle_details'><input type='checkbox' id='checkbox_all_details' name='checkbox_all' onclick=\"edit_all_toggle('details');\"></span>\n";
echo " </td>\n";
}
echo " <td class='vtable'><strong>".($text['label-recording'] ?? 'Recording')."</strong></td>\n";
echo " </tr>\n";
if (!empty($phrase_details)) {
foreach($phrase_details as $x => $field) {
//clean up output for display
if ($field['phrase_detail_function'] == 'play-file' && substr($field['phrase_detail_data'], 0, 21) == '${lua streamfile.lua ') {
$phrase_detail_function = $text['label-play'];
$phrase_detail_data = str_replace('${lua streamfile.lua ', '', $field['phrase_detail_data']);
$phrase_detail_data = str_replace('}', '', $phrase_detail_data);
}
else if ($field['phrase_detail_function'] == 'execute' && substr($field['phrase_detail_data'], 0, 6) == 'sleep(') {
$phrase_detail_function = $text['label-pause'];
$phrase_detail_data = str_replace('sleep(', '', $field['phrase_detail_data']);
$phrase_detail_data = str_replace(')', '', $phrase_detail_data);
$phrase_detail_data = ($phrase_detail_data / 1000).'s'; // seconds
}
else if ($field['phrase_detail_function'] == 'play-file') {
$phrase_detail_function = $text['label-play'];
$phrase_detail_data = str_replace($_SESSION['switch']['recordings']['dir'].'/'.$_SESSION['domain_name'].'/', '', $field['phrase_detail_data']);
}
else {
$phrase_detail_function = $field['phrase_detail_function'];
$phrase_detail_data = $field['phrase_detail_data'];
}
echo "<tr>\n";
echo " <td class='vtable'>".escape($phrase_detail_function)."&nbsp;</td>\n";
echo " <td class='vtable'>".escape($phrase_detail_data)."&nbsp;</td>\n";
echo " <td class='vtable' style='text-align: center;'>".$field['phrase_detail_order']."&nbsp;</td>\n";
echo " <td class='vtable' style='text-align: center; padding-bottom: 3px;'>";
if (is_uuid($field['phrase_detail_uuid'])) {
echo " <input type='checkbox' name='phrase_details_delete[".$x."][checked]' value='true' class='chk_delete checkbox_details' onclick=\"edit_delete_action('details');\">\n";
echo " <input type='hidden' name='phrase_details_delete[".$x."][uuid]' value='".escape($field['phrase_detail_uuid'])."' />\n";
}
echo " </td>\n";
echo "</tr>\n";
}
}
unset($phrase_details, $field);
echo "<tr>\n";
echo " </thead>\n";
//draggable rows are initially empty
echo "<tbody id='structure'>\n";
echo "</tbody>";
//show loading
echo "<tbody id='loading'><tr><td>&nbsp;</td><td><center>Loading...</center></td><td>&nbsp;</td></tr></tbody>\n";
//cloning row and buttons created outside of 'structure' table body
echo "<tbody>";
echo "<tr id='empty_row' style='display: none;'>\n";
echo " <td style='border-bottom: none;' nowrap='nowrap'><center><span class='fa-solid fa-arrows-up-down'></span></center></td>";
echo " <td class='vtable' style='border-bottom: none;' align='left' nowrap='nowrap'>\n";
echo " <select name='phrase_detail_function' id='phrase_detail_function' class='formfld' onchange=\"load_action_options(this.selectedIndex);\">\n";
echo " <select class='formfld' name='phrase_detail_function_empty' id='phrase_detail_function_empty' tag=''>\n";
echo " <option value='play-file'>".$text['label-play']."</option>\n";
echo " <option value='execute'>".$text['label-pause']."</option>\n";
if (if_group("superadmin")) {
echo " <option value='pause'>".$text['label-pause']."</option>\n";
if (permission_exists('phrase_execute')) {
echo " <option value='execute'>".$text['label-execute']."</option>\n";
}
echo " </select>\n";
echo " </td>\n";
echo " <td class='vtable' style='border-bottom: none;' align='left' nowrap='nowrap'>\n";
echo " <select name='phrase_detail_data' id='phrase_detail_data' class='formfld' style='width: 300px; min-width: 300px; max-width: 300px;' ".((if_group("superadmin")) ? "onchange='action_to_input();'" : null)."></select>";
if (if_group("superadmin")) {
echo " <input id='phrase_detail_data_switch' type='button' class='btn' style='margin-left: 4px; display: none;' value='&#9665;' onclick=\"action_to_select(); load_action_options(document.getElementById('phrase_detail_function').selectedIndex);\">\n";
}
echo " <script>load_action_options(0);</script>\n";
echo " <select class='formfld' id='phrase_detail_data_empty' name='phrase_detail_data_empty' style='width: 300px; min-width: 300px; max-width: 300px;' tag=''></select>";
// if (permission_exists('phrase_execute')) {
// echo " <input id='phrase_detail_data_switch_empty' type='button' class='btn' style='margin-left: 4px; display: none;' value='&#9665;' onclick=\"action_to_select(); load_action_options(document.getElementById('phrase_detail_function_empty').selectedIndex);\">\n";
// }
echo " <input type=hidden name='empty_uuid' value=''>";
echo " <input class='formfld' type=text name='empty_phrase_detail_text' value='' style='width: 300px; min-width: 300px; max-width: 300px; display: none'>";
echo " <span style='white-space: nowrap; display: flex; align-items: center; gap: 10px;'>";
echo " <input class='form-control-range' type=range name='range' minrange='1' style='width: 250px; min-width: 250px; max-width: 250px; display: none'>";
echo " <input type='text' class='formfld' name='sleep' style='width: 40px; min-width: 40px; max-width: 40px; display: none'>";
echo " </span>";
echo " </td>\n";
echo " <td class='vtable' style='border-bottom: none;'>\n";
echo " <select name='phrase_detail_order' class='formfld'>\n";
for ($i = 0; $i <= 999; $i++) {
$i_padded = str_pad($i, 3, '0', STR_PAD_LEFT);
echo " <option value='".escape($i_padded)."'>".escape($i_padded)."</option>\n";
}
echo " </select>\n";
echo " </td>\n";
echo " <td>\n";
echo button::create(['type'=>'submit','label'=>$text['button-add'],'icon'=>$_SESSION['theme']['button_icon_add']]);
echo " </td>\n";
echo " </tr>\n";
echo "</tr>\n";
echo "<tr>";
echo "<td>&nbsp;</td>";
echo "<td class='vtable' style='align=center;' colspan='2'><center>";
echo button::create(['type'=>'button','icon'=>$_SESSION['theme']['button_icon_add'], 'label' => $text['label-add'], 'onclick' => 'add_row()']);
echo button::create(['type'=>'button','icon'=>'fa-solid fa-minus', 'label' => $text['label-delete'], 'onclick' => 'remove_row()']);
echo "</center></td>";
echo "<td>&nbsp;</td>";
echo "</tr>";
echo "</tbody>\n";
echo "</table>\n";
echo " ".$text['description-structure']."\n";
@ -650,5 +617,3 @@
//include the footer
require_once "resources/footer.php";
?>

View File

@ -0,0 +1,199 @@
<?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-2024
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Mark J Crane <markjcrane@fusionpbx.com>
* Tim Fry <tim@fusionpbx.com>
*/
// require the class loader and global functions
//require dirname(__DIR__, 2) . '/resources/classes/auto_loader.php'; new auto_loader();
//require dirname(__DIR__, 2) . '/resources/functions.php';
require_once dirname(__DIR__, 2) . '/resources/require.php';
// Disable output buffering and compression
ini_set('output_buffering', 'off');
ini_set('zlib.output_compression', 'off');
ini_set('implicit_flush', 1);
ob_implicit_flush(1);
// Set headers to ensure immediate response
header('Content-Type: text/plain');
header('Cache-Control: no-cache');
header('Content-Encoding: none');
function fetch_recordings(database $database): array {
global $domain_uuid;
// guard against corrupt data
if (empty($domain_uuid) || !is_uuid($domain_uuid)) {
throw new Exception('Domain is invalid');
}
// always return an array
$return_value = [];
$sql = "select recording_uuid, recording_name, recording_filename, domain_uuid from v_recordings ";
$sql .= "where domain_uuid = :domain_uuid ";
$sql .= "order by recording_name asc ";
$parameters['domain_uuid'] = $domain_uuid;
$recordings = $database->select($sql, $parameters, 'all');
if (!empty($recordings)) {
$return_value = $recordings;
}
return $return_value;
}
function fetch_sound_files(settings $settings) {
$return_value = [];
//get the switch sound files
$file = new file($settings);
$sound_files = $file->sounds();
//try finding display_name in the switch sound files
if (!empty($sound_files)) {
$return_value = $sound_files;
}
return $return_value;
}
function fetch_phrase_details(settings $settings, string $phrase_uuid): array {
global $domain_uuid;
// guard against corrupt data
if (empty($domain_uuid) || !is_uuid($domain_uuid)) {
throw new Exception('Domain is invalid');
}
if (empty($phrase_uuid) || !is_uuid($phrase_uuid)) {
throw new Exception('Phrase UUID is invalid');
}
$database = $settings->database();
// set the return value to be an empty array
$return_value = [];
// get the phrase details
if (!empty($phrase_uuid)) {
$sql = "select * from v_phrase_details ";
$sql .= "where domain_uuid = :domain_uuid ";
$sql .= "and phrase_uuid = :phrase_uuid ";
$sql .= "order by phrase_detail_order asc ";
$parameters['domain_uuid'] = $domain_uuid;
$parameters['phrase_uuid'] = $phrase_uuid;
$phrase_details = $database->select($sql, $parameters, 'all');
}
//existing details
if (!empty($phrase_details)) {
$recordings = fetch_recordings($database);
$sound_files = fetch_sound_files($settings);
//update the array to include the recording name for display in select box
foreach ($phrase_details as &$row) {
$row['display_name'] = '';
$file = basename($row['phrase_detail_data']);
//get the display_name from recording name based on the file matched
foreach ($recordings as $key => $recordings_row) {
//match on filename first and then domain_uuid
if ($recordings_row['recording_filename'] === $file && $recordings_row['domain_uuid'] === $row['domain_uuid']) {
$row['display_name'] = $recordings[$key]['recording_name'];
break;
}
}
//check if display_name was not found in the recording names
if (strlen($row['display_name']) === 0) {
//try finding display_name in the switch sound files
if (!empty($sound_files)) {
//use optimized php function with strict comparison
$i = array_search($row['phrase_detail_data'], $sound_files, true);
//if found in the switch sound files
if ($i !== false) {
//set the display_name to the switch sound file name
$row['display_name'] = $sound_files[$i];
}
}
}
}
$return_value = $phrase_details;
}
return $return_value;
}
function fetch_domain_uuid(database $database): string {
$domain = $_SERVER['HTTP_HOST'];
$domain_uuid = '';
$sql = 'select domain_uuid from v_domains where domain_name = :domain';
$parameters = [];
$parameters['domain'] = $domain;
$result = $database->select($sql, $parameters, 'column');
if (!empty($result)) {
$domain_uuid = $result;
}
return $domain_uuid;
}
function send_message(string $json_data) {
echo $json_data . "\n";
ob_flush();
flush();
}
$config = config::load();
$database = database::new(['config' => $config]);
$domain_uuid = fetch_domain_uuid($database);
$settings = new settings(['database' => $database, 'domain_uuid' => $domain_uuid]);
// Set default response
$response = ['code' => 200, 'message' => ''];
// Check if the request method is POST
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$message = '';
// Get the raw POST data
$input = file_get_contents('php://input');
// Parse JSON data
$data = json_decode($input, true); // Decode JSON as associative array
if (isset($data['request'])) {
try {
//check the data source requested
switch ($data['request']) {
case 'sound_files':
$message = fetch_sound_files($settings);
break;
case 'recordings':
$message = fetch_recordings($database);
break;
case 'phrase_details':
$phrase_uuid = $data['data'];
$message = fetch_phrase_details($settings, $phrase_uuid);
break;
}
} catch (Exception $e) {
$response['code'] = 500;
$message = $e->getMessage();
}
}
//save the message
$response['message'] = $message;
//send the response
send_message(json_encode($response));
exit();
}
exit();

View File

@ -75,7 +75,7 @@
$token = new token;
if (!$token->validate($_SERVER['PHP_SELF'])) {
message::add($text['message-invalid_token'],'negative');
header('Location: '.$this->list_page);
header('Location: '.$this->list_page);$is_uuid
exit;
}
@ -429,4 +429,70 @@
}
} //method
} //class
/**
* Returns a path and filename of the recording_uuid provided from the database.
* If the recording is a base64 encoded file the file and path may be empty.
* @param database $database Database object
* @param string $recording_uuid Recording UUID
* @return string recording path and filename or an empty string when not found or base64
*/
public static function get_recording_filename(database $database, string $recording_uuid): string {
$sql = "select recording_filename from v_recordings";
$sql .= " where recording_uuid = :recording_uuid";
$parameters = [];
$parameters['recording_uuid'] = $recording_uuid;
$result = $database->select($sql, $parameters, 'column');
if ($result !== false) {
return $result;
}
return "";
}
/**
* Returns an associative array of recordings with the uuid as key and recording filename as value.
* When the recording is a base64 encoded recording, the filename returned is the filename only with no path.
* @param settings $settings Settings object
* @param int $limit (Optional) Limit the number of results returned
* @return array
*/
public static function get_all_domain_recordings(settings $settings, int $limit = 0): array {
//set defaults
$recordings = [];
$parameters = [];
//get the database object from the settings object
$database = $settings->database();
//get the domain name using the domain_uuid in the database object
$domain_uuid = $settings->domain_uuid();
$domain_name = $database->select("SELECT domain_name from v_domains where domain_uuid = :domain_uuid", ['domain_uuid' => $domain_uuid], 'column');
//get the recording directory
$recordings_dir = $settings->get('switch', 'recordings', '/var/lib/freeswitch/recordings') . DIRECTORY_SEPARATOR . $domain_name;
//build initial sql that ignores the domain_uuid
$sql = "SELECT recording_uuid, recording_filename, recording_base64 IS NOT NULL AS has_base64_recording FROM v_recordings";
//add domain_uuid to sql when available
if (!empty($domain_name) && is_uuid($domain_uuid)) {
$sql .= " where domain_uuid = :domain_uuid";
$parameters['domain_uuid'] = $domain_uuid;
}
//add limit to sql if needed
if (!empty($limit)) {
$sql .= " limit $limit";
}
//get the result
$rows = $database->select($sql, $parameters);
//iterate over all rows returned to remap them to uuid => filename
if (!empty($rows)) {
//set the path and filename for each of the uuids
foreach($rows as $row) {
if ($row['has_base64_recording']) {
$recordings[$row['recording_uuid']] = '${lua streamfile.lua ' . basename($row['recording_filename']) .'}';
} else {
$recordings[$row['recording_uuid']] = $recordings_dir . DIRECTORY_SEPARATOR . $row['recording_filename'];
}
}
}
//return recordings or empty array
return $recordings;
}
}
}

View File

@ -0,0 +1,377 @@
// use an async function so page works without delays
document.addEventListener("DOMContentLoaded", async function () {
// Initialize the select options
const select = document.getElementById('phrase_detail_data_empty');
const grp_rec = document.createElement('optgroup');
const grp_snd = document.createElement('optgroup');
// Add a blank entry
select.appendChild(new Option('', ''));
// Add recordings
grp_rec.label = window.phrase_label_recordings;
try {
const phrase_recordings = await fetch_data({request: 'recordings', data: ''});
for (let i = 0; i < phrase_recordings.length; i++) {
grp_rec.appendChild(new Option(phrase_recordings[i].recording_name, phrase_recordings[i].recording_uuid));
}
select.appendChild(grp_rec);
} catch (error) {
console.error("Error fetching recordings:", error);
}
// Add sounds
grp_snd.label = window.phrase_label_sounds;
try {
const phrase_sounds = await fetch_data({request: 'sound_files', data: ''});
for (let i = 0; i < phrase_sounds.length; i++) {
grp_snd.appendChild(new Option(phrase_sounds[i], phrase_sounds[i]));
}
select.appendChild(grp_snd);
} catch (error) {
console.error("Error fetching recordings:", error);
}
// add the existing data
add_existing();
// add empty row
add_row();
// Initialize draggable rows
add_draggable_rows();
});
function remove_loading() {
//remove loading
const loading = document.getElementById('loading');
if (loading) {
loading.remove();
}
}
async function fetch_data(command) {
try {
const response = await fetch('resources/phrase_responder.php', {
method: 'POST', // or 'GET' depending on your requirement
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(command) // If sending data with POST
});
const data = await response.json();
return data.message;
} catch (error) {
console.error('Error:', error);
}
}
//
// Inserts all existing records before the empty one
//
async function add_existing() {
//get the body structure
const tbody = document.getElementById('structure');
//get the phrase id
const urlParams = new URLSearchParams(window.location.search);
const phrase_uuid = urlParams.get('id');
//fetch the phrase details from the database
const phrase_details = await fetch_data({request: 'phrase_details', data: phrase_uuid});
//display the phrase details
for (let index=0; index < phrase_details.length; index++) {
//add an empty row
add_row();
//get the action select box
const select_action = document.getElementById('phrase_detail_function[' + index + ']');
//set the chosen option
select_by_value(select_action, phrase_details[index].phrase_detail_function);
//get the data select box
const select_data = document.getElementById('phrase_detail_data[' + index + ']');
//set the chosen option
select_by_text(select_data, phrase_details[index]['display_name']);
const uuid_field = document.getElementById('phrase_detail_uuid[' +index+']');
uuid_field.value = phrase_details[index]['phrase_detail_uuid'];
//set the slider value
const slider = document.getElementById('slider['+index+']');
const sleep = document.getElementById('sleep['+index+']');
const phrase_detail_text = document.getElementById('phrase_detail_text['+index+']');
//update the sleep data
if (phrase_details[index].phrase_detail_function === 'pause') {
sleep.value = phrase_details[index].phrase_detail_data;
slider.value = phrase_details[index].phrase_detail_data;
}
//update the execute text
if (phrase_details[index].phrase_detail_function === 'execute') {
phrase_detail_text.value = phrase_details[index].phrase_detail_data;
}
// Manually trigger the change event to select the proper display
if (phrase_details[index].phrase_detail_function !== 'play-file') {
const changeEvent = new Event('change', { bubbles: true });
select_action.dispatchEvent(changeEvent);
}
}
remove_loading();
}
//
// Set the selected index on a dropdown box based on the value (key)
//
function select_by_value(selectElement, valueToFind) {
// Loop through the options of the select element
for (let i = 0; i < selectElement.options.length; i++) {
if (selectElement.options[i].value === valueToFind) {
selectElement.selectedIndex = i; // Set the selected index
return; // Exit the loop once found
}
}
console.warn('Value not found in select options');
}
//
// Set the selected index on a dropdown box based on the text
//
function select_by_text(selectElement, textToFind) {
for (let i = 0; i < selectElement.options.length; i++) {
if (selectElement.options[i].text === textToFind) {
selectElement.selectedIndex = i;
return;
}
}
console.warn('Text not found in select options');
}
//
// Add draggable functionality to rows
//
function add_draggable_rows() {
const tableBody = document.getElementById('structure');
let draggedRow = null;
// Add drag listeners only to the leftmost cell on the row
tableBody.querySelectorAll('tr').forEach(row => {
const dragHandleCell = row.cells[0]; // Assuming the first cell is the one left to the dropdown
if (!dragHandleCell) return;
// Enable dragging from this cell
dragHandleCell.setAttribute('draggable', 'true');
dragHandleCell.addEventListener('dragstart', (e) => {
draggedRow = row;
row.classList.add('dragging');
});
dragHandleCell.addEventListener('dragend', () => {
if (draggedRow) {
draggedRow.classList.remove('dragging');
draggedRow = null;
}
});
dragHandleCell.addEventListener('dragover', (e) => {
e.preventDefault();
const targetRow = e.target.closest('tr');
if (targetRow && targetRow !== draggedRow) {
const bounding = targetRow.getBoundingClientRect();
const offset = e.clientY - bounding.top;
if (offset > bounding.height / 2) {
targetRow.parentNode.insertBefore(draggedRow, targetRow.nextSibling);
} else {
targetRow.parentNode.insertBefore(draggedRow, targetRow);
}
}
});
dragHandleCell.addEventListener('dragend', () => {
if (draggedRow) {
draggedRow.classList.remove('dragging');
draggedRow = null;
update_order();
}
});
});
}
//
// Function to update the 'name' attribute based on row numbers
//
function update_order() {
const tableBody = document.getElementById('structure');
const rows = tableBody.querySelectorAll('tr');
//iterate over all rows to renumber them
rows.forEach((row, index) => {
//set 'name' attribute and id
row.setAttribute('name', 'row_' + index);
row.id = 'row_' + index;
//get the select boxes
const select_list = row.querySelectorAll('td select'); //action and recording select dropdown boxes
//get the input boxes
const input_boxes = row.querySelectorAll('td input');
//uuid
const phrase_detail_uuid = input_boxes[0];
phrase_detail_uuid.removeAttribute('id');
phrase_detail_uuid.id = 'phrase_detail_uuid[' + index + ']';
phrase_detail_uuid.name = phrase_detail_uuid.id;
//execute action
const phrase_detail_text = input_boxes[1];
temp_value = phrase_detail_text.value;
phrase_detail_text.removeAttribute('id');
phrase_detail_text.id = 'phrase_detail_text[' + index + ']';
phrase_detail_text.name = phrase_detail_text.id;
phrase_detail_text.value = temp_value;
//slider
const slider = input_boxes[2];
slider.removeAttribute('id');
slider.id = 'slider[' + index + ']';
slider.name = slider.id;
//sleep value
const sleep = input_boxes[3];
temp_value = sleep.value;
sleep.removeAttribute('id');
sleep.id = 'sleep[' + index + ']';
sleep.name = sleep.id;
sleep.value = temp_value;
//play, pause, execute select box
const select_function = select_list[0];
select_function.removeAttribute('id');
select_function.id = 'phrase_detail_function[' + index + ']'
select_function.name = select_function.id;
//recording select box
const select_data = select_list[1];
select_data.removeAttribute('id');
select_data.id = 'phrase_detail_data[' + index + ']'
select_data.name = select_data.id;
});
}
//
// Ensure the order is updated when submitting the form
//
function submit_phrase() {
//ensure order is updated before submitting form
update_order();
//submit form
const form = document.getElementById('frm').submit();
}
//
// Add a new row to the table
//
function add_row() {
const tbody = document.getElementById('structure');
// current index is the count subtract the hidden row
const index = tbody.childElementCount;
const newRow = document.getElementById('empty_row').cloneNode(true);
//reset id
newRow.removeAttribute('id');
newRow.id = 'row_' + index;
//un-hide row
newRow.style.display = '';
//reset 'name' attribute
newRow.setAttribute('name', 'recording_' + index);
//get the select boxes
const select_list = newRow.querySelectorAll('td select'); //action and recording select dropdown boxes
//play, pause, execute select box
const select_action = select_list[0];
select_action.removeAttribute('id');
select_action.id = 'phrase_detail_function[' + index + ']';
select_action.name = 'phrase_detail_function[' + index + ']';
//recording select box
const select_data = select_list[1];
select_data.removeAttribute('id');
select_data.id = 'phrase_detail_data[' + index + ']';
select_data.name = 'phrase_detail_data[' + index + ']';
//uuid field
const uuid_field = newRow.querySelector('input[name="empty_uuid"]');
uuid_field.removeAttribute('id');
uuid_field.id = 'phrase_detail_uuid[' + index +']';
uuid_field.name = 'phrase_detail_uuid[' + index +']';
const phrase_detail_text = newRow.querySelector('input[name="empty_phrase_detail_text"]');
phrase_detail_text.removeAttribute('id');
phrase_detail_text.id = 'phrase_detail_text[' + index + ']';
phrase_detail_text.name = 'phrase_detail_text[' + index + ']';
//slider
const slider = newRow.querySelector('input[name="range"]');
slider.removeAttribute('id');
slider.id = 'slider[' + index + ']';
slider.name = 'slider[' + index + ']';
//sleep
const sleep = newRow.querySelector('input[name="sleep"]');
sleep.removeAttribute('id');
sleep.id = 'sleep[' + index + ']';
sleep.name = 'sleep[' + index + ']';
sleep.value = slider.value;
let changing = false;
slider.addEventListener('mousemove', function () {
changing = true;
sleep.value = slider.value;
changing = false;
});
sleep.addEventListener('keyup', function() {
if (!changing) {
if (sleep.value.length > 0)
slider.value = sleep.value;
else {
slider.value = 0;
}
}
})
//add switchable select box to text input box
select_action.addEventListener('change', function () {
if (select_action.value === 'execute') {
//show text box
select_data.style.display = 'none';
slider.style.display = 'none';
sleep.style.display = 'none';
phrase_detail_text.style.display = 'block';
} else if (select_action.value === 'pause') {
//show the range bar
select_data.style.display = 'none';
phrase_detail_text.style.display = 'none';
slider.style.display = 'block';
sleep.style.display = 'block';
} else {
//show drop down
phrase_detail_text.style.display = 'none';
slider.style.display = 'none';
sleep.style.display = 'none';
select_data.style.display = 'block';
}
});
//add the row to the table body
tbody.appendChild(newRow);
//reinitialize draggable functionality on the row
add_draggable_rows();
return index;
}
//
// Remove the last row in the table
//
function remove_row() {
const tbody = document.getElementById('structure');
if (tbody && tbody.rows.length > 1) {
tbody.lastElementChild.remove();
}
}

View File

@ -0,0 +1,52 @@
/*
* 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-2024
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Mark J Crane <markjcrane@fusionpbx.com>
* Tim Fry <tim@fusionpbx.com>
*/
async function fetchData() {
try {
const response = await fetch('phrase_responder.php', {
method: 'POST', // or 'GET' depending on your requirement
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ request: 'phrase_details', phrase_uuid: '86141ace-b218-4f07-b412-fe02d4fdde17' }) // If sending data with POST
});
const data = await response.text();
const json = JSON.parse(data);
const body = document.body;
const input = document.createElement('input');
input.type = 'text';
input.value = json.message;
body.appendChild(input);
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
fetchData();

View File

@ -0,0 +1,204 @@
<?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-2024
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Mark J Crane <markjcrane@fusionpbx.com>
* Tim Fry <tim@fusionpbx.com>
*/
require_once dirname(__DIR__, 3) . '/resources/require.php';
//check permissions
if (permission_exists('phrase_add') || permission_exists('phrase_edit')) {
//access granted
}
else {
echo "access denied";
exit;
}
// Disable output buffering and compression
ini_set('output_buffering', 'off');
ini_set('zlib.output_compression', 'off');
ini_set('implicit_flush', 1);
ob_implicit_flush(1);
// Set headers to ensure immediate response
header('Content-Type: text/plain');
header('Cache-Control: no-cache');
header('Content-Encoding: none');
function fetch_recordings(database $database): array {
global $domain_uuid;
// guard against corrupt data
if (empty($domain_uuid) || !is_uuid($domain_uuid)) {
throw new Exception('Domain is invalid');
}
// always return an array
$return_value = [];
$sql = "select recording_uuid, recording_name, recording_filename, domain_uuid from v_recordings ";
$sql .= "where domain_uuid = :domain_uuid ";
$sql .= "order by recording_name asc ";
$parameters['domain_uuid'] = $domain_uuid;
$recordings = $database->select($sql, $parameters, 'all');
if (!empty($recordings)) {
$return_value = $recordings;
}
return $return_value;
}
function fetch_sound_files(settings $settings) {
$return_value = [];
//get the switch sound files
$file = new file($settings);
$sound_files = $file->sounds();
//try finding display_name in the switch sound files
if (!empty($sound_files)) {
$return_value = $sound_files;
}
return $return_value;
}
function fetch_phrase_details(settings $settings, string $phrase_uuid): array {
global $domain_uuid;
// guard against corrupt data
if (empty($domain_uuid) || !is_uuid($domain_uuid)) {
throw new Exception('Domain is invalid');
}
if (empty($phrase_uuid) || !is_uuid($phrase_uuid)) {
throw new Exception('Phrase UUID is invalid');
}
$database = $settings->database();
// set the return value to be an empty array
$return_value = [];
// get the phrase details
if (!empty($phrase_uuid)) {
$sql = "select * from v_phrase_details ";
$sql .= "where domain_uuid = :domain_uuid ";
$sql .= "and phrase_uuid = :phrase_uuid ";
$sql .= "order by phrase_detail_order asc ";
$parameters['domain_uuid'] = $domain_uuid;
$parameters['phrase_uuid'] = $phrase_uuid;
$phrase_details = $database->select($sql, $parameters, 'all');
}
//existing details
if (!empty($phrase_details)) {
$recordings = fetch_recordings($database);
$sound_files = fetch_sound_files($settings);
//update the array to include the recording name for display in select box
foreach ($phrase_details as &$row) {
$row['display_name'] = '';
$file = basename($row['phrase_detail_data']);
//get the display_name from recording name based on the file matched
foreach ($recordings as $key => $recordings_row) {
//match on filename first and then domain_uuid
if ($recordings_row['recording_filename'] === $file && $recordings_row['domain_uuid'] === $row['domain_uuid']) {
$row['display_name'] = $recordings[$key]['recording_name'];
break;
}
}
//check if display_name was not found in the recording names
if (strlen($row['display_name']) === 0) {
//try finding display_name in the switch sound files
if (!empty($sound_files)) {
//use optimized php function with strict comparison
$i = array_search($row['phrase_detail_data'], $sound_files, true);
//if found in the switch sound files
if ($i !== false) {
//set the display_name to the switch sound file name
$row['display_name'] = $sound_files[$i];
}
}
}
}
$return_value = $phrase_details;
}
return $return_value;
}
function fetch_domain_uuid(database $database): string {
$domain = $_SERVER['HTTP_HOST'];
$domain_uuid = '';
$sql = 'select domain_uuid from v_domains where domain_name = :domain';
$parameters = [];
$parameters['domain'] = $domain;
$result = $database->select($sql, $parameters, 'column');
if (!empty($result)) {
$domain_uuid = $result;
}
return $domain_uuid;
}
function send_message(string $json_data) {
echo $json_data . "\n";
ob_flush();
flush();
}
$config = config::load();
$database = database::new(['config' => $config]);
$domain_uuid = fetch_domain_uuid($database);
$settings = new settings(['database' => $database, 'domain_uuid' => $domain_uuid]);
// Set default response
$response = ['code' => 200, 'message' => ''];
// Check if the request method is POST
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$message = '';
// Get the raw POST data
$input = file_get_contents('php://input');
// Parse JSON data
$data = json_decode($input, true); // Decode JSON as associative array
if (isset($data['request'])) {
try {
//check the data source requested
switch ($data['request']) {
case 'sound_files':
$message = fetch_sound_files($settings);
break;
case 'recordings':
$message = fetch_recordings($database);
break;
case 'phrase_details':
$phrase_uuid = $data['data'];
$message = fetch_phrase_details($settings, $phrase_uuid);
break;
}
} catch (Exception $e) {
$response['code'] = 500;
$message = $e->getMessage();
}
}
//save the message
$response['message'] = $message;
//send the response
send_message(json_encode($response));
exit();
}
exit();

View File

@ -0,0 +1,11 @@
.draggable-row {
cursor: move;
}
.dragging {
opacity: 0.5;
}
.target-background {
background-color: #ffc107;
}

View File

@ -2,8 +2,6 @@
/**
* cache class provides an abstracted cache
*
* @method string glob
*/
class file {
@ -12,12 +10,17 @@ class file {
*/
public $recursive;
public $files;
private $settings;
/**
* Called when the object is created
* @param settings $settings Settings object
*/
public function __construct() {
//place holder
public function __construct($settings = null) {
if ($settings === null) {
$settings = new settings();
}
$this->settings = $settings;
}
/**
@ -66,8 +69,8 @@ class file {
if (!isset($voice)) { $voice = 'callie'; }
//set the variables
if (!empty($_SESSION['switch']['sounds']['dir']) && file_exists($_SESSION['switch']['sounds']['dir'])) {
$dir = $_SESSION['switch']['sounds']['dir'].'/'.$language.'/'.$dialect.'/'.$voice;
if (!empty($this->settings->get('switch', 'sounds')) && file_exists($this->settings->get('switch', 'sounds'))) {
$dir = $this->settings->get('switch', 'sounds').'/'.$language.'/'.$dialect.'/'.$voice;
$rate = '8000';
$files = $this->glob($dir.'/*/'.$rate, true);
}
@ -93,5 +96,3 @@ class file {
$files = $file->sounds();
print_r($files);
*/
?>

View File

@ -110,6 +110,22 @@ class settings implements clear_cache {
return $this->database;
}
/**
* Returns the domain_uuid that was used to load the settings
* @return string domain_uuid or an empty string
*/
public function domain_uuid(): string {
return $this->domain_uuid;
}
/**
* Returns the user_uuid that was used to load the settings
* @return string user_uuid or an empty string
*/
public function user_uuid(): string {
return $this->user_uuid;
}
/**
* Reloads the settings from the database
*/