diff --git a/app/phrases/phrase.php b/app/phrases/phrase.php new file mode 100644 index 0000000000..7e304b6512 --- /dev/null +++ b/app/phrases/phrase.php @@ -0,0 +1,30 @@ + + * Portions created by the Initial Developer are Copyright (C) 2008-2024 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark J Crane + * Tim Fry + */ + +echo ""; +echo ""; diff --git a/app/phrases/phrase_edit.php b/app/phrases/phrase_edit.php index e8ba79f0af..e878f04247 100644 --- a/app/phrases/phrase_edit.php +++ b/app/phrases/phrase_edit.php @@ -414,10 +414,10 @@ if (count($_POST) > 0) { } } //send the phrase details to the browser as a global scope json array object - echo "window.phrase_details = " . json_encode($phrase_details, true) . ";\n"; + //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 "window.phrase_details = [];\n"; } //recording files diff --git a/app/phrases/phrase_responder.php b/app/phrases/phrase_responder.php new file mode 100644 index 0000000000..004042d8a4 --- /dev/null +++ b/app/phrases/phrase_responder.php @@ -0,0 +1,199 @@ + + * Portions created by the Initial Developer are Copyright (C) 2008-2024 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark J Crane + * Tim Fry + */ + +// 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(); diff --git a/app/phrases/resources/javascript/phrase_edit.js b/app/phrases/resources/javascript/phrase_edit.js index 1385f5a769..e226919a39 100644 --- a/app/phrases/resources/javascript/phrase_edit.js +++ b/app/phrases/resources/javascript/phrase_edit.js @@ -1,4 +1,4 @@ -document.addEventListener("DOMContentLoaded", function () { +document.addEventListener("DOMContentLoaded", async function () { // Initialize the select options const select = document.getElementById('phrase_detail_data_empty'); const grp_rec = document.createElement('optgroup'); @@ -9,17 +9,27 @@ document.addEventListener("DOMContentLoaded", function () { // Add recordings grp_rec.label = window.phrase_label_recordings; - for (let i = 0; i < window.phrase_recordings.length; i++) { - grp_rec.appendChild(new Option(window.phrase_recordings[i].recording_name, window.phrase_recordings[i].recording_uuid)); + 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); } - select.appendChild(grp_rec); // Add sounds grp_snd.label = window.phrase_label_sounds; - for (let i = 0; i < window.phrase_sounds.length; i++) { - grp_snd.appendChild(new Option(window.phrase_sounds[i], window.phrase_sounds[i])); + 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); } - select.appendChild(grp_snd); // add the existing data add_existing(); @@ -31,34 +41,65 @@ document.addEventListener("DOMContentLoaded", function () { add_draggable_rows(); }); +async function fetch_data(command) { + try { + const response = await fetch('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 // -function add_existing() { +async function add_existing() { + //get the body structure const tbody = document.getElementById('structure'); - - for (let index=0; index < window.phrase_details.length; index++) { + //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 + ']'); - select_by_value(select_action, window.phrase_details[index].phrase_detail_function); + //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 + ']'); - select_by_text(select_data, window.phrase_details[index]['display_name']); + //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 = window.phrase_details[index]['phrase_detail_uuid']; + 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 (window.phrase_details[index].phrase_detail_function === 'pause') { - sleep.value = window.phrase_details[index].phrase_detail_data; - slider.value = window.phrase_details[index].phrase_detail_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 (window.phrase_details[index].phrase_detail_function === 'execute') { - phrase_detail_text.value = window.phrase_details[index].phrase_detail_data; + 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 (window.phrase_details[index].phrase_detail_function !== 'play-file') { + if (phrase_details[index].phrase_detail_function !== 'play-file') { const changeEvent = new Event('change', { bubbles: true }); select_action.dispatchEvent(changeEvent); } diff --git a/app/phrases/resources/javascript/phrase_fetch.js b/app/phrases/resources/javascript/phrase_fetch.js new file mode 100644 index 0000000000..2e899040b0 --- /dev/null +++ b/app/phrases/resources/javascript/phrase_fetch.js @@ -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 + * Portions created by the Initial Developer are Copyright (C) 2008-2024 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark J Crane + * Tim Fry + */ + +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();