/* * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * Copyright (C) 2005-2014, Anthony Minessale II * * 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 FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * * The Initial Developer of the Original Code is * Anthony Minessale II * Portions created by the Initial Developer are Copyright (C) * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Anthony Minessale II * Neal Horman * * switch_ivr_menu.c -- IVR Library (menu code) * */ #include struct switch_ivr_menu_action; struct switch_ivr_menu { char *name; char *greeting_sound; char *short_greeting_sound; char *invalid_sound; char *exit_sound; char *transfer_sound; char *buf; char *ptr; char *confirm_macro; char *confirm_key; char *tts_engine; char *tts_voice; int confirm_attempts; int digit_len; int max_failures; int max_timeouts; int timeout; int inter_timeout; char *exec_on_max_fail; char *exec_on_max_timeout; switch_size_t inlen; uint32_t flags; struct switch_ivr_menu_action *actions; struct switch_ivr_menu *next; switch_memory_pool_t *pool; int stack_count; char *pin; char *prompt_pin_file; char *bad_pin_file; }; struct switch_ivr_menu_action { switch_ivr_menu_action_function_t *function; switch_ivr_action_t ivr_action; char *arg; char *bind; int re; struct switch_ivr_menu_action *next; }; #define MENU_EVENT_ENTER "menu::enter" #define MENU_EVENT_EXIT "menu::exit" static void ivr_send_event(switch_core_session_t *session, char *event_type, switch_ivr_menu_t *menu) { switch_channel_t *channel = switch_core_session_get_channel(session); switch_event_t *event = NULL; if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, event_type) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Menu-Name", menu->name); switch_channel_event_set_data(channel, event); switch_event_fire(&event); } } static switch_ivr_menu_t *switch_ivr_menu_find(switch_ivr_menu_t *stack, const char *name) { switch_ivr_menu_t *ret; for (ret = stack; ret; ret = ret->next) { if (!name || !strcmp(ret->name, name)) break; } return ret; } static void switch_ivr_menu_stack_add(switch_ivr_menu_t ** top, switch_ivr_menu_t *bottom) { switch_ivr_menu_t *ptr; for (ptr = *top; ptr && ptr->next; ptr = ptr->next); if (ptr) { ptr->next = bottom; } else { *top = bottom; } } SWITCH_DECLARE(switch_status_t) switch_ivr_menu_init(switch_ivr_menu_t ** new_menu, switch_ivr_menu_t *main, const char *name, const char *greeting_sound, const char *short_greeting_sound, const char *invalid_sound, const char *exit_sound, const char *transfer_sound, const char *confirm_macro, const char *confirm_key, const char *tts_engine, const char *tts_voice, int confirm_attempts, int inter_timeout, int digit_len, int timeout, int max_failures, int max_timeouts, switch_memory_pool_t *pool) { switch_ivr_menu_t *menu; uint8_t newpool = 0; if (!pool) { if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "OH OH no pool\n"); return SWITCH_STATUS_MEMERR; } newpool = 1; } if (!(menu = switch_core_alloc(pool, sizeof(*menu)))) { if (newpool) { switch_core_destroy_memory_pool(&pool); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memory Error!\n"); return SWITCH_STATUS_MEMERR; } } menu->pool = pool; if (!confirm_attempts) { confirm_attempts = 3; } if (!inter_timeout) { inter_timeout = timeout / 2; } if (!zstr(name)) { menu->name = switch_core_strdup(menu->pool, name); } if (!zstr(greeting_sound)) { menu->greeting_sound = switch_core_strdup(menu->pool, greeting_sound); } if (!zstr(short_greeting_sound)) { menu->short_greeting_sound = switch_core_strdup(menu->pool, short_greeting_sound); } if (!zstr(invalid_sound)) { menu->invalid_sound = switch_core_strdup(menu->pool, invalid_sound); } if (!zstr(transfer_sound)) { menu->transfer_sound = switch_core_strdup(menu->pool, transfer_sound); } if (!zstr(exit_sound)) { menu->exit_sound = switch_core_strdup(menu->pool, exit_sound); } if (!zstr(confirm_key)) { menu->confirm_key = switch_core_strdup(menu->pool, confirm_key); } if (!zstr(confirm_macro)) { menu->confirm_macro = switch_core_strdup(menu->pool, confirm_macro); } if (!zstr(tts_engine)) { menu->tts_engine = switch_core_strdup(menu->pool, tts_engine); } if (!zstr(tts_voice)) { menu->tts_voice = switch_core_strdup(menu->pool, tts_voice); } menu->confirm_attempts = confirm_attempts; menu->inlen = digit_len; if (max_failures > 0) { menu->max_failures = max_failures; } else { menu->max_failures = 3; } if (max_timeouts > 0) { menu->max_timeouts = max_timeouts; } else { menu->max_timeouts = 3; } menu->timeout = timeout; menu->inter_timeout = inter_timeout; menu->actions = NULL; if (newpool) { switch_set_flag(menu, SWITCH_IVR_MENU_FLAG_FREEPOOL); } if (menu->timeout <= 0) { menu->timeout = 10000; } if (main) { switch_ivr_menu_stack_add(&main, menu); } else { switch_set_flag(menu, SWITCH_IVR_MENU_FLAG_STACK); } menu->buf = switch_core_alloc(menu->pool, 1024); *new_menu = menu; return SWITCH_STATUS_SUCCESS; } SWITCH_DECLARE(switch_status_t) switch_ivr_menu_bind_action(switch_ivr_menu_t *menu, switch_ivr_action_t ivr_action, const char *arg, const char *bind) { switch_ivr_menu_action_t *action, *ap; uint32_t len; if ((action = switch_core_alloc(menu->pool, sizeof(*action)))) { action->bind = switch_core_strdup(menu->pool, bind); action->arg = switch_core_strdup(menu->pool, arg); if (*action->bind == '/') { action->re = 1; } else { len = (uint32_t) strlen(action->bind); if (len > menu->inlen) { menu->inlen = len; } } action->ivr_action = ivr_action; if (menu->actions) { for(ap = menu->actions; ap && ap->next; ap = ap->next); ap->next = action; } else { menu->actions = action; } return SWITCH_STATUS_SUCCESS; } return SWITCH_STATUS_MEMERR; } SWITCH_DECLARE(switch_status_t) switch_ivr_menu_bind_function(switch_ivr_menu_t *menu, switch_ivr_menu_action_function_t *function, const char *arg, const char *bind) { switch_ivr_menu_action_t *action, *ap; uint32_t len; if ((action = switch_core_alloc(menu->pool, sizeof(*action)))) { action->bind = switch_core_strdup(menu->pool, bind); action->arg = switch_core_strdup(menu->pool, arg); if (*action->bind == '/') { action->re = 1; } else { len = (uint32_t) strlen(action->bind); if (len > menu->inlen) { menu->inlen = len; } } action->function = function; if (menu->actions) { for(ap = menu->actions; ap && ap->next; ap = ap->next); ap->next = action; } else { menu->actions = action; } return SWITCH_STATUS_SUCCESS; } return SWITCH_STATUS_MEMERR; } SWITCH_DECLARE(switch_status_t) switch_ivr_menu_stack_free(switch_ivr_menu_t *stack) { switch_status_t status = SWITCH_STATUS_FALSE; if (stack != NULL && stack->pool != NULL) { if (switch_test_flag(stack, SWITCH_IVR_MENU_FLAG_STACK) && switch_test_flag(stack, SWITCH_IVR_MENU_FLAG_FREEPOOL)) { switch_memory_pool_t *pool = stack->pool; status = switch_core_destroy_memory_pool(&pool); } else { status = SWITCH_STATUS_SUCCESS; } } return status; } static switch_status_t play_and_collect(switch_core_session_t *session, switch_ivr_menu_t *menu, char *sound, switch_size_t need) { char terminator; uint32_t len; char *ptr; switch_status_t status = SWITCH_STATUS_FALSE; switch_input_args_t args = { 0 }; switch_channel_t *channel; char *sound_expanded = sound; switch_size_t menu_buf_len = 0; const char *terminator_str = "#"; if (!session || !menu || zstr(sound)) { return status; } if ((channel = switch_core_session_get_channel(session))) { const char *tmp; sound_expanded = switch_channel_expand_variables(channel, sound); if ((tmp = switch_channel_get_variable(channel, "ivr_menu_terminator")) && !zstr(tmp)) { terminator_str = tmp; } } memset(menu->buf, 0, menu->inlen + 1); menu->ptr = menu->buf; if (!need) { len = 1; ptr = NULL; } else { len = (uint32_t) menu->inlen + 1; ptr = menu->ptr; } args.buf = ptr; args.buflen = len; status = switch_ivr_play_file(session, NULL, sound_expanded, &args); if (sound_expanded != sound) { switch_safe_free(sound_expanded); } if (!need) { return status; } menu_buf_len = strlen(menu->buf); menu->ptr += menu_buf_len; if (menu_buf_len < need) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "waiting for %u/%u digits t/o %d\n", (uint32_t) (menu->inlen - strlen(menu->buf)), (uint32_t) need, menu->inter_timeout); status = switch_ivr_collect_digits_count(session, menu->ptr, menu->inlen - strlen(menu->buf), need, terminator_str, &terminator, menu_buf_len ? menu->inter_timeout : menu->timeout, menu->inter_timeout, menu->timeout); } if (menu->confirm_macro && status == SWITCH_STATUS_SUCCESS && *menu->buf != '\0') { switch_input_args_t confirm_args = { 0 }, *ap = NULL; char buf[10] = ""; char terminator_key; int att = menu->confirm_attempts; while (att) { confirm_args.buf = buf; confirm_args.buflen = sizeof(buf); memset(buf, 0, confirm_args.buflen); if (menu->confirm_key) { ap = &confirm_args; } switch_ivr_phrase_macro(session, menu->confirm_macro, menu->buf, NULL, ap); if (menu->confirm_key && *buf == '\0') { switch_ivr_collect_digits_count(session, buf, sizeof(buf), 1, terminator_str, &terminator_key, menu->timeout, 0, 0); } if (menu->confirm_key && *buf != '\0') { if (*menu->confirm_key == *buf) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "approving digits '%s' via confirm key %s\n", menu->buf, menu->confirm_key); break; } else { att = 0; break; } } att--; } if (!att) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "rejecting digits '%s' via confirm key %s\n", menu->buf, menu->confirm_key); *menu->buf = '\0'; } } switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "digits '%s'\n", menu->buf); return status; } static void exec_app(switch_core_session_t *session, char *app_str) { switch_channel_t *channel = switch_core_session_get_channel(session); char *app = switch_core_session_strdup(session, app_str); char *data = strchr(app, ' '); char *expanded = NULL; if (data) { *data++ = '\0'; } expanded = switch_channel_expand_variables(channel, data); switch_core_session_execute_application(session, app, expanded); if (expanded && expanded != data) { free(expanded); } } SWITCH_DECLARE(switch_status_t) switch_ivr_menu_execute(switch_core_session_t *session, switch_ivr_menu_t *stack, char *name, void *obj) { int reps = 0, errs = 0, timeouts = 0, match = 0, running = 1; char *greeting_sound = NULL, *aptr = NULL; char arg[512]; switch_ivr_action_t todo = SWITCH_IVR_ACTION_DIE; switch_ivr_menu_action_t *ap; switch_ivr_menu_t *menu = NULL; switch_channel_t *channel; switch_status_t status = SWITCH_STATUS_SUCCESS; if (++stack->stack_count > 12) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Too many levels of recursion.\n"); switch_goto_status(SWITCH_STATUS_FALSE, end); } if (!session || !stack || zstr(name)) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Invalid menu context\n"); switch_goto_status(SWITCH_STATUS_FALSE, end); } channel = switch_core_session_get_channel(session); if (!(menu = switch_ivr_menu_find(stack, name))) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Invalid Menu!\n"); switch_goto_status(SWITCH_STATUS_FALSE, end); } if (!zstr(menu->tts_engine) && !zstr(menu->tts_voice)) { switch_channel_set_variable(channel, "tts_engine", menu->tts_engine); switch_channel_set_variable(channel, "tts_voice", menu->tts_voice); } switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Executing IVR menu %s\n", menu->name); switch_channel_set_variable(channel, "ivr_menu_status", "success"); ivr_send_event(session, MENU_EVENT_ENTER, menu); if (!zstr(menu->pin)) { char digit_buffer[128] = ""; char *digits_regex = switch_core_session_sprintf(session, "^%s$", menu->pin); if (switch_play_and_get_digits(session, (uint32_t)strlen(menu->pin), (uint32_t)strlen(menu->pin), 3, 3000, "#", menu->prompt_pin_file, menu->bad_pin_file, NULL, digit_buffer, sizeof(digit_buffer), digits_regex, 10000, NULL) != SWITCH_STATUS_SUCCESS) { switch_goto_status(SWITCH_STATUS_FALSE, end); } } for (reps = 0; running && status == SWITCH_STATUS_SUCCESS; reps++) { if (!switch_channel_ready(channel)) { break; } if (errs == menu->max_failures) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Maximum failures\n"); switch_channel_set_variable(channel, "ivr_menu_status", "failure"); if (!zstr(menu->exec_on_max_fail)) { exec_app(session, menu->exec_on_max_fail); } break; } if (timeouts == menu->max_timeouts) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Maximum timeouts\n"); switch_channel_set_variable(channel, "ivr_menu_status", "timeout"); if (!zstr(menu->exec_on_max_timeout)) { exec_app(session, menu->exec_on_max_timeout); } break; } if (reps > 0 && menu->short_greeting_sound) { greeting_sound = menu->short_greeting_sound; } else { greeting_sound = menu->greeting_sound; } match = 0; aptr = NULL; memset(arg, 0, sizeof(arg)); memset(menu->buf, 0, menu->inlen + 1); if (play_and_collect(session, menu, greeting_sound, menu->inlen) == SWITCH_STATUS_TIMEOUT && *menu->buf == '\0') { timeouts++; continue; } if (*menu->buf != '\0') { for (ap = menu->actions; ap; ap = ap->next) { int ok = 0; char substituted[1024]; char *use_arg = ap->arg; if (!zstr(menu->tts_engine) && !zstr(menu->tts_voice)) { switch_channel_set_variable(channel, "tts_engine", menu->tts_engine); switch_channel_set_variable(channel, "tts_voice", menu->tts_voice); } if (ap->re) { switch_regex_t *re = NULL; int ovector[30]; if ((ok = switch_regex_perform(menu->buf, ap->bind, &re, ovector, sizeof(ovector) / sizeof(ovector[0])))) { switch_perform_substitution(re, ok, ap->arg, menu->buf, substituted, sizeof(substituted), ovector); use_arg = substituted; } switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "action regex [%s] [%s] [%d]\n", menu->buf, ap->bind, ok); switch_regex_safe_free(re); } else { ok = !strcmp(menu->buf, ap->bind); } if (ok) { match++; errs = 0; if (ap->function) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "IVR function on menu '%s' matched '%s' param '%s'\n", menu->name, menu->buf, use_arg); todo = ap->function(menu, use_arg, arg, sizeof(arg), obj); aptr = arg; } else { todo = ap->ivr_action; aptr = use_arg; switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "IVR action on menu '%s' matched '%s' param '%s'\n", menu->name, menu->buf, aptr); } switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "switch_ivr_menu_execute todo=[%d]\n", todo); switch (todo) { case SWITCH_IVR_ACTION_DIE: status = SWITCH_STATUS_FALSE; break; case SWITCH_IVR_ACTION_PLAYSOUND: status = switch_ivr_play_file(session, NULL, aptr, NULL); break; case SWITCH_IVR_ACTION_EXECMENU: if (!strcmp(aptr, menu->name)) { status = SWITCH_STATUS_SUCCESS; } else { reps = -1; ivr_send_event(session, MENU_EVENT_EXIT, menu); status = switch_ivr_menu_execute(session, stack, aptr, obj); ivr_send_event(session, MENU_EVENT_ENTER, menu); } break; case SWITCH_IVR_ACTION_EXECAPP: { switch_application_interface_t *application_interface; char *app_name; char *app_arg = NULL; status = SWITCH_STATUS_FALSE; if (!zstr(aptr)) { app_name = switch_core_session_strdup(session, aptr); if ((app_arg = strchr(app_name, ' '))) { *app_arg++ = '\0'; } if ((application_interface = switch_loadable_module_get_application_interface(app_name))) { if (!zstr(menu->transfer_sound) && !strcmp(app_name, "transfer")) { status = play_and_collect(session, menu, menu->transfer_sound, 0); } switch_core_session_exec(session, application_interface, app_arg); UNPROTECT_INTERFACE(application_interface); status = SWITCH_STATUS_SUCCESS; } } } break; case SWITCH_IVR_ACTION_BACK: running = 0; status = SWITCH_STATUS_SUCCESS; break; case SWITCH_IVR_ACTION_TOMAIN: switch_set_flag(stack, SWITCH_IVR_MENU_FLAG_FALLTOMAIN); status = SWITCH_STATUS_BREAK; break; case SWITCH_IVR_ACTION_NOOP: status = SWITCH_STATUS_SUCCESS; break; default: switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Invalid TODO!\n"); break; } } } if (switch_test_flag(menu, SWITCH_IVR_MENU_FLAG_STACK)) { /* top level */ if (switch_test_flag(stack, SWITCH_IVR_MENU_FLAG_FALLTOMAIN)) { /* catch the fallback and recover */ switch_clear_flag(stack, SWITCH_IVR_MENU_FLAG_FALLTOMAIN); status = SWITCH_STATUS_SUCCESS; running = 1; continue; } } } if (!match) { if (*menu->buf) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "IVR menu '%s' caught invalid input '%s'\n", menu->name, menu->buf); if (menu->invalid_sound) { play_and_collect(session, menu, menu->invalid_sound, 0); } } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "IVR menu '%s' no input detected\n", menu->name); } errs++; /* breaks are ok too */ if (SWITCH_STATUS_IS_BREAK(status)) { status = SWITCH_STATUS_SUCCESS; } } } if (stack->stack_count == 1) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "exit-sound '%s'\n", menu->exit_sound); if (!zstr(menu->exit_sound)) { status = play_and_collect(session, menu, menu->exit_sound, 0); } } end: stack->stack_count--; if (menu) { ivr_send_event(session, MENU_EVENT_EXIT, menu); } return status; } /******************************************************************************************************/ typedef struct switch_ivr_menu_xml_map { char *name; switch_ivr_action_t action; switch_ivr_menu_action_function_t *function; struct switch_ivr_menu_xml_map *next; } switch_ivr_menu_xml_map_t; struct switch_ivr_menu_xml_ctx { switch_memory_pool_t *pool; struct switch_ivr_menu_xml_map *map; int autocreated; }; static switch_ivr_menu_xml_map_t *switch_ivr_menu_stack_xml_find(switch_ivr_menu_xml_ctx_t *xml_ctx, const char *name) { switch_ivr_menu_xml_map_t *map = (xml_ctx != NULL ? xml_ctx->map : NULL); int rc = -1; while (map != NULL && (rc = strcasecmp(map->name, name)) != 0) { map = map->next; } return (rc == 0 ? map : NULL); } static switch_status_t switch_ivr_menu_stack_xml_add(switch_ivr_menu_xml_ctx_t *xml_ctx, const char *name, int action, switch_ivr_menu_action_function_t *function) { switch_status_t status = SWITCH_STATUS_FALSE; /* if this action/function does not exist yet */ if (xml_ctx != NULL && name != NULL && xml_ctx->pool != NULL && switch_ivr_menu_stack_xml_find(xml_ctx, name) == NULL) { switch_ivr_menu_xml_map_t *map = switch_core_alloc(xml_ctx->pool, sizeof(switch_ivr_menu_xml_map_t)); if (map != NULL) { map->name = switch_core_strdup(xml_ctx->pool, name); map->action = action; map->function = function; if (map->name != NULL) { /* insert map item at top of list */ map->next = xml_ctx->map; xml_ctx->map = map; status = SWITCH_STATUS_SUCCESS; } else { status = SWITCH_STATUS_MEMERR; } } else { status = SWITCH_STATUS_MEMERR; } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "switch_ivr_menu_stack_xml_add binding '%s'\n", name); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to add binding %s\n", name); } return status; } static struct iam_s { const char *name; switch_ivr_action_t action; } iam[] = { { "menu-exit", SWITCH_IVR_ACTION_DIE}, { "menu-sub", SWITCH_IVR_ACTION_EXECMENU}, { "menu-exec-app", SWITCH_IVR_ACTION_EXECAPP}, { "menu-play-sound", SWITCH_IVR_ACTION_PLAYSOUND}, { "menu-back", SWITCH_IVR_ACTION_BACK}, { "menu-top", SWITCH_IVR_ACTION_TOMAIN}, { NULL, 0} }; SWITCH_DECLARE(switch_status_t) switch_ivr_menu_str2action(const char *action_name, switch_ivr_action_t *action) { int i; if (!zstr(action_name)) { for (i = 0;; i++) { if (!iam[i].name) { break; } if (!strcasecmp(iam[i].name, action_name)) { *action = iam[i].action; return SWITCH_STATUS_SUCCESS; } } } return SWITCH_STATUS_FALSE; } static switch_bool_t is_valid_action(const char *action) { int i; if (!zstr(action)) { for (i = 0;; i++) { if (!iam[i].name) { break; } if (!strcmp(iam[i].name, action)) { return SWITCH_TRUE; } } } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid Action [%s]\n", switch_str_nil(action)); return SWITCH_FALSE; } SWITCH_DECLARE(switch_status_t) switch_ivr_menu_stack_xml_init(switch_ivr_menu_xml_ctx_t ** xml_menu_ctx, switch_memory_pool_t *pool) { switch_status_t status = SWITCH_STATUS_FALSE; int autocreated = 0; /* build a memory pool ? */ if (pool == NULL) { status = switch_core_new_memory_pool(&pool); autocreated = 1; } /* allocate the xml context */ if (xml_menu_ctx != NULL && pool != NULL) { *xml_menu_ctx = switch_core_alloc(pool, sizeof(switch_ivr_menu_xml_ctx_t)); if (*xml_menu_ctx != NULL) { (*xml_menu_ctx)->pool = pool; (*xml_menu_ctx)->autocreated = autocreated; (*xml_menu_ctx)->map = NULL; status = SWITCH_STATUS_SUCCESS; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to alloc xml_ctx\n"); status = SWITCH_STATUS_FALSE; } } /* build the standard/default xml menu handler mappings */ if (status == SWITCH_STATUS_SUCCESS && xml_menu_ctx != NULL && *xml_menu_ctx != NULL) { int i; for (i = 0; iam[i].name && status == SWITCH_STATUS_SUCCESS; i++) { status = switch_ivr_menu_stack_xml_add(*xml_menu_ctx, iam[i].name, iam[i].action, NULL); } } return status; } SWITCH_DECLARE(switch_status_t) switch_ivr_menu_stack_xml_add_custom(switch_ivr_menu_xml_ctx_t *xml_menu_ctx, const char *name, switch_ivr_menu_action_function_t *function) { return switch_ivr_menu_stack_xml_add(xml_menu_ctx, name, -1, function); } SWITCH_DECLARE(switch_status_t) switch_ivr_menu_stack_xml_build(switch_ivr_menu_xml_ctx_t *xml_menu_ctx, switch_ivr_menu_t ** menu_stack, switch_xml_t xml_menus, switch_xml_t xml_menu) { switch_status_t status = SWITCH_STATUS_FALSE; if (xml_menu_ctx != NULL && menu_stack != NULL && xml_menu != NULL) { const char *menu_name = switch_xml_attr_soft(xml_menu, "name"); /* if the attr doesn't exist, return "" */ const char *greet_long = switch_xml_attr(xml_menu, "greet-long"); /* if the attr doesn't exist, return NULL */ const char *greet_short = switch_xml_attr(xml_menu, "greet-short"); /* if the attr doesn't exist, return NULL */ const char *invalid_sound = switch_xml_attr(xml_menu, "invalid-sound"); /* if the attr doesn't exist, return NULL */ const char *exit_sound = switch_xml_attr(xml_menu, "exit-sound"); /* if the attr doesn't exist, return NULL */ const char *transfer_sound = switch_xml_attr(xml_menu, "transfer-sound"); /* if the attr doesn't exist, return NULL */ const char *timeout = switch_xml_attr_soft(xml_menu, "timeout"); /* if the attr doesn't exist, return "" */ const char *max_failures = switch_xml_attr_soft(xml_menu, "max-failures"); /* if the attr doesn't exist, return "" */ const char *max_timeouts = switch_xml_attr_soft(xml_menu, "max-timeouts"); const char *exec_on_max_fail = switch_xml_attr(xml_menu, "exec-on-max-failures"); const char *exec_on_max_timeout = switch_xml_attr(xml_menu, "exec-on-max-timeouts"); const char *confirm_macro = switch_xml_attr(xml_menu, "confirm-macro"); const char *confirm_key = switch_xml_attr(xml_menu, "confirm-key"); const char *tts_engine = switch_xml_attr(xml_menu, "tts-engine"); const char *tts_voice = switch_xml_attr(xml_menu, "tts-voice"); const char *confirm_attempts = switch_xml_attr_soft(xml_menu, "confirm-attempts"); const char *digit_len = switch_xml_attr_soft(xml_menu, "digit-len"); const char *inter_timeout = switch_xml_attr_soft(xml_menu, "inter-digit-timeout"); const char *pin = switch_xml_attr_soft(xml_menu, "pin"); const char *prompt_pin_file = switch_xml_attr_soft(xml_menu, "pin-file"); const char *bad_pin_file = switch_xml_attr_soft(xml_menu, "bad-pin-file"); switch_ivr_menu_t *menu = NULL; if (zstr(max_timeouts)) { max_timeouts = max_failures; } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "building menu '%s'\n", menu_name); status = switch_ivr_menu_init(&menu, *menu_stack, menu_name, greet_long, greet_short, invalid_sound, exit_sound, transfer_sound, confirm_macro, confirm_key, tts_engine, tts_voice, atoi(confirm_attempts), atoi(inter_timeout), atoi(digit_len), atoi(timeout), strlen(max_failures) ? atoi(max_failures) : 0, strlen(max_timeouts) ? atoi(max_timeouts) : 0, xml_menu_ctx->pool); if (!zstr(exec_on_max_fail)) { menu->exec_on_max_fail = switch_core_strdup(menu->pool, exec_on_max_fail); } if (!zstr(exec_on_max_timeout)) { menu->exec_on_max_timeout = switch_core_strdup(menu->pool, exec_on_max_timeout); } if (!zstr(pin)) { if (zstr(prompt_pin_file)) { prompt_pin_file = "ivr/ivr-please_enter_pin_followed_by_pound.wav"; } if (zstr(bad_pin_file)) { bad_pin_file = "conference/conf-bad-pin.wav"; } menu->pin = switch_core_strdup(menu->pool, pin); menu->prompt_pin_file = switch_core_strdup(menu->pool, prompt_pin_file); menu->bad_pin_file = switch_core_strdup(menu->pool, bad_pin_file); } /* set the menu_stack for the caller */ if (status == SWITCH_STATUS_SUCCESS && *menu_stack == NULL) { *menu_stack = menu; if (xml_menu_ctx->autocreated) { switch_set_flag(menu, SWITCH_IVR_MENU_FLAG_FREEPOOL); } } if (status == SWITCH_STATUS_SUCCESS && menu != NULL) { switch_xml_t xml_kvp; /* build menu entries */ for (xml_kvp = switch_xml_child(xml_menu, "entry"); xml_kvp != NULL && status == SWITCH_STATUS_SUCCESS; xml_kvp = xml_kvp->next) { const char *action = switch_xml_attr(xml_kvp, "action"); const char *digits = switch_xml_attr(xml_kvp, "digits"); const char *param = switch_xml_attr_soft(xml_kvp, "param"); if (is_valid_action(action) && !zstr(digits)) { switch_ivr_menu_xml_map_t *xml_map = xml_menu_ctx->map; int found = 0; /* find and appropriate xml handler */ while (xml_map != NULL && !found) { if (!(found = (strcasecmp(xml_map->name, action) == 0))) { xml_map = xml_map->next; } } if (found && xml_map != NULL) { /* do we need to build a new sub-menu ? */ if (xml_map->action == SWITCH_IVR_ACTION_EXECMENU && switch_ivr_menu_find(*menu_stack, param) == NULL) { if ((xml_menu = switch_xml_find_child(xml_menus, "menu", "name", param)) != NULL) { status = switch_ivr_menu_stack_xml_build(xml_menu_ctx, menu_stack, xml_menus, xml_menu); } } /* finally bind the menu entry */ if (status == SWITCH_STATUS_SUCCESS) { if (xml_map->function != NULL) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "binding menu caller control '%s'/'%s' to '%s'\n", xml_map->name, param, digits); status = switch_ivr_menu_bind_function(menu, xml_map->function, param, digits); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "binding menu action '%s' to '%s'\n", xml_map->name, digits); status = switch_ivr_menu_bind_action(menu, xml_map->action, param, digits); } } } } else { status = SWITCH_STATUS_FALSE; } } } } if (status != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to build xml menu\n"); } return status; } /* For Emacs: * Local Variables: * mode:c * indent-tabs-mode:t * tab-width:4 * c-basic-offset:4 * End: * For VIM: * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet: */