IVR Menu Edit: Add text to speech option for new greetings (#7994)

* IVR Menu Edit: Add text to speech option for new greetings

* Update app_languages.php
This commit is contained in:
Alex
2026-05-26 15:18:16 -07:00
committed by GitHub
parent 4d34454d14
commit e1f6bf795e
2 changed files with 460 additions and 3 deletions
+161
View File
@@ -945,6 +945,167 @@ $text['label-copy']['zh-cn'] = "复制";
$text['label-copy']['ja-jp'] = "コピー";
$text['label-copy']['ko-kr'] = "복사";
$text['label-new']['en-us'] = "New";
$text['label-new']['en-gb'] = "New";
$text['label-new']['ar-eg'] = "جديدة";
$text['label-new']['de-at'] = "Neu";
$text['label-new']['de-ch'] = "Neu";
$text['label-new']['de-de'] = "Neu";
$text['label-new']['el-gr'] = "Νέος";
$text['label-new']['es-cl'] = "Nueva";
$text['label-new']['es-mx'] = "Nueva";
$text['label-new']['fr-ca'] = "Nouveau";
$text['label-new']['fr-fr'] = "Nouveau";
$text['label-new']['he-il'] = "חדש";
$text['label-new']['it-it'] = "Nuovo";
$text['label-new']['ka-ge'] = "ახალი";
$text['label-new']['nl-nl'] = "Nieuws";
$text['label-new']['pl-pl'] = "Nowy";
$text['label-new']['pt-br'] = "Nova";
$text['label-new']['pt-pt'] = "Nova";
$text['label-new']['ro-ro'] = "Nou";
$text['label-new']['ru-ru'] = "Новый";
$text['label-new']['sv-se'] = "Nytt";
$text['label-new']['uk-ua'] = "Нові";
$text['label-new']['tr-tr'] = "Yeni";
$text['label-new']['zh-cn'] = "新的";
$text['label-new']['ja-jp'] = "新しい";
$text['label-new']['ko-kr'] = "새로운";
$text['label-text_to_speech']['en-us'] = "Text to Speech";
$text['label-text_to_speech']['en-gb'] = "Text to Speech";
$text['label-text_to_speech']['ar-eg'] = "تحويل النص إلى كلام";
$text['label-text_to_speech']['de-at'] = "Text-to-Speech";
$text['label-text_to_speech']['de-ch'] = "Text-to-Speech";
$text['label-text_to_speech']['de-de'] = "Text-to-Speech";
$text['label-text_to_speech']['el-gr'] = "Κείμενο σε Ομιλία";
$text['label-text_to_speech']['es-cl'] = "Texto a voz";
$text['label-text_to_speech']['es-mx'] = "Texto a voz";
$text['label-text_to_speech']['fr-ca'] = "Synthèse vocale";
$text['label-text_to_speech']['fr-fr'] = "Synthèse vocale";
$text['label-text_to_speech']['he-il'] = "טקסט לדיבור";
$text['label-text_to_speech']['it-it'] = "Testo a voce";
$text['label-text_to_speech']['ka-ge'] = "ტექსტის ხმოვანი";
$text['label-text_to_speech']['nl-nl'] = "Tekst naar spraak";
$text['label-text_to_speech']['pl-pl'] = "Tekst na mowę";
$text['label-text_to_speech']['pt-br'] = "Texto para fala";
$text['label-text_to_speech']['pt-pt'] = "Texto para fala";
$text['label-text_to_speech']['ro-ro'] = "Text la voce";
$text['label-text_to_speech']['ru-ru'] = "Синтез речи";
$text['label-text_to_speech']['sv-se'] = "Text till tal";
$text['label-text_to_speech']['uk-ua'] = "Синтез мови";
$text['label-text_to_speech']['tr-tr'] = "Metinden Konuşmaya";
$text['label-text_to_speech']['zh-cn'] = "文字转语音";
$text['label-text_to_speech']['ja-jp'] = "テキスト読み上げ";
$text['label-text_to_speech']['ko-kr'] = "텍스트 음성 변환";
$text['label-message']['en-us'] = "Message";
$text['label-message']['en-gb'] = "Message";
$text['label-message']['ar-eg'] = "رسالة";
$text['label-message']['de-at'] = "Nachricht";
$text['label-message']['de-ch'] = "Nachricht";
$text['label-message']['de-de'] = "Nachricht";
$text['label-message']['el-gr'] = "Μήνυμα";
$text['label-message']['es-cl'] = "Mensaje";
$text['label-message']['es-mx'] = "Mensaje";
$text['label-message']['fr-ca'] = "Message";
$text['label-message']['fr-fr'] = "Message";
$text['label-message']['he-il'] = "הודעה";
$text['label-message']['it-it'] = "Messaggio";
$text['label-message']['ka-ge'] = "შეტყობინება";
$text['label-message']['nl-nl'] = "Boodschap";
$text['label-message']['pl-pl'] = "Wiadomość";
$text['label-message']['pt-br'] = "Mensagem";
$text['label-message']['pt-pt'] = "Mensagem";
$text['label-message']['ro-ro'] = "Mesaj";
$text['label-message']['ru-ru'] = "Сообщение";
$text['label-message']['sv-se'] = "Meddelande";
$text['label-message']['uk-ua'] = "Повідомлення";
$text['label-message']['zh-cn'] = "信息";
$text['label-message']['ja-jp'] = "メッセージ";
$text['label-message']['ko-kr'] = "메시지";
$text['label-speed']['en-us'] = "Speed";
$text['label-speed']['en-gb'] = "Speed";
$text['label-speed']['ar-eg'] = "سرعة";
$text['label-speed']['de-at'] = "Geschwindigkeit";
$text['label-speed']['de-ch'] = "Geschwindigkeit";
$text['label-speed']['de-de'] = "Geschwindigkeit";
$text['label-speed']['el-gr'] = "Ταχύτητα";
$text['label-speed']['es-cl'] = "Velocidad";
$text['label-speed']['es-mx'] = "Velocidad";
$text['label-speed']['fr-ca'] = "Vitesse";
$text['label-speed']['fr-fr'] = "Vitesse";
$text['label-speed']['he-il'] = "מהירות";
$text['label-speed']['it-it'] = "Velocità";
$text['label-speed']['ka-ge'] = "სიჩქარე";
$text['label-speed']['nl-nl'] = "Snelheid";
$text['label-speed']['pl-pl'] = "Prędkość";
$text['label-speed']['pt-br'] = "Velocidade";
$text['label-speed']['pt-pt'] = "Velocidade";
$text['label-speed']['ro-ro'] = "Viteză";
$text['label-speed']['ru-ru'] = "Скорость";
$text['label-speed']['sv-se'] = "Hastighet";
$text['label-speed']['uk-ua'] = "Швидкість";
$text['label-speed']['tr-tr'] = "Hız";
$text['label-speed']['zh-cn'] = "速度";
$text['label-speed']['ja-jp'] = "速度";
$text['label-speed']['ko-kr'] = "속도";
$text['label-voice']['en-us'] = "Voice";
$text['label-voice']['en-gb'] = "Voice";
$text['label-voice']['ar-eg'] = "صوت";
$text['label-voice']['de-at'] = "Stimme";
$text['label-voice']['de-ch'] = "Stimme";
$text['label-voice']['de-de'] = "Stimme";
$text['label-voice']['el-gr'] = "Φωνή";
$text['label-voice']['es-cl'] = "Voz";
$text['label-voice']['es-mx'] = "Voz";
$text['label-voice']['fr-ca'] = "Voix";
$text['label-voice']['fr-fr'] = "Voix";
$text['label-voice']['he-il'] = "קוֹל";
$text['label-voice']['it-it'] = "Voce";
$text['label-voice']['ka-ge'] = "ხმა";
$text['label-voice']['nl-nl'] = "Stem";
$text['label-voice']['pl-pl'] = "Głos";
$text['label-voice']['pt-br'] = "Voz";
$text['label-voice']['pt-pt'] = "Voz";
$text['label-voice']['ro-ro'] = "Voce";
$text['label-voice']['ru-ru'] = "Голос";
$text['label-voice']['sv-se'] = "Röst";
$text['label-voice']['uk-ua'] = "Голос";
$text['label-voice']['tr-tr'] = "Ses";
$text['label-voice']['zh-cn'] = "嗓音";
$text['label-voice']['ja-jp'] = "";
$text['label-voice']['ko-kr'] = "목소리";
$text['label-recording_name']['en-us'] = "Recording Name";
$text['label-recording_name']['en-gb'] = "Recording Name";
$text['label-recording_name']['ar-eg'] = "الاسم المسجل";
$text['label-recording_name']['de-at'] = "Name der Aufnahme";
$text['label-recording_name']['de-ch'] = "Name der Aufnahme";
$text['label-recording_name']['de-de'] = "Name der Aufnahme";
$text['label-recording_name']['el-gr'] = "Όνομα εγγραφής";
$text['label-recording_name']['es-cl'] = "Nombre de la grabación";
$text['label-recording_name']['es-mx'] = "Nombre de la grabación";
$text['label-recording_name']['fr-ca'] = "Nom de l'enregistrement";
$text['label-recording_name']['fr-fr'] = "Nom de l'enregistrement";
$text['label-recording_name']['he-il'] = "שם הקובץ מדיה";
$text['label-recording_name']['it-it'] = "Nome Registrazione";
$text['label-recording_name']['ka-ge'] = "ჩანაწერის სახელი";
$text['label-recording_name']['nl-nl'] = "Opnamenaam";
$text['label-recording_name']['pl-pl'] = "Nazwa nagrania";
$text['label-recording_name']['pt-br'] = "Nome da gravação";
$text['label-recording_name']['pt-pt'] = "Nome da gravação";
$text['label-recording_name']['ro-ro'] = "Nume înregistrare";
$text['label-recording_name']['ru-ru'] = "Название записи";
$text['label-recording_name']['sv-se'] = "Namn på inspelning";
$text['label-recording_name']['uk-ua'] = "Назва запису";
$text['label-recording_name']['tr-tr'] = "Kayıt Adı";
$text['label-recording_name']['zh-cn'] = "录音名称";
$text['label-recording_name']['ja-jp'] = "録音名";
$text['label-recording_name']['ko-kr'] = "녹음 이름";
$text['header-option_list']['en-us'] = "Option List";
$text['header-option_list']['en-gb'] = "Option List";
$text['header-option_list']['ar-eg'] = "قائمة الخيارات";
+299 -3
View File
@@ -50,6 +50,42 @@
$ivr_menu_greet_short = '';
$ivr_menu_description = '';
$ivr_menu_ringback = $settings->get('ivr_menu','default_ringback', 'local_stream://default');
$recording_name = '';
$recording_message = '';
$recording_description = '';
$recording_speed = '1.0';
$recording_uuid = '';
$speed_enabled = false;
$speed_options = [];
$translate_enabled = false;
$language_enabled = false;
//set the variables
$domain_uuid = $_SESSION['domain_uuid'];
$domain_name = $_SESSION['domain_name'];
$user_uuid = $_SESSION['user_uuid'];
//add the settings object
$settings = new settings(["domain_uuid" => $domain_uuid, "user_uuid" => $user_uuid]);
$speech_enabled = class_exists('speech') && $settings->get('speech', 'enabled', false);
$speech_engine = $settings->get('speech', 'engine', '');
//add the speech object and get the voices and languages arrays
if ($speech_enabled && !empty($speech_engine)) {
$speech = new speech($settings);
$voices = $speech->get_voices();
$recording_extension = $speech->get_format();
$speed_enabled = $speech->is_speed_enabled();
$speed_options = $speed_enabled ? $speech->get_speed_options() : [];
// Determine the aray type single, or multi
$voices_array_type = array_type($voices);
// Sort the array by language code keys alphabetically
if ($voices_array_type == 'multi') {
ksort($voices);
}
}
//initialize the destinations object
$destination = new destinations;
@@ -120,7 +156,6 @@
//get http post values and set them to php variables
if (!empty($_POST)) {
//process the http post data by submitted action
if (!empty($_POST['action']) && is_uuid($_POST['ivr_menu_uuid'])) {
$array[0]['checked'] = 'true';
@@ -436,6 +471,76 @@
$p->add("dialplan_edit", "temp");
}
//build the recording array
$greeting_types = ['long', 'short'];
foreach ($greeting_types as $x => $type) {
$greeting_type = 'greeting_'.$type;
$recording_name = $_POST[$greeting_type]['recording_name'] ?? '';
$recording_voice = $_POST[$greeting_type]['recording_voice'] ?? '';
$recording_speed = $_POST[$greeting_type]['recording_speed'] ?? '1.0';
$recording_message = $_POST[$greeting_type]['recording_message'] ?? '';
$recording_desc = $_POST[$greeting_type]['recording_description'] ?? '';
if (permission_exists('recording_edit') && !empty($recording_message)) {
$recording_uuid = uuid();
if (empty($recording_name)) {
$recording_name = 'recording'.$ivr_menu_extension.($type == 'short' ? '-'.$type : null).'-'.str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT);
}
//set the recording format for approved types
if (!in_array($recording_extension, ['mp3', 'wav'], true)) {
//default to wav
$recording_extension = 'wav';
}
//build the setting object and get the recording path
$recording_path = $settings->get('switch', 'recordings').'/'.$domain_name;
//create the file name
$recording_filename = empty($_POST[$greeting_type]['recording_filename'] ?? '') ? preg_replace('#[^a-zA-Z0-9_\-]#', '_', $recording_name) : $_POST[$greeting_type]['recording_filename'];
if (!str_ends_with($recording_filename, ".$recording_extension")) {
$recording_filename .= ".$recording_extension";
}
//text to audio - make a new audio file from the message
$speech->audio_path = $recording_path;
$speech->audio_filename = $recording_filename;
$speech->audio_voice = $recording_voice;
if ($speed_enabled) {
$speech->audio_speed = (float)$recording_speed;
}
$speech->audio_message = $recording_message;
$speech->speech();
//fix invalid riff & data header lengths in generated wave file
if ($speech_engine == 'openai') {
$recording_filename_temp = str_replace('.'.$recording_extension, '.tmp.'.$recording_extension, $recording_filename);
if (file_exists($recording_path.'/'.$recording_filename)) {
exec('sox --ignore-length '.escapeshellarg($recording_path.'/'.$recording_filename).' '.escapeshellarg($recording_path.'/'.$recording_filename_temp));
}
if (file_exists($recording_path.'/'.$recording_filename_temp)) {
recursive_delete($recording_path.'/'.$recording_filename);
exec('mv '.escapeshellarg($recording_path.'/'.$recording_filename_temp).' '.escapeshellarg($recording_path.'/'.$recording_filename));
}
unset($recording_filename_temp);
}
$array['recordings'][$x]['domain_uuid'] = $domain_uuid;
$array['recordings'][$x]['recording_uuid'] = $recording_uuid;
$array['recordings'][$x]['recording_filename'] = $recording_filename;
$array['recordings'][$x]['recording_name'] = $recording_name;
$array['recordings'][$x]['recording_voice'] = $speech_enabled ? $recording_voice : null;
$array['recordings'][$x]['recording_speed'] = ($speech_enabled && $speed_enabled) ? $recording_speed : null;
$array['recordings'][$x]['recording_message'] = $speech_enabled ? $recording_message : null;
$array['recordings'][$x]['recording_description'] = $recording_desc;
// Update IVR menu fields directly
$array['ivr_menus'][0]["ivr_menu_greet_{$type}"] = $recording_filename;
}
}
//save to the data
$database->save($array);
$message = $database->message;
@@ -858,6 +963,23 @@
echo "</td>\n";
echo "</tr>\n";
// Greet recording tts styles
echo "<style>\n";
echo ".greet_short_tts,\n";
echo ".greet_long_tts {\n";
echo " overflow: hidden;\n";
echo " max-height: 0;\n";
echo " opacity: 0;\n";
echo " transition: max-height 0.3s ease, opacity 0.3s ease;\n";
echo "}\n";
echo ".greet_short_tts.animate-in,\n";
echo ".greet_long_tts.animate-in {\n";
echo " padding: 5px 0;\n";
echo " max-height: 500px;\n";
echo " opacity: 1;\n";
echo "}\n";
echo "</style>\n";
$instance_id = 'ivr_menu_greet_long';
$instance_label = 'greet_long';
$instance_value = $ivr_menu_greet_long;
@@ -869,9 +991,14 @@
echo "</tr>\n";
echo "<tr>\n";
echo "<td class='vtable' align='left'>\n";
echo "<select name='".$instance_id."' id='".$instance_id."' class='formfld' ".(permission_exists('recording_play') || permission_exists('recording_download') ? "onchange=\"recording_reset('".$instance_id."'); set_playable('".$instance_id."', this.value, this.options[this.selectedIndex].parentNode.getAttribute('data-type'));\"" : null).">\n";
echo "<select name='".$instance_id."' id='".$instance_id."' class='formfld' onchange=\"".(permission_exists('recording_play') || permission_exists('recording_download') ? "recording_reset('".$instance_id."'); set_playable('".$instance_id."', this.value, this.options[this.selectedIndex].parentNode.getAttribute('data-type'));" : null)." if (this.options[this.selectedIndex].getAttribute('data-tts') == 'new') {document.querySelectorAll('.".$instance_label."_tts').forEach(el => { el.classList.add('animate-in'); });} else {document.querySelectorAll('.".$instance_label."_tts').forEach(el => { el.classList.remove('animate-in'); });}\">\n";
echo " <option value=''></option>\n";
$found = $playable = false;
if ($speech_enabled && !empty($speech_engine)) {
echo "<optgroup label='".$text['label-text_to_speech']."'>\n";
echo " <option value='' data-tts='new'>".$text['label-new']."</option>\n";
echo "</optgroup>\n";
}
if (!empty($audio_files[0]) && is_array($audio_files[0]) && @sizeof($audio_files[0]) != 0) {
foreach ($audio_files[0] as $key => $value) {
echo "<optgroup label=".$text['label-'.$key]." data-type='".$key."'>\n";
@@ -937,6 +1064,88 @@
unset($playable, $mime_type);
}
echo "<br />\n";
if ($speech_enabled && !empty($speech_engine)) {
echo "<div class='".$instance_label."_tts'>\n";
echo " <strong>".$text['label-recording_name']."</strong><br />\n";
echo " <input class='formfld ' type='text' name='greeting_long[recording_name]' maxlength='255' value=\"".escape($recording_name)."\">\n";
echo " <br /><br />\n";
echo " <div style='display: flex; flex-wrap: wrap; column-gap: 10px; width: 400px;'>\n";
echo " <div style='flex: 1; min-width: 200px;'>\n";
echo " <strong>".$text['label-voice']."</strong>\n";
echo " </div>\n";
if ($speed_enabled) {
echo " <div style='flex: 1; min-width: 120px;'>\n";
echo " <strong>".$text['label-speed']."</strong>\n";
echo " </div>\n";
}
// Voice
echo " <div style='flex: 1; min-width: 200px;'>\n";
if (!empty($voices)) {
if ($voices_array_type == 'single') {
echo " <select class='formfld' name='greeting_long[recording_voice]' style='width: 100%;'>\n";
echo " <option value=''></option>\n";
foreach ($voices as $key => $voice) {
$recording_voice_selected = (!empty($recording_voice) && $key == $recording_voice) ? "selected='selected'" : null;
echo " <option value='".escape($key)."' $recording_voice_selected>".escape(ucwords($voice))."</option>\n";
}
echo " </select>\n";
}
if ($voices_array_type == 'multi') {
echo " <select class='formfld' id='recording_voice_source' name='greeting_long[recording_voice_source]' style='display: none;'>\n";
echo " <option value=''></option>\n";
foreach ($voices as $category => $sub_array) {
$category = $text['label-'.$category] ?? $category;
echo "<optgroup label='".$category."' data-type='".$category."'>\n";
foreach ($sub_array as $key => $voice) {
$recording_voice_selected = (!empty($recording_voice) && $key == $recording_voice) ? "selected='selected'" : null;
echo " <option value='".escape($key)."' $recording_voice_selected>".escape(ucwords($voice))."</option>\n";
}
echo "</optgroup>\n";
}
echo " </select>\n";
echo " <select class='formfld' id='recording_voice_group_select' style='width: 100%; margin-bottom: 5px;'>\n";
echo " <option value='' disabled='disabled' selected='selected'></option>\n";
echo " </select>\n";
echo " <select class='formfld' id='recording_voice_option_select' name='greeting_long[recording_voice]' style='width: 100%;' disabled='disabled'>\n";
echo " <option value='' disabled='disabled' selected='selected'></option>\n";
echo " </select>\n";
echo "<script>\n";
echo " select_group_option('recording_voice_source', 'recording_voice_group_select', 'recording_voice_option_select');\n";
echo "</script>\n";
}
} else {
echo " <input class='formfld' type='text' name='greeting_long[recording_voice]' maxlength='255' value=''>\n";
}
echo " </div>\n";
// Speed
if ($speed_enabled) {
echo " <div style='flex: 1; min-width: 120px;'>\n";
echo " <select class='formfld' name='greeting_long[recording_speed]' style='width: 100%;'>\n";
foreach ($speed_options as $speed_value => $speed_label) {
$selected = (string)$recording_speed === $speed_value ? "selected='selected'" : '';
echo " <option value='".escape($speed_value)."' $selected>".escape($speed_label)."</option>\n";
}
echo " </select>\n";
echo " <br />\n";
echo " </div>\n";
}
echo " </div>\n";
echo " <br />\n";
echo " <strong>".$text['label-message']."</strong><br />\n";
echo " <textarea class='formfld' name='greeting_long[recording_message]' style='width: 300px; height: 150px;'></textarea>\n";
echo "</div>\n";
}
echo $text['description-'.$instance_label]."\n";
echo "</td>\n";
echo "</tr>\n";
@@ -952,8 +1161,13 @@
echo "</tr>\n";
echo "<tr>\n";
echo "<td class='vtable' align='left'>\n";
echo "<select name='".$instance_id."' id='".$instance_id."' class='formfld' ".(permission_exists('recording_play') || permission_exists('recording_download') ? "onchange=\"recording_reset('".$instance_id."'); set_playable('".$instance_id."', this.value, this.options[this.selectedIndex].parentNode.getAttribute('data-type'));\"" : null).">\n";
echo "<select name='".$instance_id."' id='".$instance_id."' class='formfld' onchange=\"".(permission_exists('recording_play') || permission_exists('recording_download') ? "recording_reset('".$instance_id."'); set_playable('".$instance_id."', this.value, this.options[this.selectedIndex].parentNode.getAttribute('data-type'));" : null)." if (this.options[this.selectedIndex].getAttribute('data-tts') == 'new') {document.querySelectorAll('.".$instance_label."_tts').forEach(el => { el.classList.add('animate-in'); });} else {document.querySelectorAll('.".$instance_label."_tts').forEach(el => { el.classList.remove('animate-in'); });}\">\n";
echo " <option value=''></option>\n";
if ($speech_enabled && !empty($speech_engine)) {
echo "<optgroup label='".($text['label-text_to_speech'] ?? 'Text to Speech')."'>\n";
echo " <option value='' data-tts='new'>".escape($text['label-new'] ?? 'New')."</option>\n";
echo "</optgroup>\n";
}
$found = $playable = false;
if (!empty($audio_files[0]) && is_array($audio_files[0]) && @sizeof($audio_files[0]) != 0) {
foreach ($audio_files[0] as $key => $value) {
@@ -1020,6 +1234,88 @@
unset($playable, $mime_type);
}
echo "<br />\n";
if ($speech_enabled && !empty($speech_engine)) {
echo "<div class='".$instance_label."_tts'>\n";
echo " <strong>".$text['label-recording_name']."</strong><br />\n";
echo " <input class='formfld' type='text' name='greeting_short[recording_name]' maxlength='255' value=\"".escape($recording_name)."\">\n";
echo " <br /><br />\n";
echo " <div style='display: flex; flex-wrap: wrap; column-gap: 10px; width: 400px;'>\n";
echo " <div style='flex: 1; min-width: 200px;'>\n";
echo " <strong>".$text['label-voice']."</strong>\n";
echo " </div>\n";
if ($speed_enabled) {
echo " <div style='flex: 1; min-width: 120px;'>\n";
echo " <strong>".$text['label-speed']."</strong>\n";
echo " </div>\n";
}
// Voice
echo " <div style='flex: 1; min-width: 200px;'>\n";
if (!empty($voices)) {
if ($voices_array_type == 'single') {
echo " <select class='formfld' name='greeting_short[recording_voice]' style='width: 100%;'>\n";
echo " <option value=''></option>\n";
foreach ($voices as $key => $voice) {
$recording_voice_selected = (!empty($recording_voice) && $key == $recording_voice) ? "selected='selected'" : null;
echo " <option value='".escape($key)."' $recording_voice_selected>".escape(ucwords($voice))."</option>\n";
}
echo " </select>\n";
}
if ($voices_array_type == 'multi') {
echo " <select class='formfld' id='recording_voice_source' name='greeting_short[recording_voice_source]' style='display: none;'>\n";
echo " <option value=''></option>\n";
foreach ($voices as $category => $sub_array) {
$category = $text['label-'.$category] ?? $category;
echo "<optgroup label='".$category."' data-type='".$category."'>\n";
foreach ($sub_array as $key => $voice) {
$recording_voice_selected = (!empty($recording_voice) && $key == $recording_voice) ? "selected='selected'" : null;
echo " <option value='".escape($key)."' $recording_voice_selected>".escape(ucwords($voice))."</option>\n";
}
echo "</optgroup>\n";
}
echo " </select>\n";
echo " <select class='formfld' id='recording_voice_group_select' style='width: 100%; margin-bottom: 5px;'>\n";
echo " <option value='' disabled='disabled' selected='selected'></option>\n";
echo " </select>\n";
echo " <select class='formfld' id='recording_voice_option_select' name='greeting_short[recording_voice]' style='width: 100%;' disabled='disabled'>\n";
echo " <option value='' disabled='disabled' selected='selected'></option>\n";
echo " </select>\n";
echo "<script>\n";
echo " select_group_option('recording_voice_source', 'recording_voice_group_select', 'recording_voice_option_select');\n";
echo "</script>\n";
}
} else {
echo " <input class='formfld' type='text' name='greeting_short[recording_voice]' maxlength='255' value=\"".escape($recording_voice)."\">\n";
}
echo " </div>\n";
// Speed
if ($speed_enabled) {
echo " <div style='flex: 1; min-width: 120px;'>\n";
echo " <select class='formfld' name='greeting_short[recording_speed]' style='width: 100%;'>\n";
foreach ($speed_options as $speed_value => $speed_label) {
$selected = (string)$recording_speed === $speed_value ? "selected='selected'" : '';
echo " <option value='".escape($speed_value)."' $selected>".escape($speed_label)."</option>\n";
}
echo " </select>\n";
echo " <br />\n";
echo " </div>\n";
}
echo " </div>\n";
echo " <br />\n";
echo " <strong>".$text['label-message']."</strong><br />\n";
echo " <textarea class='formfld' name='greeting_short[recording_message]' style='width: 300px; height: 150px;'></textarea>\n";
echo "</div>\n";
}
echo $text['description-'.$instance_label]."\n";
echo "</td>\n";
echo "</tr>\n";