Index: freepbx/tags/2.4.0beta1/amp_conf/htdocs/admin/functions.inc.php =================================================================== --- freepbx/tags/2.4.0beta1/amp_conf/htdocs/admin/functions.inc.php (revision 5477) +++ freepbx/tags/2.4.0beta1/amp_conf/htdocs/admin/functions.inc.php (revision 5477) @@ -0,0 +1,3603 @@ + array('std' , 'mysql'), + 'AMPDBNAME' => array('std' , 'asterisk'), + 'AMPENGINE' => array('std' , 'asterisk'), + 'ASTMANAGERPORT' => array('std' , '5038'), + 'AMPDBHOST' => array('std' , 'localhost'), + 'AMPDBUSER' => array('std' , 'asteriskuser'), + 'AMPDBPASS' => array('std' , 'amp109'), + 'AMPMGRUSER' => array('std' , 'admin'), + 'AMPMGRPASS' => array('std' , 'amp111'), + 'FOPPASSWORD' => array('std' , 'passw0rd'), + 'FOPSORT' => array('std' , 'extension'), + + 'ASTETCDIR' => array('dir' , '/etc/asterisk'), + 'ASTMODDIR' => array('dir' , '/usr/lib/asterisk/modules'), + 'ASTVARLIBDIR' => array('dir' , '/var/lib/asterisk'), + 'ASTAGIDIR' => array('dir' , '/var/lib/asterisk/agi-bin'), + 'ASTSPOOLDIR' => array('dir' , '/var/spool/asterisk/'), + 'ASTRUNDIR' => array('dir' , '/var/run/asterisk'), + 'ASTLOGDIR' => array('dir' , '/var/log/asterisk'), + 'AMPBIN' => array('dir' , '/var/lib/asterisk/bin'), + 'AMPSBIN' => array('dir' , '/usr/sbin'), + 'AMPWEBROOT' => array('dir' , '/var/www/html'), + 'FOPWEBROOT' => array('dir' , '/var/www/html/panel'), + + 'USECATEGORIES' => array('bool' , true), + 'ENABLECW' => array('bool' , true), + 'CWINUSEBUSY' => array('bool' , true), + 'FOPRUN' => array('bool' , true), + 'AMPBADNUMBER' => array('bool' , true), + 'DEVEL' => array('bool' , false), + 'DEVELRELOAD' => array('bool' , false), + 'CUSTOMASERROR' => array('bool' , true), + 'DYNAMICHINTS' => array('bool' , false), + 'BADDESTABORT' => array('bool' , false), + 'SERVERINTITLE' => array('bool' , false), + 'XTNCONFLICTABORT' => array('bool' , false), +); + +function parse_amportal_conf($filename) { + global $amp_conf_defaults; + + /* defaults + * This defines defaults and formating to assure consistency across the system so that + * components don't have to keep being 'gun shy' about these variables. + * + + */ + $file = file($filename); + if (is_array($file)) { + foreach ($file as $line) { + if (preg_match("/^\s*([a-zA-Z0-9_]+)=([a-zA-Z0-9 .&-@=_<>\"\']+)\s*$/",$line,$matches)) { + $conf[ $matches[1] ] = $matches[2]; + } + } + } else { + die_freepbx("

".sprintf(_("Missing or unreadable config file (%s)...cannot continue"), $filename)."

"); + } + + // set defaults + foreach ($amp_conf_defaults as $key=>$arr) { + + switch ($arr[0]) { + // for type dir, make sure there is no trailing '/' to keep consistent everwhere + // + case 'dir': + if (!isset($conf[$key]) || trim($conf[$key]) == '') { + $conf[$key] = $arr[1]; + } else { + $conf[$key] = rtrim($conf[$key],'/'); + } + break; + // booleans: + // "yes", "true", "on", true, 1 (case-insensitive) will be treated as true, everything else is false + // + case 'bool': + if (!isset($conf[$key])) { + $conf[$key] = $arr[1]; + } else { + $conf[$key] = ($conf[$key] === true || strtolower($conf[$key]) == 'true' || $conf[$key] === 1 || $conf[$key] == '1' + || strtolower($conf[$key]) == 'yes' || strtolower($conf[$key]) == 'on'); + } + break; + default: + if (!isset($conf[$key])) { + $conf[$key] = $arr[1]; + } else { + $conf[$key] = trim($conf[$key]); + } + } + } + +/* + TODO: what was this, should the comment be removed? + + if (($amp_conf["AMPDBENGINE"] == "sqlite") && (!isset($amp_conf["AMPDBENGINE"]))) + $amp_conf["AMPDBFILE"] = "/var/lib/freepbx/freepbx.sqlite"; +*/ + + return $conf; +} + +function parse_asterisk_conf($filename) { + //TODO: Should the correction of $amp_conf be passed by refernce and optional? + // + global $amp_conf; + + $convert = array( + 'astetcdir' => 'ASTETCDIR', + 'astmoddir' => 'ASTMODDIR', + 'astvarlibdir' => 'ASTVARLIBDIR', + 'astagidir' => 'ASTAGIDIR', + 'astspooldir' => 'ASTSPOOLDIR', + 'astrundir' => 'ASTRUNDIR', + 'astlogdir' => 'ASTLOGDIR' + ); + + $file = file($filename); + foreach ($file as $line) { + if (preg_match("/^\s*([a-zA-Z0-9]+)\s* => \s*(.*)\s*([;#].*)?/",$line,$matches)) { + $conf[ $matches[1] ] = rtrim($matches[2],'/'); + } + } + + // Now that we parsed asterisk.conf, we need to make sure $amp_conf is consistent + // so just set it to what we found, since this is what asterisk will use anyhow. + // + foreach ($convert as $ast_conf_key => $amp_conf_key) { + if (isset($conf[$ast_conf_key])) { + $amp_conf[$amp_conf_key] = $conf[$ast_conf_key]; + } + } + return $conf; +} + + +define("NOTIFICATION_TYPE_CRITICAL", 100); +define("NOTIFICATION_TYPE_SECURITY", 200); +define("NOTIFICATION_TYPE_UPDATE", 300); +define("NOTIFICATION_TYPE_ERROR", 400); +define("NOTIFICATION_TYPE_WARNING" , 500); +define("NOTIFICATION_TYPE_NOTICE", 600); + +class notifications { + + var $not_loaded = true; + var $notification_table = array(); + var $_db; + + function &create(&$db) { + static $obj; + if (!isset($obj)) { + $obj = new notifications($db); + } + return $obj; + } + + function notifications(&$db) { + $this->_db =& $db; + } + + + function add_critical($module, $id, $display_text, $extended_text="", $link="", $reset=true, $candelete=false) { + $this->_add_type(NOTIFICATION_TYPE_CRITICAL, $module, $id, $display_text, $extended_text, $link, $reset, $candelete); + } + function add_security($module, $id, $display_text, $extended_text="", $link="", $reset=true, $candelete=false) { + $this->_add_type(NOTIFICATION_TYPE_SECURITY, $module, $id, $display_text, $extended_text, $link, $reset, $candelete); + } + function add_update($module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=false) { + $this->_add_type(NOTIFICATION_TYPE_UPDATE, $module, $id, $display_text, $extended_text, $link, $reset, $candelete); + } + function add_error($module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=false) { + $this->_add_type(NOTIFICATION_TYPE_ERROR, $module, $id, $display_text, $extended_text, $link, $reset, $candelete); + } + function add_warning($module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=false) { + $this->_add_type(NOTIFICATION_TYPE_WARNING, $module, $id, $display_text, $extended_text, $link, $reset, $candelete); + } + function add_notice($module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=true) { + $this->_add_type(NOTIFICATION_TYPE_NOTICE, $module, $id, $display_text, $extended_text, $link, $reset, $candelete); + } + + + function list_critical($show_reset=false) { + return $this->_list(NOTIFICATION_TYPE_CRITICAL, $show_reset); + } + function list_security($show_reset=false) { + return $this->_list(NOTIFICATION_TYPE_SECURITY, $show_reset); + } + function list_update($show_reset=false) { + return $this->_list(NOTIFICATION_TYPE_UPDATE, $show_reset); + } + function list_error($show_reset=false) { + return $this->_list(NOTIFICATION_TYPE_ERROR, $show_reset); + } + function list_warning($show_reset=false) { + return $this->_list(NOTIFICATION_TYPE_WARNING, $show_reset); + } + function list_notice($show_reset=false) { + return $this->_list(NOTIFICATION_TYPE_NOTICE, $show_reset); + } + function list_all($show_reset=false) { + return $this->_list("", $show_reset); + } + + + function reset($module, $id) { + $module = q($module); + $id = q($id); + + $sql = "UPDATE notifications SET reset = 1 WHERE module = $module AND id = $id"; + sql($sql); + } + + function delete($module, $id) { + $module = q($module); + $id = q($id); + + $sql = "DELETE FROM notifications WHERE module = $module AND id = $id"; + sql($sql); + } + + function safe_delete($module, $id) { + $module = q($module); + $id = q($id); + + $sql = "DELETE FROM notifications WHERE module = $module AND id = $id AND candelete = 1"; + sql($sql); + } + + /* Internal functions + */ + + function _add_type($level, $module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=false) { + if ($this->not_loaded) { + $this->notification_table = $this->_list("",true); + $this->not_loaded = false; + } + + $existing_row = false; + foreach ($this->notification_table as $row) { + if ($row['module'] == $module && $row['id'] == $id ) { + $existing_row = $row; + break; + } + } + // Found an existing row - check if anything changed or if we are suppose to reset it + // + $candelete = $candelete ? 1 : 0; + if ($existing_row) { + + if (($reset && $existing_row['reset'] == 1) || $existing_row['level'] != $level || $existing_row['display_text'] != $display_text || $existing_row['extended_text'] != $extended_text || $existing_row['link'] != $link || $existing_row['candelete'] == $candelete) { + + // If $reset is set to the special case of PASSIVE then the updates will not change it's value in an update + // + $reset_value = ($reset == 'PASSIVE') ? $existing_row['reset'] : 0; + + $module = q($module); + $id = q($id); + $level = q($level); + $display_text = q($display_text); + $extended_text = q($extended_text); + $link = q($link); + $now = time(); + $sql = "UPDATE notifications SET + level = $level, + display_text = $display_text, + extended_text = $extended_text, + link = $link, + reset = $reset_value, + candelete = $candelete, + timestamp = $now + WHERE module = $module AND id = $id + "; + sql($sql); + + // TODO: I should really just add this to the internal cache, but really + // how often does this get called that if is a big deal. + $this->not_loaded = true; + } + } else { + // No existing row so insert this new one + // + $now = time(); + $module = q($module); + $id = q($id); + $level = q($level); + $display_text = q($display_text); + $extended_text = q($extended_text); + $link = q($link); + $sql = "INSERT INTO notifications + (module, id, level, display_text, extended_text, link, reset, candelete, timestamp) + VALUES + ($module, $id, $level, $display_text, $extended_text, $link, 0, $candelete, $now) + "; + sql($sql); + + // TODO: I should really just add this to the internal cache, but really + // how often does this get called that if is a big deal. + $this->not_loaded = true; + } + } + + function _list($level, $show_reset=false) { + + $level = q($level); + $where = array(); + + if (!$show_reset) { + $where[] = "reset = 0"; + } + + switch ($level) { + case NOTIFICATION_TYPE_CRITICAL: + case NOTIFICATION_TYPE_SECURITY: + case NOTIFICATION_TYPE_UPDATE: + case NOTIFICATION_TYPE_ERROR: + case NOTIFICATION_TYPE_WARNING: + case NOTIFICATION_TYPE_NOTICE: + $where[] = "level = $level "; + break; + default: + } + $sql = "SELECT * FROM notifications "; + if (count($where)) { + $sql .= " WHERE ".implode(" AND ", $where); + } + $sql .= " ORDER BY level, module"; + + $list = sql($sql,"getAll",DB_FETCHMODE_ASSOC); + return $list; + } + /* Returns the number of active notifications + */ + function get_num_active() { + $sql = "SELECT COUNT(id) FROM notifications WHERE reset = 0"; + return sql($sql,'getOne'); + } +} + +class cronmanager { + /** + * note: time is the hour time of day a job should run, -1 indicates don't care + */ + + function &create(&$db) { + static $obj; + if (!isset($obj)) { + $obj = new cronmanager($db); + } + return $obj; + } + + function cronmanager(&$db) { + $this->_db =& $db; + } + + function save_email($address) { + $address = q($address); + sql("DELETE FROM admin WHERE variable = 'email'"); + sql("INSERT INTO admin (variable, value) VALUES ('email', $address)"); + } + + function get_email() { + $sql = "SELECT value FROM admin WHERE variable = 'email'"; + return sql($sql, 'getOne'); + } + + function save_hash($id, &$string) { + $hash = md5($string); + $id = q($id); + sql("DELETE FROM admin WHERE variable = $id"); + sql("INSERT INTO admin (variable, value) VALUE ($id, '$hash')"); + } + + function check_hash($id, &$string) { + $id = q($id); + $sql = "SELECT value FROM admin WHERE variable = $id LIMIT 1"; + $hash = sql($sql, "getOne"); + return ($hash == md5($string)); + } + + function enable_updates($freq=24) { + global $amp_conf; + + $night_time = array(19,20,21,22,23,0,1,2,3,4,5); + $run_time = $night_time[rand(0,10)]; + $command = $amp_conf['AMPBIN']."/module_admin listonline"; + $lasttime = 0; + + $sql = "SELECT * FROM cronmanager WHERE module = 'module_admin' AND id = 'UPDATES'"; + $result = sql($sql, "getAll",DB_FETCHMODE_ASSOC); + if (count($result)) { + $sql = "UPDATE cronmanager SET + freq = '$freq', + command = '$command' + WHERE + module = 'module_admin' AND id = 'UPDATES' + "; + } else { + $sql = "INSERT INTO cronmanager + (module, id, time, freq, lasttime, command) + VALUES + ('module_admin', 'UPDATES', '$run_time', $freq, 0, '$command') + "; + } + sql($sql); + } + + function disable_updates() { + sql("DELETE FROM cronmanager WHERE module = 'module_admin' AND id = 'UPDATES'"); + } + + function updates_enabled() { + $results = sql("SELECT module, id FROM cronmanager WHERE module = 'module_admin' AND id = 'UPDATES'",'getAll'); + return count($results); + } + + /** run_jobs() + * select all entries that need to be run now and run them, then update the times. + * + * 1. select all entries + * 2. foreach entry, if its paramters indicate it should be run, then run it and + * update it was run in the time stamp. + */ + function run_jobs() { + + $errors = 0; + $error_arr = array(); + + $now = time(); + $jobs = sql("SELECT * FROM cronmanager","getAll", DB_FETCHMODE_ASSOC); + foreach ($jobs as $job) { + $nexttime = $job['lasttime'] + $job['freq']*3600; + if ($nexttime <= $now) { + if ($job['time'] >= 0 && $job['time'] < 24) { + $date_arr = getdate($now); + // Now if lasttime is 0, then we want this kicked off at the proper hour + // after wich the frequencey will set the pace for same time each night + // + if (($date_arr['hours'] != $job['time']) && !$job['lasttime']) { + continue; + } + } + } else { + // no need to run job, time is not up yet + continue; + } + // run the job + exec($job['command'],$job_out,$ret); + if ($ret) { + $errors++; + $error_arr[] = array($job['command'],$ret); + + // If there where errors, let's print them out in case the script is being debugged or running + // from cron which will then put the errors out through the cron system. + // + foreach ($job_out as $line) { + echo $line."\n"; + } + } else { + $module = $job['module']; + $id = $job['id']; + $sql = "UPDATE cronmanager SET lasttime = $now WHERE module = '$module' AND id = '$id'"; + sql($sql); + } + } + if ($errors) { + $nt =& notifications::create($db); + $text = sprintf(_("Cronmanager encountered %s Errors"),$errors); + $extext = _("The following commands failed with the listed error"); + foreach ($error_arr as $item) { + $extext .= "
".$item[0]." (".$item[1].")"; + } + $nt->add_error('cron_manager', 'EXECFAIL', $text, $extext, '', true, true); + } + } +} + +/** check if a specific extension is being used, or get a list of all extensions that are being used + * @param mixed an array of extension numbers to check against, or if boolean true then return list of all extensions + * @param array a hash of module names to search for callbacks, otherwise global $active_modules is used + * @return array returns an empty array if exten not in use, or any array with usage info, or of all usage + * if exten is boolean true + * @description Upon passing in an array of extension numbers, this api will query all modules to determine if any + * are using those extension numbers. If so, it will return an array with the usage information + * as described below, otherwise an empty array. If passed boolean true, it will return an array + * of the same format with all extensions on the system that are being used. + * + * $exten_usage[$module][$exten]['description'] // description of the extension + * ['edit_url'] // a url that could be invoked to edit extension + * ['status'] // Status: INUSE, RESERVED, RESTRICTED + */ +function framework_check_extension_usage($exten=true, $module_hash=false) { + global $active_modules; + $exten_usage = array(); + + if (!is_array($module_hash)) { + $module_hash = $active_modules; + } + + if (!is_array($exten) && $exten !== true) { + $exten = array($exten); + } + foreach(array_keys($module_hash) as $mod) { + $function = $mod."_check_extensions"; + if (function_exists($function)) { + $module_usage = $function($exten); + if (!empty($module_usage)) { + $exten_usage[$mod] = $module_usage; + } + } + } + if ($exten === true) { + return $exten_usage; + } else { + foreach (array_keys($exten_usage) as $mod) { + foreach ($exten as $test_exten) { + if (isset($exten_usage[$mod][$test_exten])) { + $exten_matches[$mod][$test_exten] = $exten_usage[$mod][$test_exten]; + } + } + } + } + return $exten_matches; +} + +/** check if a specific destination is being used, or get a list of all destinations that are being used + * @param mixed an array of destinations to check against, or if boolean true then return list of all destinations in use + * @param array a hash of module names to search for callbacks, otherwise global $active_modules is used + * @return array returns an empty array if destination not in use, or any array with usage info, or of all usage + * if dest is boolean true + * @description Upon passing in an array of destinations, this api will query all modules to determine if any + * are using that destination. If so, it will return an array with the usage information + * as described below, otherwise an empty array. If passed boolean true, it will return an array + * of the same format with all destinations on the system that are being used. + * + * $dest_usage[$module][]['dest'] // The destination being used + * ['description'] // Description of who is using it + * ['edit_url'] // a url that could be invoked to edit the using entity + * + */ +function framework_check_destination_usage($dest=true, $module_hash=false) { + global $active_modules; + $dest_usage = array(); + $dest_matches = array(); + + if (!is_array($module_hash)) { + $module_hash = $active_modules; + } + + if (!is_array($dest) && $dest !== true) { + $dest = array($dest); + } + foreach(array_keys($module_hash) as $mod) { + $function = $mod."_check_destinations"; + if (function_exists($function)) { + $module_usage = $function($dest); + if (!empty($module_usage)) { + $dest_usage[$mod] = $module_usage; + } + } + } + if ($dest === true) { + return $dest_usage; + } else { + /* + $destlist[] = array( + 'dest' => $thisdest, + 'description' => 'Annoucement: '.$result['description'], + 'edit_url' => 'config.php?display=announcement&type='.$type.'&extdisplay='.urlencode($thisid), + ); + */ + foreach (array_keys($dest_usage) as $mod) { + foreach ($dest as $test_dest) { + foreach ($dest_usage[$mod] as $dest_item) { + if ($dest_item['dest'] == $test_dest) { + $dest_matches[$mod][] = $dest_item; + } + } + } + } + } + return $dest_matches; +} + +/** provide optional alert() box and formatted url info for extension conflicts + * @param array an array of extensions that are in conflict obtained from framework_check_extension_usage + * @param boolean default false. True if url and descriptions should be split, false to combine (see return) + * @param boolean default true. True to echo an alert() box, false to bypass the alert box + * @return array returns an array of formatted URLs with descriptions. If $split is true, retuns an array + * of the URLs with each element an array in the format of array('label' => 'description, 'url' => 'a url') + * @description This is used upon detecting conflicting extension numbers to provide an optional alert box of the issue + * by a module which should abort the attempt to create the extension. It also returns an array of + * URLs that can be displayed by the module to show the conflicting extension(s) and links to edit + * them or further interogate. The resulting URLs are returned in an array either formatted for immediate + * display or split into a description and the raw URL to provide more fine grained control (or use with guielements). + */ +function framework_display_extension_usage_alert($usage_arr=array(),$split=false,$alert=true) { + $url = array(); + if (!empty($usage_arr)) { + $conflicts=0; + foreach($usage_arr as $rawmodule => $properties) { + foreach($properties as $exten => $details) { + $conflicts++; + if ($conflicts == 1) { + switch ($details['status']) { + case 'INUSE': + $str = "Extension $exten not available, it is currently used by ".htmlspecialchars($details['description'])."."; + if ($split) { + $url[] = array('label' => "Edit: ".htmlspecialchars($details['description']), + 'url' => $details['edit_url'], + ); + } else { + $url[] = "Edit: ".htmlspecialchars($details['description']).""; + } + break; + default: + $str = "This extension is not available: ".htmlspecialchars($details['description'])."."; + } + } else { + if ($split) { + $url[] = array('label' => "Edit: ".htmlspecialchars($details['description']), + 'url' => $details['edit_url'], + ); + } else { + $url[] = "Edit: ".htmlspecialchars($details['description']).""; + } + } + } + } + if ($conflicts > 1) { + $str .= sprintf(" There are %s additonal conflicts not listed",$conflicts-1); + } + } + if ($alert) { + echo ""; + } + return($url); +} + +/** check if a specific destination is being used, or get a list of all destinations that are being used + * @param mixed an array of destinations to check against + * @param array a hash of module names to search for callbacks, otherwise global $active_modules is used + * @return array array with a message and tooltip to display usage of this destination + * @description This is called to generate a label and tooltip which summarized the usage of this + * destination and a tooltip listing all the places that use it + * + */ +function framework_display_destination_usage($dest, $module_hash=false) { + + if (!is_array($dest)) { + $dest = array($dest); + } + $usage_list = framework_check_destination_usage($dest, $module_hash); + if (!empty($usage_list)) { + $usage_count = 0; + $str = null; + foreach ($usage_list as $mod_list) { + foreach ($mod_list as $details) { + $usage_count++; + $str .= $details['description']."
"; + } + } + $object = $usage_count > 1 ? "Objects":"Object"; + return array('text' => sprintf(_("Used as Destination by %s %s"),$usage_count, $object), + 'tooltip' => $str, + ); + } else { + return array(); + } +} + +/** determines which module a list of destinations belongs to, and if the destination object exists + * @param mixed an array of destinations to check against + * @param array a hash of module names to search for callbacks, otherwise global $active_modules is used + * @return array an array structure with informaiton about the destinations (see code) + * @description Mainly used by framework_list_problem_destinations. This function will find the module + * that a destination belongs to and determine if the object still exits. This allow it to + * either obtain the identify, identify it as an object that has been deleted, or identify + * it as an unknown destination, usually a custom destination. + * + */ +function framework_identify_destinations($dest, $module_hash=false) { + global $active_modules; + static $dest_cache = array(); + + $dest_results = array(); + + $dest_usage = array(); + $dest_matches = array(); + + if (!is_array($module_hash)) { + $module_hash = $active_modules; + } + + if (!is_array($dest)) { + $dest = array($dest); + } + + foreach ($dest as $target) { + if (isset($dest_cache[$target])) { + $dest_results[$target] = $dest_cache[$target]; + } else { + + $found_owner = false; + foreach(array_keys($module_hash) as $mod) { + $function = $mod."_getdestinfo"; + if (function_exists($function)) { + $check_module = $function($target); + if ($check_module !== false) { + $found_owner = true; + $dest_cache[$target] = array($mod => $check_module); + $dest_results[$target] = $dest_cache[$target]; + break; + } + } + } + if (! $found_owner) { + //echo "Not Found: $target\n"; + $dest_cache[$target] = false; + $dest_results[$target] = $dest_cache[$target]; + } + } + } + return $dest_results; +} + +/** create a comprehensive list of all destinations that are problematic + * @param array an array of destinations to check against + * @param bool set to true if custome (unknown) destinations should be reported + * @return array an array of the destinations that are empty, orphaned or custom + * @description This function will scan the entire system and identify destinations + * that are problematic. Either empty, orphaned or an unknow custom + * destinations. An orphaned destination is one that should belong + * to a module but the object it would have pointed to does not exist + * because it was probably deleted. + */ +function framework_list_problem_destinations($module_hash=false, $ignore_custom=false) { + global $active_modules; + + if (!is_array($module_hash)) { + $module_hash = $active_modules; + } + + $my_dest_arr = array(); + $problem_dests = array(); + + $all_dests = framework_check_destination_usage(true, $module_hash); + + foreach ($all_dests as $dests) { + foreach ($dests as $adest) { + if (!empty($adest['dest'])) { + $my_dest_arr[] = $adest['dest']; + } + } + } + $my_dest_arr = array_unique($my_dest_arr); + + $identities = framework_identify_destinations($my_dest_arr, $module_hash); + + foreach ($all_dests as $dests) { + foreach ($dests as $adest) { + if (empty($adest['dest'])) { + $problem_dests[] = array('status' => 'EMPTY', + 'dest' => $adest['dest'], + 'description' => $adest['description'], + 'edit_url' => $adest['edit_url'], + ); + } else if ($identities[$adest['dest']] === false){ + if ($ignore_custom) { + continue; + } + $problem_dests[] = array('status' => 'CUSTOM', + 'dest' => $adest['dest'], + 'description' => $adest['description'], + 'edit_url' => $adest['edit_url'], + ); + } else if (is_array($identities[$adest['dest']])){ + foreach ($identities[$adest['dest']] as $details) { + if (empty($details)) { + $problem_dests[] = array('status' => 'ORPHAN', + 'dest' => $adest['dest'], + 'description' => $adest['description'], + 'edit_url' => $adest['edit_url'], + ); + + } + break; // there is only one set per array + } + } else { + echo "ERROR?\n"; + var_dump($adest); + } + } + } + return $problem_dests; +} + +/** sort the hash based on the inner key + */ +function _framework_sort_exten($a, $b) { + $a_key = array_keys($a); + $a_key = $a_key[0]; + $b_key = array_keys($b); + $b_key = $b_key[0]; + if ($a_key == $b_key) { + return 0; + } else { + return ($a_key < $b_key) ? -1 : 1; + } +} + +/** create a comprehensive list of all extensions conflicts + * @return array an array of the destinations that are empty, orphaned or custom + * @description This returns an array structure with information about all + * extension numbers that are in conflict. This means the same number + * is being used by 2 or more modules and the results will be ambiguous + * which one will be ignored when dialed. See the code for the + * structure of the retured array. + */ +function framework_list_extension_conflicts($module_hash=false) { + global $active_modules; + + if (!is_array($module_hash)) { + $module_hash = $active_modules; + } + + $exten_list = framework_check_extension_usage(true,$module_hash); + + /** Bookkeeping hashes + * full_hash[] will contain the first extension encountered + * conflict_hash[] will contain any subsequent extensions if conflicts + * + * If there are conflicts, the full set is what is in conflict_hash + the + * first extension encoutnered in full_hash[] + */ + $full_hash = array(); + $conflict_hash = array(); + + foreach ($exten_list as $mod => $mod_extens) { + foreach ($mod_extens as $exten => $details) { + if (!isset($full_hash[$exten])) { + $full_hash[$exten] = $details; + } else { + $conflict_hash[] = array($exten => $details); + } + } + } + + // extract conflicting remaining extension from full_hash but needs to be unique + // + if (!empty($conflict_hash)) { + $other_conflicts = array(); + foreach ($conflict_hash as $item) { + foreach (array_keys($item) as $exten) { + $other_conflicts[$exten] = $full_hash[$exten]; + } + } + foreach ($other_conflicts as $exten => $details) { + $conflict_hash[] = array($exten => $details); + } + usort($conflict_hash, "_framework_sort_exten"); + return $conflict_hash; + } +} + +/** Expands variables from amportal.conf + * Replaces any variables enclosed in percent (%) signs with their value + * eg, "%AMPWEBROOT%/admin/functions.inc.php" + */ +function expand_variables($string) { + global $amp_conf; + $search = $replace = array(); + foreach ($amp_conf as $key=>$value) { + $search[] = '%'.$key.'%'; + $replace[] = $value; + } + return str_replace($search, $replace, $string); +} + +function getAmpAdminUsers() { + global $db; + + $sql = "SELECT username FROM ampusers WHERE sections='*'"; + $results = $db->getAll($sql); + if(DB::IsError($results)) { + die_freepbx($results->getMessage()); + } + return $results; +} + +function getAmpUser($username) { + global $db; + + $sql = "SELECT username, password, extension_low, extension_high, deptname, sections FROM ampusers WHERE username = '".addslashes($username)."'"; + $results = $db->getAll($sql); + if(DB::IsError($results)) { + die_freepbx($results->getMessage()); + } + + if (count($results) > 0) { + $user = array(); + $user["username"] = $results[0][0]; + $user["password"] = $results[0][1]; + $user["extension_low"] = $results[0][2]; + $user["extension_high"] = $results[0][3]; + $user["deptname"] = $results[0][4]; + $user["sections"] = explode(";",$results[0][5]); + return $user; + } else { + return false; + } +} + +class ampuser { + var $username; + var $_password; + var $_extension_high; + var $_extension_low; + var $_deptname; + var $_sections; + + function ampuser($username) { + $this->username = $username; + if ($user = getAmpUser($username)) { + $this->_password = $user["password"]; + $this->_extension_high = $user["extension_high"]; + $this->_extension_low = $user["extension_low"]; + $this->_deptname = $user["deptname"]; + $this->_sections = $user["sections"]; + } else { + // user doesn't exist + $this->_password = false; + $this->_extension_high = ""; + $this->_extension_low = ""; + $this->_deptname = ""; + $this->_sections = array(); + } + } + + /** Give this user full admin access + */ + function setAdmin() { + $this->_extension_high = ""; + $this->_extension_low = ""; + $this->_deptname = ""; + $this->_sections = array("*"); + } + + function checkPassword($password) { + // strict checking so false will never match + return ($this->_password === $password); + } + + function checkSection($section) { + // if they have * then it means all sections + return in_array("*", $this->_sections) || in_array($section, $this->_sections); + } +} + +// returns true if extension is within allowed range +function checkRange($extension){ + $low = isset($_SESSION["AMP_user"]->_extension_low)?$_SESSION["AMP_user"]->_extension_low:''; + $high = isset($_SESSION["AMP_user"]->_extension_high)?$_SESSION["AMP_user"]->_extension_high:''; + + if ((($extension >= $low) && ($extension <= $high)) || ($low == '' && $high == '')) + return true; + else + return false; +} + +// returns true if department string matches dept for this user +function checkDept($dept){ + $deptname = isset($_SESSION["AMP_user"])?$_SESSION["AMP_user"]->_deptname:null; + + if ( ($dept == null) || ($dept == $deptname) ) + return true; + else + return false; +} + +function engine_getinfo() { + global $amp_conf; + global $astman; + + switch ($amp_conf['AMPENGINE']) { + case 'asterisk': + if (isset($astman) && $astman->connected()) { + //get version (1.4) + $response = $astman->send_request('Command', array('Command'=>'core show version')); + if (preg_match('/No such command/',$response['data'])) { + // get version (1.2) + $response = $astman->send_request('Command', array('Command'=>'show version')); + } + $verinfo = $response['data']; + } else { + // could not connect to asterisk manager, try console + $verinfo = exec('asterisk -V'); + } + + if (preg_match('/Asterisk (\d+(\.\d+)*)(-?(\S*))/', $verinfo, $matches)) { + return array('engine'=>'asterisk', 'version' => $matches[1], 'additional' => $matches[4]); + } elseif (preg_match('/Asterisk SVN-(\d+(\.\d+)*)(-?(\S*))/', $verinfo, $matches)) { + return array('engine'=>'asterisk', 'version' => $matches[1], 'additional' => $matches[4]); + } elseif (preg_match('/Asterisk SVN-branch-(\d+(\.\d+)*)-r(-?(\S*))/', $verinfo, $matches)) { + return array('engine'=>'asterisk', 'version' => $matches[1].'.'.$matches[4], 'additional' => $matches[4]); + } elseif (preg_match('/Asterisk SVN-trunk-r(-?(\S*))/', $verinfo, $matches)) { + return array('engine'=>'asterisk', 'version' => '1.6', 'additional' => $matches[1]); + } + + return array('engine'=>'ERROR-UNABLE-TO-CONNECT', 'version'=>'0', 'additional' => '0'); + break; + } + return array('engine'=>'ERROR-UNSUPPORTED-ENGINE-'.$amp_conf['AMPENGINE'], 'version'=>'0', 'additional' => '0'); +} + + +if (!function_exists('version_compare_freepbx')) { + /* verison_compare that works with freePBX version numbers + */ + function version_compare_freepbx($version1, $version2, $op = null) { + $version1 = str_replace("rc","RC", strtolower($version1)); + $version2 = str_replace("rc","RC", strtolower($version2)); + if (!is_null($op)) { + return version_compare($version1, $version2, $op); + } else { + return version_compare($version1, $version2); + } + } +} + + +/* queries database using PEAR. +* $type can be query, getAll, getRow, getCol, getOne, etc +* $fetchmode can be DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC, DB_FETCHMODE_OBJECT +* returns array, unless using getOne +*/ +function sql($sql,$type="query",$fetchmode=null) { + global $db; + $results = $db->$type($sql,$fetchmode); + if(DB::IsError($results)) { + die_freepbx($results->getDebugInfo() . "SQL -
$sql" ); + } + return $results; +} + +/** Format input so it can be safely used as a literal in a query. + * Literals are values such as strings or numbers which get utilized in places + * like WHERE, SET and VALUES clauses of SQL statements. + * The format returned depends on the PHP data type of input and the database + * type being used. This simply calls PEAR's DB::smartQuote() function + * @param mixed The value to go into the database + * @return string A value that can be safely inserted into an SQL query + */ +function q(&$value) { + global $db; + return $db->quoteSmart($value); +} + +// sql text formatting -- couldn't see that one was available already +function sql_formattext($txt) { + if (isset($txt)) { + $fmt = str_replace("'", "''", $txt); + $fmt = "'" . $fmt . "'"; + } else { + $fmt = 'null'; + } + + return $fmt; +} + + +function die_freepbx($text, $extended_text="", $type="FATAL") { + if (function_exists('fatal')) { + // "custom" error handler + // fatal may only take one param, so we suppress error messages because it doesn't really matter + @fatal($text, $extended_text, $type); + } else if (isset($_SERVER['REQUEST_METHOD'])) { + // running in webserver + echo "

".$type." ERROR

\n"; + echo "

".$text."

\n"; + if (!empty($extended_text)) { + echo "

".$extended_text."

\n"; + } + } else { + // CLI + echo "[$type] ".$text." ".$extended_text."\n"; + } + + // always ensure we exit at this point + exit(1); +} + +//tell application we need to reload asterisk +function needreload() { + global $db; + $sql = "UPDATE admin SET value = 'true' WHERE variable = 'need_reload'"; + $result = $db->query($sql); + if(DB::IsError($result)) { + die_freepbx($result->getMessage()); + } +} + +function check_reload_needed() { + global $db; + global $amp_conf; + $sql = "SELECT value FROM admin WHERE variable = 'need_reload'"; + $row = $db->getRow($sql); + if(DB::IsError($row)) { + die_freepbx($row->getMessage()); + } + return ($row[0] == 'true' || $amp_conf['DEVELRELOAD']); +} + +function do_reload() { + global $amp_conf, $asterisk_conf, $db, $astman; + + $notify =& notifications::create($db); + + $return = array('num_errors'=>0,'test'=>'abc'); + $exit_val = null; + + if (isset($amp_conf["PRE_RELOAD"]) && !empty($amp_conf['PRE_RELOAD'])) { + exec( $amp_conf["PRE_RELOAD"], $output, $exit_val ); + + if ($exit_val != 0) { + $desc = sprintf(_("Exit code was %s and output was: %s"), $exit_val, "\n\n".implode("\n",$output)); + $notify->add_error('freepbx','reload_pre_script', sprintf(_('Could not run %s script.'), $amp_conf['PRE_RELOAD']), $desc); + + $return['num_errors']++; + } else { + $notify->delete('freepbx', 'reload_pre_script'); + } + } + + $retrieve = $amp_conf['AMPBIN'].'/retrieve_conf'; + //exec($retrieve.'&>'.$asterisk_conf['astlogdir'].'/freepbx-retrieve.log', $output, $exit_val); + exec($retrieve, $output, $exit_val); + + // retrive_conf html output + $return['retrieve_conf'] = 'exit: '.$exit_val.'
'.implode('
',$output); + + if ($exit_val != 0) { + $return['status'] = false; + $return['message'] = sprintf(_('Reload failed because retrieve_conf encountered an error: %s'),$exit_val); + $return['num_errors']++; + $notify->add_critical('freepbx','RCONFFAIL', _("retrieve_conf failed, config not applied"), $return['message']); + return $return; + } + + if (!isset($astman) || !$astman) { + $return['status'] = false; + $return['message'] = _('Reload failed because FreePBX could not connect to the asterisk manager interface.'); + $return['num_errors']++; + $notify->add_critical('freepbx','RCONFFAIL', _("retrieve_conf failed, config not applied"), $return['message']); + return $return; + } + $notify->delete('freepbx', 'RCONFFAIL'); + + //reload MOH to get around 'reload' not actually doing that. + $astman->send_request('Command', array('Command'=>'moh reload')); + + //reload asterisk + $astman->send_request('Command', array('Command'=>'reload')); + + $return['status'] = true; + $return['message'] = _('Successfully reloaded'); + + + if ($amp_conf['FOPRUN']) { + //bounce op_server.pl + $wOpBounce = $amp_conf['AMPBIN'].'/bounce_op.sh'; + exec($wOpBounce.' &>'.$asterisk_conf['astlogdir'].'/freepbx-bounce_op.log', $output, $exit_val); + + if ($exit_val != 0) { + $desc = _('Could not reload the FOP operator panel server using the bounce_op.sh script. Configuration changes may not be reflected in the panel display.'); + $notify->add_error('freepbx','reload_fop', _('Could not reload FOP server'), $desc); + + $return['num_errors']++; + } else { + $notify->delete('freepbx','reload_fop'); + } + } + + //store asterisk reloaded status + $sql = "UPDATE admin SET value = 'false' WHERE variable = 'need_reload'"; + $result = $db->query($sql); + if(DB::IsError($result)) { + $return['message'] = _('Successful reload, but could not clear reload flag due to a database error: ').$db->getMessage(); + $return['num_errors']++; + } + + if (isset($amp_conf["POST_RELOAD"]) && !empty($amp_conf['POST_RELOAD'])) { + exec( $amp_conf["POST_RELOAD"], $output, $exit_val ); + + if ($exit_val != 0) { + $desc = sprintf(_("Exit code was %s and output was: %s"), $exit_val, "\n\n".implode("\n",$output)); + $notify->add_error('freepbx','reload_post_script', sprintf(_('Could not run %s script.'), 'POST_RELOAD'), $desc); + + $return['num_errors']++; + } else { + $notify->delete('freepbx', 'reload_post_script'); + } + } + + return $return; +} + +//get the version number +function getversion() { + global $db; + $sql = "SELECT value FROM admin WHERE variable = 'version'"; + $results = $db->getRow($sql); + if(DB::IsError($results)) { + die_freepbx($results->getMessage()); + } + return $results[0]; +} + +//get the version number +function get_framework_version() { + global $db; + $sql = "SELECT version FROM modules WHERE modulename = 'framework' AND enabled = 1"; + $version = $db->getOne($sql); + if(DB::IsError($version)) { + die_freepbx($version->getMessage()); + } + return $version; +} + +// draw list for users and devices with paging +function drawListMenu($results, $skip, $type, $dispnum, $extdisplay, $description) { + // Dirty Fix to get rid of [NEXT/PREV] since I'm not sure what passing skip does and don't want to mess with it. + // When someone feels like looking closer at the below, probably should remove the code. + // I removed pagination cause of the new scroll box ticket #1415 + $perpage=20000; + + $skipped = 0; + $index = 0; + if ($skip == "") $skip = 0; + echo "\n"; +} + +// this function returns true if $astman is defined and set to something (implying a current connection, false otherwise. +// this function no longer puts out an error message, it is up to the caller to handle the situation. +// Should probably be changed (at least name) to check if a connection is available to the current engine) +// +function checkAstMan() { + global $astman; + + return ($astman)?true:false; +} + +/* merge_ext_followme($dest) { + * + * The purpose of this function is to take a destination + * that was either a core extension OR a findmefollow-destination + * and convert it so that they are merged and handled just like + * direct-did routing + * + * Assuming an extension number of 222: + * + * The two formats that existed for findmefollow were: + * + * ext-findmefollow,222,1 + * ext-findmefollow,FM222,1 + * + * The one format that existed for core was: + * + * ext-local,222,1 + * + * In all those cases they should be converted to: + * + * from-did-direct,222,1 + * + */ +function merge_ext_followme($dest) { + + if (preg_match("/^\s*ext-findmefollow,(FM)?(\d+),(\d+)/",$dest,$matches) || + preg_match("/^\s*ext-local,(FM)?(\d+),(\d+)/",$dest,$matches) ) { + // matches[2] => extn + // matches[3] => priority + return "from-did-direct,".$matches[2].",".$matches[3]; + } else { + return $dest; + } +} + + +/** Recursively read voicemail.conf (and any included files) + * This function is called by getVoicemailConf() + */ +function parse_voicemailconf($filename, &$vmconf, &$section) { + if (is_null($vmconf)) { + $vmconf = array(); + } + if (is_null($section)) { + $section = "general"; + } + + if (file_exists($filename)) { + $fd = fopen($filename, "r"); + while ($line = fgets($fd, 1024)) { + if (preg_match("/^\s*(\d+)\s*=>\s*(\d*),(.*),(.*),(.*),(.*)\s*([;#].*)?/",$line,$matches)) { + // "mailbox=>password,name,email,pager,options" + // this is a voicemail line + $vmconf[$section][ $matches[1] ] = array("mailbox"=>$matches[1], + "pwd"=>$matches[2], + "name"=>$matches[3], + "email"=>$matches[4], + "pager"=>$matches[5], + "options"=>array(), + ); + + // parse options + //output($matches); + foreach (explode("|",$matches[6]) as $opt) { + $temp = explode("=",$opt); + //output($temp); + if (isset($temp[1])) { + list($key,$value) = $temp; + $vmconf[$section][ $matches[1] ]["options"][$key] = $value; + } + } + } else if (preg_match("/^\s*(\d+)\s*=>\s*dup,(.*)\s*([;#].*)?/",$line,$matches)) { + // "mailbox=>dup,name" + // duplace name line + $vmconf[$section][ $matches[1] ]["dups"][] = $matches[2]; + } else if (preg_match("/^\s*#include\s+(.*)\s*([;#].*)?/",$line,$matches)) { + // include another file + + if ($matches[1][0] == "/") { + // absolute path + $filename = $matches[1]; + } else { + // relative path + $filename = dirname($filename)."/".$matches[1]; + } + + parse_voicemailconf($filename, $vmconf, $section); + + } else if (preg_match("/^\s*\[(.+)\]/",$line,$matches)) { + // section name + $section = strtolower($matches[1]); + } else if (preg_match("/^\s*([a-zA-Z0-9-_]+)\s*=\s*(.*?)\s*([;#].*)?$/",$line,$matches)) { + // name = value + // option line + $vmconf[$section][ $matches[1] ] = $matches[2]; + } + } + fclose($fd); + } +} + +/** Write the voicemail.conf file + * This is called by saveVoicemail() + * It's important to make a copy of $vmconf before passing it. Since this is a recursive function, has to + * pass by reference. At the same time, it removes entries as it writes them to the file, so if you don't have + * a copy, by the time it's done $vmconf will be empty. +*/ +function write_voicemailconf($filename, &$vmconf, &$section, $iteration = 0) { + global $amp_conf; + if ($iteration == 0) { + $section = null; + } + + $output = array(); + + // if the file does not, copy if from the template. + // TODO: is this logical? + // TODO: don't use hardcoded path...? + if (!file_exists($filename)) { + if (!copy( rtrim($amp_conf["ASTETCDIR"],"/")."/voicemail.conf.template", $filename )){ + return; + } + } + + $fd = fopen($filename, "r"); + while ($line = fgets($fd, 1024)) { + if (preg_match("/^(\s*)(\d+)(\s*)=>(\s*)(\d*),(.*),(.*),(.*),(.*)(\s*[;#].*)?$/",$line,$matches)) { + // "mailbox=>password,name,email,pager,options" + // this is a voicemail line + //DEBUG echo "\nmailbox"; + + // make sure we have something as a comment + if (!isset($matches[10])) { + $matches[10] = ""; + } + + // $matches[1] [3] and [4] are to preserve indents/whitespace, we add these back in + + if (isset($vmconf[$section][ $matches[2] ])) { + // we have this one loaded + // repopulate from our version + $temp = & $vmconf[$section][ $matches[2] ]; + + $options = array(); + foreach ($temp["options"] as $key=>$value) { + $options[] = $key."=".$value; + } + + $output[] = $matches[1].$temp["mailbox"].$matches[3]."=>".$matches[4].$temp["pwd"].",".$temp["name"].",".$temp["email"].",".$temp["pager"].",". implode("|",$options).$matches[10]; + + // remove this one from $vmconf + unset($vmconf[$section][ $matches[2] ]); + } else { + // we don't know about this mailbox, so it must be deleted + // (and hopefully not JUST added since we did read_voiceamilconf) + + // do nothing + } + + } else if (preg_match("/^(\s*)(\d+)(\s*)=>(\s*)dup,(.*)(\s*[;#].*)?$/",$line,$matches)) { + // "mailbox=>dup,name" + // duplace name line + // leave it as-is (for now) + //DEBUG echo "\ndup mailbox"; + $output[] = $line; + } else if (preg_match("/^(\s*)#include(\s+)(.*)(\s*[;#].*)?$/",$line,$matches)) { + // include another file + //DEBUG echo "\ninclude ".$matches[3]."
"; + + // make sure we have something as a comment + if (!isset($matches[4])) { + $matches[4] = ""; + } + + if ($matches[3][0] == "/") { + // absolute path + $include_filename = $matches[3]; + } else { + // relative path + $include_filename = dirname($filename)."/".$matches[3]; + } + + $output[] = $matches[1]."#include".$matches[2].$matches[3].$matches[4]; + write_voicemailconf($include_filename, $vmconf, $section, $iteration+1); + + //DEBUG echo "
"; + + } else if (preg_match("/^(\s*)\[(.+)\](\s*[;#].*)?$/",$line,$matches)) { + // section name + //DEBUG echo "\nsection"; + + // make sure we have something as a comment + if (!isset($matches[3])) { + $matches[3] = ""; + } + + // check if this is the first run (section is null) + if ($section !== null) { + // we need to add any new entries here, before the section changes + //DEBUG echo "
"; + //DEBUG var_dump($vmconf[$section]); + if (isset($vmconf[$section])){ //need this, or we get an error if we unset the last items in this section - should probably automatically remove the section/context from voicemail.conf + foreach ($vmconf[$section] as $key=>$value) { + if (is_array($value)) { + // mailbox line + + $temp = & $vmconf[$section][ $key ]; + + $options = array(); + foreach ($temp["options"] as $key1=>$value) { + $options[] = $key1."=".$value; + } + + $output[] = $temp["mailbox"]." => ".$temp["pwd"].",".$temp["name"].",".$temp["email"].",".$temp["pager"].",". implode("|",$options); + + // remove this one from $vmconf + unset($vmconf[$section][ $key ]); + + } else { + // option line + + $output[] = $key."=".$vmconf[$section][ $key ]; + + // remove this one from $vmconf + unset($vmconf[$section][ $key ]); + } + } + } + //DEBUG echo "
"; + } + + $section = strtolower($matches[2]); + $output[] = $matches[1]."[".$section."]".$matches[3]; + $existing_sections[] = $section; //remember that this section exists + + } else if (preg_match("/^(\s*)([a-zA-Z0-9-_]+)(\s*)=(\s*)(.*?)(\s*[;#].*)?$/",$line,$matches)) { + // name = value + // option line + //DEBUG echo "\noption line"; + + + // make sure we have something as a comment + if (!isset($matches[6])) { + $matches[6] = ""; + } + + if (isset($vmconf[$section][ $matches[2] ])) { + $output[] = $matches[1].$matches[2].$matches[3]."=".$matches[4].$vmconf[$section][ $matches[2] ].$matches[6]; + + // remove this one from $vmconf + unset($vmconf[$section][ $matches[2] ]); + } + // else it's been deleted, so we don't write it in + + } else { + // unknown other line -- probably a comment or whitespace + //DEBUG echo "\nother: ".$line; + + $output[] = str_replace(array("\n","\r"),"",$line); // str_replace so we don't double-space + } + } + + if (($iteration == 0) && (is_array($vmconf))) { + // we need to add any new entries here, since it's the end of the file + //DEBUG echo "END OF FILE!!
"; + //DEBUG var_dump($vmconf); + foreach (array_keys($vmconf) as $section) { + if (!in_array($section,$existing_sections)) // If this is a new section, write the context label + $output[] = "[".$section."]"; + foreach ($vmconf[$section] as $key=>$value) { + if (is_array($value)) { + // mailbox line + + $temp = & $vmconf[$section][ $key ]; + + $options = array(); + foreach ($temp["options"] as $key=>$value) { + $options[] = $key."=".$value; + } + + $output[] = $temp["mailbox"]." => ".$temp["pwd"].",".$temp["name"].",".$temp["email"].",".$temp["pager"].",". implode("|",$options); + + // remove this one from $vmconf + unset($vmconf[$section][ $key ]); + + } else { + // option line + + $output[] = $key."=".$vmconf[$section][ $key ]; + + // remove this one from $vmconf + unset($vmconf[$section][$key ]); + } + } + } + //DEBUG echo "
"; + } + + fclose($fd); + + //DEBUG echo "\n\nwriting ".$filename; + //DEBUG echo "\n-----------\n"; + //DEBUG echo implode("\n",$output); + //DEBUG echo "\n-----------\n"; + + // write this file back out + + if ($fd = fopen($filename, "w")) { + fwrite($fd, implode("\n",$output)."\n"); + fclose($fd); + } + +} + + +// $goto is the current goto destination setting +// $i is the destination set number (used when drawing multiple destination sets in a single form ie: digital receptionist) +// esnure that any form that includes this calls the setDestinations() javascript function on submit. +// ie: if the form name is "edit", and drawselects has been called with $i=2 then use onsubmit="setDestinations(edit,2)" +function drawselects($goto,$i,$show_custom=false) { + + /* --- MODULES BEGIN --- */ + global $active_modules; + + $all_destinations = array(); + + $selectHtml = ''; + + //check for module-specific destination functions + foreach ($active_modules as $rawmod => $module) { + $funct = strtolower($rawmod.'_destinations'); + + //if the modulename_destinations() function exits, run it and display selections for it + if (function_exists($funct)) { + $destArray = $funct(); //returns an array with 'destination' and 'description', and optionally 'category' + if (is_Array($destArray)) { + foreach ($destArray as $dest) { + $cat = (isset($dest['category']) ? $dest['category'] : $module['displayname']); + $all_destinations[$cat][] = $dest; + } + } + } + } +// var_dump($all_destinations); +// var_dump($goto); + + $foundone = false; + foreach ($all_destinations as $cat=>$destination) { + // create a select option for each destination + $options = ""; + $checked = false; + foreach ($destination as $dest) { + $options .= '